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 }