1 /**
2 * PKCS #8
3 * 
4 * Copyright:
5 * (C) 1999-2007 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.pubkey.pkcs8;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO):
15 
16 alias pkcs8 = botan.pubkey.pkcs8;
17 
18 public import botan.rng.rng;
19 public import botan.pubkey.pubkey;
20 import botan.pubkey.x509_key;
21 import std.datetime;
22 import botan.filters.pipe;
23 import botan.asn1.der_enc;
24 import botan.asn1.ber_dec;
25 import botan.asn1.alg_id;
26 import botan.asn1.oids;
27 import botan.pbkdf.pbkdf;
28 import botan.stream.stream_cipher;
29 
30 import botan.libstate.libstate;
31 import botan.codec.pem;
32 import botan.pubkey.pk_algs;
33 import botan.utils.types;
34 import botan.constructs.pbes2;
35 import std.range : empty;
36 import botan.algo_base.scan_token;
37 
38 /**
39 * PKCS #8 General Exception
40 */
41 final class PKCS8Exception : DecodingError
42 {
43     this(in string error)
44     {
45         super("PKCS #8: " ~ error);
46     }
47 }
48 
49 /**
50 * BER encode a private key
51 * Params:
52 *  key = the private key to encode
53 * Returns: BER encoded key
54 */
55 SecureArray!ubyte BER_encode(in PrivateKey key)
56 {
57     __gshared immutable size_t PKCS8_VERSION = 0;
58     auto vec = DEREncoder()
59             .startCons(ASN1Tag.SEQUENCE)
60             .encode(PKCS8_VERSION)
61             .encode(key.pkcs8AlgorithmIdentifier())
62             .encode(key.pkcs8PrivateKey(), ASN1Tag.OCTET_STRING)
63             .endCons()
64             .getContentsRef();
65     return vec;
66 }
67 
68 /**
69 * Get a string containing a PEM encoded private key.
70 * Params:
71 *  key = the key to encode
72 * Returns: encoded key
73 */
74 string PEM_encode(in PrivateKey key)
75 {
76     auto ret = BER_encode(key);
77     return PEM.encode(ret, "PRIVATE KEY");
78 }
79 
80 /**
81 * Encrypt a key using PKCS #8 encryption
82 * Params:
83 *  key = the key to encode
84 *  rng = the rng to use
85 *  pass = the password to use for encryption
86 *  dur = number of time to run the password derivation
87 *  pbe_algo = the name of the desired password-based encryption
88             algorithm; if empty ("") a reasonable (portable/secure)
89             default will be chosen.
90 * Returns: encrypted key in binary BER form
91 */
92 Vector!ubyte BER_encode(in PrivateKey key,
93                         RandomNumberGenerator rng,
94                         in string pass,
95                         Duration dur = 300.msecs,
96                         in string pbe_algo = "")
97 {
98 	const auto pbe_params = choosePbeParams(pbe_algo, key.algoName());
99 	
100 	const Pair!(AlgorithmIdentifier, Array!ubyte) pbe_info =
101 		pbes2Encrypt(*pkcs8.BER_encode(key), pass, dur, pbe_params.first, pbe_params.second, rng);
102 	
103 	return DEREncoder()
104 		    .startCons(ASN1Tag.SEQUENCE)
105 			.encode(pbe_info.first)
106 			.encode(*(pbe_info.second), ASN1Tag.OCTET_STRING)
107 			.endCons()
108 			.getContentsUnlocked();
109 }
110 
111 /**
112 * Get a string containing a PEM encoded private key, encrypting it with a
113 * password.
114 * Params:
115 *  key = the key to encode
116 *  rng = the rng to use
117 *  pass = the password to use for encryption
118 *  dur = number of milliseconds to run the password derivation
119 *  pbe_algo = the name of the desired password-based encryption
120             algorithm; if empty ("") a reasonable (portable/secure)
121             default will be chosen.
122 * Returns: encrypted key in PEM form
123 */
124 string PEM_encode(in PrivateKey key,
125                   RandomNumberGenerator rng,
126                   in string pass,
127                   Duration dur = 300.msecs,
128                   in string pbe_algo = "")
129 {
130     if (pass == "")
131         return PEM_encode(key);
132     auto contents = BER_encode(key, rng, pass, dur, pbe_algo);
133     //logTrace("PEM got contents");
134     return PEM.encode(contents, "ENCRYPTED PRIVATE KEY");
135 }
136 
137 /**
138 * Load a key from a data source.
139 * Params:
140 *  source = the data source providing the encoded key
141 *  rng = the rng to use
142 *  get_pass = a function that returns passphrases
143 * Returns: loaded private key object
144 */
145 PrivateKey loadKey(DataSource source,
146                    RandomNumberGenerator rng,
147                    string delegate() get_pass)
148 {
149      auto alg_id = AlgorithmIdentifier();
150     SecureVector!ubyte pkcs8_key = PKCS8_decode(source, get_pass, alg_id);
151     const string alg_name = OIDS.lookup(alg_id.oid);
152     if (alg_name == "" || alg_name == alg_id.oid.toString())
153         throw new PKCS8Exception("Unknown algorithm OID: " ~ alg_id.oid.toString());
154     //logTrace("loadKey alg id: ", OIDS.lookup(alg_id.oid));
155     return makePrivateKey(alg_id, pkcs8_key, rng);
156 }
157 
158 /** Load a key from a data source.
159 * Params:
160 *  source = the data source providing the encoded key
161 *  rng = the rng to use
162 *  pass = the passphrase to decrypt the key. Provide an empty
163 * string if the key is not encrypted
164 * Returns: loaded private key object
165 */
166 PrivateKey loadKey(DataSource source,
167                    RandomNumberGenerator rng,
168                    in string pass = "")
169 {
170 	return loadKey(source, rng, { return pass; });
171 }
172 
173 /**
174 * Load a key from a file.
175 * Params:
176 *  filename = the path to the file containing the encoded key
177 *  rng = the rng to use
178 *  get_pass = a function that returns passphrases
179 * Returns: loaded private key object
180 */
181 PrivateKey loadKey(in string filename,
182                    RandomNumberGenerator rng,
183                    string delegate() get_pass)
184 {
185     auto source = DataSourceStream(filename, true);
186     return loadKey(cast(DataSource)source, rng, get_pass);
187 }
188 
189 /** Load a key from a file.
190 * Params:
191 *  filename = the path to the file containing the encoded key
192 *  rng = the rng to use
193 *  pass = the passphrase to decrypt the key. Provide an empty
194 * string if the key is not encrypted
195 * Returns: loaded private key object
196 */
197 PrivateKey loadKey(in string filename,
198                    RandomNumberGenerator rng,
199                    in string pass = "")
200 {
201 	return loadKey(filename, rng, { return pass; });
202 }
203 
204 
205 /**
206 * Copy an existing encoded key object.
207 * Params:
208 *  key = the key to copy
209 *  rng = the rng to use
210 * Returns: new copy of the key
211 */
212 PrivateKey copyKey(in PrivateKey key,
213                    RandomNumberGenerator rng)
214 {
215     auto source = DataSourceMemory(PEM_encode(key));
216     return loadKey(cast(DataSource)source, rng);
217 }
218 
219 /*
220 * Get info from an EncryptedPrivateKeyInfo
221 */
222 SecureVector!ubyte PKCS8_extract(DataSource source,
223                                  AlgorithmIdentifier pbe_alg_id)
224 {
225     SecureVector!ubyte key_data;
226     
227     BERDecoder(source)
228             .startCons(ASN1Tag.SEQUENCE)
229             .decode(pbe_alg_id)
230             .decode(key_data, ASN1Tag.OCTET_STRING)
231             .verifyEnd();
232     
233     //logTrace("PKCS8 extract key data finished", pbe_alg_id.toString());
234     return key_data.move;
235 }
236 
237 /*
238 * PEM decode and/or decrypt a private key
239 */
240 SecureVector!ubyte PKCS8_decode(DataSource source, in string delegate() get_pass, ref AlgorithmIdentifier pk_alg_id)
241 {
242     auto pbe_alg_id = AlgorithmIdentifier();
243     SecureVector!ubyte key_data, key;
244     bool is_encrypted = true;
245     try {
246         if (maybeBER(source) && !PEM.matches(source))
247             key_data = PKCS8_extract(source, pbe_alg_id);
248         else
249         {
250             string label;
251 			import std.algorithm : endsWith;
252             key_data = PEM.decode(source, label);
253             if (label == "PRIVATE KEY") {
254                 //logTrace("Detected private key");
255                 is_encrypted = false;
256             }
257             else if (label == "ENCRYPTED PRIVATE KEY")
258             {
259                 auto key_source = DataSourceMemory(key_data);
260                 key_data = PKCS8_extract(cast(DataSource)key_source, pbe_alg_id);
261             }
262 			else if (label == "RSA PRIVATE KEY") {
263 				throw new PKCS8Exception("Unsupported format: PKCS#1 RSA Private Key file");
264 			}
265             else
266                 throw new PKCS8Exception("Unsupported PKCS#/Private Key format, you must convert your certificate to PKCS#8");
267         }
268         
269         if (key_data.empty)
270             throw new PKCS8Exception("No key data found");
271     }
272     catch(DecodingError e)
273     {
274         throw new DecodingError("PKCS #8 private key decoding failed: " ~ e.msg);
275     }
276     
277     if (!is_encrypted)
278         key = key_data.dup;
279     
280     __gshared immutable size_t MAX_TRIES = 3;
281     
282     size_t tries = 0;
283     while (true)
284     {
285         try {
286             if (MAX_TRIES && tries >= MAX_TRIES)
287                 break;
288             
289             if (is_encrypted)
290             {
291 
292 				if(OIDS.lookup(pbe_alg_id.oid) != "PBE-PKCS5v20")
293 				    throw new Exception("Unknown PBE type " ~ pbe_alg_id.oid.toString());
294                 
295 				key = pbes2Decrypt(key_data, get_pass(), pbe_alg_id.parameters);
296 			}
297 			
298 			BERDecoder(key)
299                     .startCons(ASN1Tag.SEQUENCE)
300                     .decodeAndCheck!size_t(0, "Unknown PKCS #8 version number")
301                     .decode(pk_alg_id)
302                     .decode(key, ASN1Tag.OCTET_STRING)
303                     .discardRemaining()
304                     .endCons();
305             break;
306         }
307         catch(DecodingError e)
308         {
309 			//logError("Decoding error: ", e.toString());
310             ++tries;
311         }
312     }
313     
314     if (key.empty)
315         throw new DecodingError("PKCS #8 private key decoding failed");
316     return key.move;
317 }
318 
319 private Pair!(string, string)
320 	choosePbeParams(const string pbe_algo, const string key_algo)
321 {
322 	if(pbe_algo == "")
323 	{
324 		// Defaults:
325 		if(key_algo == "Curve25519" || key_algo == "McEliece")
326 			return makePair("AES-256/GCM", "SHA-512");
327 		else // for everything else (RSA, DSA, ECDSA, GOST, ...)
328 			return makePair("AES-256/CBC", "SHA-256");
329 	}
330 	
331 	SCANToken request = SCANToken(pbe_algo);
332 	if(request.algoName() != "PBE-PKCS5v20" || request.argCount() != 2)
333 		throw new Exception("Unsupported PBE " ~ pbe_algo);
334 	return makePair(request.arg(1), request.arg(0));
335 }