1 //
2 // Copyright (C) 2021 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15
16 #include "host/commands/test_gce_driver/key_pair.h"
17
18 #include <openssl/bio.h>
19 #include <openssl/evp.h>
20 #include <openssl/pem.h>
21 #include <openssl/rsa.h>
22
23 #include <memory>
24 #include <string>
25
26 #include <android-base/logging.h>
27
28 #include "common/libs/utils/result.h"
29 #include "common/libs/utils/subprocess.h"
30
31 namespace cuttlefish {
32
SslRecordErrCallback(const char * str,size_t len,void * data)33 static int SslRecordErrCallback(const char* str, size_t len, void* data) {
34 *reinterpret_cast<std::string*>(data) = std::string(str, len);
35 return 1; // success
36 }
37
38 class BoringSslKeyPair : public KeyPair {
39 public:
40 /*
41 * We interact with boringssl directly here to avoid ssh-keygen writing
42 * directly to the filesystem. The relevant ssh-keygen command here is
43 *
44 * $ ssh-keygen -t rsa -N "" -f ${TARGET}
45 *
46 * which unfortunately tries to write to `${TARGET}.pub`, making it hard to
47 * use something like /dev/stdout or /proc/self/fd/1 to get the keys.
48 */
CreateRsa(size_t bytes)49 static Result<std::unique_ptr<KeyPair>> CreateRsa(size_t bytes) {
50 std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)> ctx{
51 EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL), EVP_PKEY_CTX_free};
52 std::string error;
53 if (!ctx) {
54 ERR_print_errors_cb(SslRecordErrCallback, &error);
55 return CF_ERR("EVP_PKEY_CTX_new_id failed: " << error);
56 }
57 if (EVP_PKEY_keygen_init(ctx.get()) <= 0) {
58 ERR_print_errors_cb(SslRecordErrCallback, &error);
59 return CF_ERR("EVP_PKEY_keygen_init failed: " << error);
60 }
61 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), bytes) <= 0) {
62 ERR_print_errors_cb(SslRecordErrCallback, &error);
63 return CF_ERR("EVP_PKEY_CTX_set_rsa_keygen_bits failed: " << error);
64 }
65
66 EVP_PKEY* pkey = nullptr;
67 if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) {
68 ERR_print_errors_cb(SslRecordErrCallback, &error);
69 return CF_ERR("EVP_PKEY_keygen failed: " << error);
70 }
71 return std::unique_ptr<KeyPair>{new BoringSslKeyPair(pkey)};
72 }
73
PemPrivateKey() const74 Result<std::string> PemPrivateKey() const override {
75 std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
76 std::string error;
77 if (!bo) {
78 ERR_print_errors_cb(SslRecordErrCallback, &error);
79 return CF_ERR("BIO_new failed: " << error);
80 }
81 if (!PEM_write_bio_PrivateKey(bo.get(), pkey_.get(), NULL, NULL, 0, 0,
82 NULL)) {
83 ERR_print_errors_cb(SslRecordErrCallback, &error);
84 return CF_ERR("PEM_write_bio_PrivateKey failed: " << error);
85 }
86 std::string priv(BIO_pending(bo.get()), ' ');
87 auto written = BIO_read(bo.get(), priv.data(), priv.size());
88 if (written != priv.size()) {
89 return CF_ERR("Unexpected amount of data written: " << written << " != "
90 << priv.size());
91 }
92 return priv;
93 }
94
PemPublicKey() const95 Result<std::string> PemPublicKey() const override {
96 std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
97 std::string error;
98 if (!bo) {
99 ERR_print_errors_cb(SslRecordErrCallback, &error);
100 return CF_ERR("BIO_new failed: " << error);
101 }
102 if (!PEM_write_bio_PUBKEY(bo.get(), pkey_.get())) {
103 ERR_print_errors_cb(SslRecordErrCallback, &error);
104 return CF_ERR("PEM_write_bio_PUBKEY failed: " << error);
105 }
106
107 std::string priv(BIO_pending(bo.get()), ' ');
108 auto written = BIO_read(bo.get(), priv.data(), priv.size());
109 if (written != priv.size()) {
110 return CF_ERR("Unexpected amount of data written: " << written << " != "
111 << priv.size());
112 }
113 return priv;
114 }
115
116 /*
117 * OpenSSH has its own distinct format for public keys, which cannot be
118 * produced directly with OpenSSL/BoringSSL primitives. Luckily it is possible
119 * to convert the BoringSSL-generated RSA key without touching the filesystem.
120 */
OpenSshPublicKey() const121 Result<std::string> OpenSshPublicKey() const override {
122 auto pem_pubkey =
123 CF_EXPECT(PemPublicKey(), "Failed to get pem public key: ");
124 auto fd = SharedFD::MemfdCreateWithData("", pem_pubkey);
125 CF_EXPECT(fd->IsOpen(),
126 "Could not create pubkey memfd: " << fd->StrError());
127 Command cmd("/usr/bin/ssh-keygen");
128 cmd.AddParameter("-i");
129 cmd.AddParameter("-f");
130 cmd.AddParameter("/proc/self/fd/0");
131 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, fd);
132 cmd.AddParameter("-m");
133 cmd.AddParameter("PKCS8");
134 std::string out;
135 std::string err;
136 CF_EXPECT(RunWithManagedStdio(std::move(cmd), nullptr, &out, &err) == 0,
137 "Could not convert pem key to openssh key. "
138 << "stdout=\"" << out << "\", stderr=\"" << err << "\"");
139 return out;
140 }
141
142 private:
BoringSslKeyPair(EVP_PKEY * pkey)143 BoringSslKeyPair(EVP_PKEY* pkey) : pkey_(pkey, EVP_PKEY_free) {}
144
145 std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> pkey_;
146 };
147
CreateRsa(size_t bytes)148 Result<std::unique_ptr<KeyPair>> KeyPair::CreateRsa(size_t bytes) {
149 return BoringSslKeyPair::CreateRsa(bytes);
150 }
151
152 } // namespace cuttlefish
153