• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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