1 /*
2 * (C) 2018 Jack Lloyd
3 *
4 * Botan is released under the Simplified BSD License (see license.txt)
5 */
6 
7 #include "cli.h"
8 
9 #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_AEAD_MODES) && defined(BOTAN_HAS_EME_OAEP) && defined(BOTAN_HAS_SHA2_32) && defined(BOTAN_HAS_PEM_CODEC) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
10 
11 #include <botan/pubkey.h>
12 #include <botan/x509_key.h>
13 #include <botan/pkcs8.h>
14 #include <botan/der_enc.h>
15 #include <botan/ber_dec.h>
16 #include <botan/oids.h>
17 #include <botan/aead.h>
18 #include <botan/pem.h>
19 #include <botan/rng.h>
20 
21 namespace Botan_CLI {
22 
23 namespace {
24 
25 class PK_Encrypt final : public Command
26    {
27    public:
PK_Encrypt()28       PK_Encrypt() : Command("pk_encrypt --aead=AES-256/GCM pubkey datafile") {}
29 
group() const30       std::string group() const override
31          {
32          return "pubkey";
33          }
34 
description() const35       std::string description() const override
36          {
37          return "Encrypt a file using a RSA public key";
38          }
39 
go()40       void go() override
41          {
42          std::unique_ptr<Botan::Public_Key> key(Botan::X509::load_key(get_arg("pubkey")));
43          if(!key)
44             {
45             throw CLI_Error("Unable to load public key");
46             }
47 
48          if(key->algo_name() != "RSA")
49             {
50             throw CLI_Usage_Error("This function requires an RSA key");
51             }
52 
53          const std::string OAEP_HASH = "SHA-256";
54          const std::string aead_algo = get_arg("aead");
55 
56          std::unique_ptr<Botan::AEAD_Mode> aead =
57             Botan::AEAD_Mode::create(aead_algo, Botan::ENCRYPTION);
58 
59          if(!aead)
60             throw CLI_Usage_Error("The AEAD '" + aead_algo + "' is not available");
61 
62          const Botan::OID aead_oid = Botan::OID::from_string(aead_algo);
63          if(aead_oid.empty())
64             throw CLI_Usage_Error("No OID defined for AEAD '" + aead_algo + "'");
65 
66          Botan::secure_vector<uint8_t> data;
67          auto insert_fn = [&](const uint8_t b[], size_t l)
68             {
69             data.insert(data.end(), b, b + l);
70             };
71          this->read_file(get_arg("datafile"), insert_fn);
72 
73          const Botan::AlgorithmIdentifier hash_id(OAEP_HASH, Botan::AlgorithmIdentifier::USE_EMPTY_PARAM);
74          const Botan::AlgorithmIdentifier pk_alg_id("RSA/OAEP", hash_id.BER_encode());
75 
76          Botan::PK_Encryptor_EME enc(*key, rng(), "OAEP(" + OAEP_HASH + ")");
77 
78          const Botan::secure_vector<uint8_t> file_key = rng().random_vec(aead->key_spec().maximum_keylength());
79 
80          const std::vector<uint8_t> encrypted_key = enc.encrypt(file_key, rng());
81 
82          const Botan::secure_vector<uint8_t> nonce = rng().random_vec(aead->default_nonce_length());
83          aead->set_key(file_key);
84          aead->set_associated_data_vec(encrypted_key);
85          aead->start(nonce);
86 
87          aead->finish(data);
88 
89          std::vector<uint8_t> buf;
90          Botan::DER_Encoder der(buf);
91 
92          der.start_cons(Botan::SEQUENCE)
93             .encode(pk_alg_id)
94             .encode(encrypted_key, Botan::OCTET_STRING)
95             .encode(aead_oid)
96             .encode(nonce, Botan::OCTET_STRING)
97             .encode(data, Botan::OCTET_STRING)
98             .end_cons();
99 
100          output() << Botan::PEM_Code::encode(buf, "PUBKEY ENCRYPTED MESSAGE", 72);
101          }
102    };
103 
104 BOTAN_REGISTER_COMMAND("pk_encrypt", PK_Encrypt);
105 
106 class PK_Decrypt final : public Command
107    {
108    public:
PK_Decrypt()109       PK_Decrypt() : Command("pk_decrypt privkey datafile") {}
110 
group() const111       std::string group() const override
112          {
113          return "pubkey";
114          }
115 
description() const116       std::string description() const override
117          {
118          return "Decrypt a file using a RSA private key";
119          }
120 
go()121       void go() override
122          {
123          Botan::DataSource_Stream input_stream(get_arg("privkey"));
124          auto get_pass = [this]() { return get_passphrase("Password"); };
125          std::unique_ptr<Botan::Private_Key> key = Botan::PKCS8::load_key(input_stream, get_pass);
126 
127          if(!key)
128             {
129             throw CLI_Error("Unable to load public key");
130             }
131 
132          if(key->algo_name() != "RSA")
133             {
134             throw CLI_Usage_Error("This function requires an RSA key");
135             }
136 
137          Botan::secure_vector<uint8_t> data;
138          std::vector<uint8_t> encrypted_key;
139          std::vector<uint8_t> nonce;
140          Botan::AlgorithmIdentifier pk_alg_id;
141          Botan::OID aead_oid;
142 
143          try
144             {
145             Botan::DataSource_Stream input(get_arg("datafile"));
146 
147             Botan::BER_Decoder(Botan::PEM_Code::decode_check_label(input, "PUBKEY ENCRYPTED MESSAGE"))
148                .start_cons(Botan::SEQUENCE)
149                   .decode(pk_alg_id)
150                   .decode(encrypted_key, Botan::OCTET_STRING)
151                   .decode(aead_oid)
152                   .decode(nonce, Botan::OCTET_STRING)
153                   .decode(data, Botan::OCTET_STRING)
154                .end_cons();
155             }
156          catch(Botan::Decoding_Error&)
157             {
158             error_output() << "Parsing input file failed: invalid format?\n";
159             return set_return_code(1);
160             }
161 
162          const std::string aead_algo = Botan::OIDS::oid2str_or_empty(aead_oid);
163          if(aead_algo == "")
164             {
165             error_output() << "Ciphertext was encrypted with an unknown algorithm";
166             return set_return_code(1);
167             }
168 
169          if(pk_alg_id.get_oid() != Botan::OID::from_string("RSA/OAEP"))
170             {
171             error_output() << "Ciphertext was encrypted with something other than RSA/OAEP";
172             return set_return_code(1);
173             }
174 
175          Botan::AlgorithmIdentifier oaep_hash_id;
176          Botan::BER_Decoder(pk_alg_id.get_parameters()).decode(oaep_hash_id);
177 
178          const std::string oaep_hash = Botan::OIDS::oid2str_or_empty(oaep_hash_id.get_oid());
179 
180          if(oaep_hash.empty())
181             {
182             error_output() << "Unknown hash function used with OAEP, OID " << oaep_hash_id.get_oid().to_string() << "\n";
183             return set_return_code(1);
184             }
185 
186          if(oaep_hash_id.get_parameters().empty() == false)
187             {
188             error_output() << "Unknown OAEP parameters used\n";
189             return set_return_code(1);
190             }
191 
192          std::unique_ptr<Botan::AEAD_Mode> aead =
193             Botan::AEAD_Mode::create_or_throw(aead_algo, Botan::DECRYPTION);
194 
195          const size_t expected_keylen = aead->key_spec().maximum_keylength();
196 
197          Botan::PK_Decryptor_EME dec(*key, rng(), "OAEP(" + oaep_hash + ")");
198 
199          const Botan::secure_vector<uint8_t> file_key =
200             dec.decrypt_or_random(encrypted_key.data(),
201                                   encrypted_key.size(),
202                                   expected_keylen,
203                                   rng());
204 
205          aead->set_key(file_key);
206          aead->set_associated_data_vec(encrypted_key);
207          aead->start(nonce);
208 
209          try
210             {
211             aead->finish(data);
212 
213             output().write(reinterpret_cast<const char*>(data.data()), data.size());
214             }
215          catch(Botan::Integrity_Failure&)
216             {
217             error_output() << "Message authentication failure, possible ciphertext tampering\n";
218             return set_return_code(1);
219             }
220          }
221    };
222 
223 BOTAN_REGISTER_COMMAND("pk_decrypt", PK_Decrypt);
224 
225 }
226 
227 }
228 
229 #endif
230