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 #include <android-base/result.h>
28
29 #include "common/libs/utils/subprocess.h"
30
31 using android::base::Error;
32 using android::base::Result;
33
34 namespace cuttlefish {
35
SslRecordErrCallback(const char * str,size_t len,void * data)36 static int SslRecordErrCallback(const char* str, size_t len, void* data) {
37 *reinterpret_cast<std::string*>(data) = std::string(str, len);
38 return 1; // success
39 }
40
41 class BoringSslKeyPair : public KeyPair {
42 public:
43 /*
44 * We interact with boringssl directly here to avoid ssh-keygen writing
45 * directly to the filesystem. The relevant ssh-keygen command here is
46 *
47 * $ ssh-keygen -t rsa -N "" -f ${TARGET}
48 *
49 * which unfortunately tries to write to `${TARGET}.pub`, making it hard to
50 * use something like /dev/stdout or /proc/self/fd/1 to get the keys.
51 */
CreateRsa(size_t bytes)52 static Result<std::unique_ptr<KeyPair>> CreateRsa(size_t bytes) {
53 std::unique_ptr<EVP_PKEY_CTX, void (*)(EVP_PKEY_CTX*)> ctx{
54 EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL), EVP_PKEY_CTX_free};
55 std::string error;
56 if (!ctx) {
57 ERR_print_errors_cb(SslRecordErrCallback, &error);
58 return Error() << "EVP_PKEY_CTX_new_id failed: " << error;
59 }
60 if (EVP_PKEY_keygen_init(ctx.get()) <= 0) {
61 ERR_print_errors_cb(SslRecordErrCallback, &error);
62 return Error() << "EVP_PKEY_keygen_init failed: " << error;
63 }
64 if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx.get(), bytes) <= 0) {
65 ERR_print_errors_cb(SslRecordErrCallback, &error);
66 return Error() << "EVP_PKEY_CTX_set_rsa_keygen_bits failed: " << error;
67 }
68
69 EVP_PKEY* pkey = nullptr;
70 if (EVP_PKEY_keygen(ctx.get(), &pkey) <= 0) {
71 ERR_print_errors_cb(SslRecordErrCallback, &error);
72 return Error() << "EVP_PKEY_keygen failed: " << error;
73 }
74 return std::unique_ptr<KeyPair>{new BoringSslKeyPair(pkey)};
75 }
76
PemPrivateKey() const77 Result<std::string> PemPrivateKey() const override {
78 std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
79 std::string error;
80 if (!bo) {
81 ERR_print_errors_cb(SslRecordErrCallback, &error);
82 return Error() << "BIO_new failed: " << error;
83 }
84 if (!PEM_write_bio_PrivateKey(bo.get(), pkey_.get(), NULL, NULL, 0, 0,
85 NULL)) {
86 ERR_print_errors_cb(SslRecordErrCallback, &error);
87 return Error() << "PEM_write_bio_PrivateKey failed: " << error;
88 }
89 std::string priv(BIO_pending(bo.get()), ' ');
90 auto written = BIO_read(bo.get(), priv.data(), priv.size());
91 if (written != priv.size()) {
92 return Error() << "Unexpected amount of data written: " << written
93 << " != " << priv.size();
94 }
95 return priv;
96 }
97
PemPublicKey() const98 Result<std::string> PemPublicKey() const override {
99 std::unique_ptr<BIO, int (*)(BIO*)> bo(BIO_new(BIO_s_mem()), BIO_free);
100 std::string error;
101 if (!bo) {
102 ERR_print_errors_cb(SslRecordErrCallback, &error);
103 return Error() << "BIO_new failed: " << error;
104 }
105 if (!PEM_write_bio_PUBKEY(bo.get(), pkey_.get())) {
106 ERR_print_errors_cb(SslRecordErrCallback, &error);
107 return Error() << "PEM_write_bio_PUBKEY failed: " << error;
108 }
109
110 std::string priv(BIO_pending(bo.get()), ' ');
111 auto written = BIO_read(bo.get(), priv.data(), priv.size());
112 if (written != priv.size()) {
113 return Error() << "Unexpected amount of data written: " << written
114 << " != " << priv.size();
115 }
116 return priv;
117 }
118
119 /*
120 * OpenSSH has its own distinct format for public keys, which cannot be
121 * produced directly with OpenSSL/BoringSSL primitives. Luckily it is possible
122 * to convert the BoringSSL-generated RSA key without touching the filesystem.
123 */
OpenSshPublicKey() const124 Result<std::string> OpenSshPublicKey() const override {
125 auto pem_pubkey = PemPublicKey();
126 if (!pem_pubkey.ok()) {
127 return Error() << "Failed to get pem public key: " << pem_pubkey.error();
128 }
129 auto fd = SharedFD::MemfdCreateWithData("", *pem_pubkey);
130 if (!fd->IsOpen()) {
131 return Error() << "Could not create pubkey memfd: " << fd->StrError();
132 }
133 Command cmd("/usr/bin/ssh-keygen");
134 cmd.AddParameter("-i");
135 cmd.AddParameter("-f");
136 cmd.AddParameter("/proc/self/fd/0");
137 cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, fd);
138 cmd.AddParameter("-m");
139 cmd.AddParameter("PKCS8");
140 std::string out;
141 std::string err;
142 auto ret = RunWithManagedStdio(std::move(cmd), nullptr, &out, &err);
143 if (ret != 0) {
144 return Error() << "Could not convert pem key to openssh key. "
145 << "stdout=\"" << out << "\", stderr=\"" << err << "\"";
146 }
147 return out;
148 }
149
150 private:
BoringSslKeyPair(EVP_PKEY * pkey)151 BoringSslKeyPair(EVP_PKEY* pkey) : pkey_(pkey, EVP_PKEY_free) {}
152
153 std::unique_ptr<EVP_PKEY, void (*)(EVP_PKEY*)> pkey_;
154 };
155
CreateRsa(size_t bytes)156 Result<std::unique_ptr<KeyPair>> KeyPair::CreateRsa(size_t bytes) {
157 return BoringSslKeyPair::CreateRsa(bytes);
158 }
159
160 } // namespace cuttlefish
161