[6a3a178] | 1 | /**
|
---|
| 2 | * Password-based encryption functions.
|
---|
| 3 | *
|
---|
| 4 | * @author Dave Longley
|
---|
| 5 | * @author Stefan Siegl <stesie@brokenpipe.de>
|
---|
| 6 | *
|
---|
| 7 | * Copyright (c) 2010-2013 Digital Bazaar, Inc.
|
---|
| 8 | * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
|
---|
| 9 | *
|
---|
| 10 | * An EncryptedPrivateKeyInfo:
|
---|
| 11 | *
|
---|
| 12 | * EncryptedPrivateKeyInfo ::= SEQUENCE {
|
---|
| 13 | * encryptionAlgorithm EncryptionAlgorithmIdentifier,
|
---|
| 14 | * encryptedData EncryptedData }
|
---|
| 15 | *
|
---|
| 16 | * EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
|
---|
| 17 | *
|
---|
| 18 | * EncryptedData ::= OCTET STRING
|
---|
| 19 | */
|
---|
| 20 | var forge = require('./forge');
|
---|
| 21 | require('./aes');
|
---|
| 22 | require('./asn1');
|
---|
| 23 | require('./des');
|
---|
| 24 | require('./md');
|
---|
| 25 | require('./oids');
|
---|
| 26 | require('./pbkdf2');
|
---|
| 27 | require('./pem');
|
---|
| 28 | require('./random');
|
---|
| 29 | require('./rc2');
|
---|
| 30 | require('./rsa');
|
---|
| 31 | require('./util');
|
---|
| 32 |
|
---|
| 33 | if(typeof BigInteger === 'undefined') {
|
---|
| 34 | var BigInteger = forge.jsbn.BigInteger;
|
---|
| 35 | }
|
---|
| 36 |
|
---|
| 37 | // shortcut for asn.1 API
|
---|
| 38 | var asn1 = forge.asn1;
|
---|
| 39 |
|
---|
| 40 | /* Password-based encryption implementation. */
|
---|
| 41 | var pki = forge.pki = forge.pki || {};
|
---|
| 42 | module.exports = pki.pbe = forge.pbe = forge.pbe || {};
|
---|
| 43 | var oids = pki.oids;
|
---|
| 44 |
|
---|
| 45 | // validator for an EncryptedPrivateKeyInfo structure
|
---|
| 46 | // Note: Currently only works w/algorithm params
|
---|
| 47 | var encryptedPrivateKeyValidator = {
|
---|
| 48 | name: 'EncryptedPrivateKeyInfo',
|
---|
| 49 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 50 | type: asn1.Type.SEQUENCE,
|
---|
| 51 | constructed: true,
|
---|
| 52 | value: [{
|
---|
| 53 | name: 'EncryptedPrivateKeyInfo.encryptionAlgorithm',
|
---|
| 54 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 55 | type: asn1.Type.SEQUENCE,
|
---|
| 56 | constructed: true,
|
---|
| 57 | value: [{
|
---|
| 58 | name: 'AlgorithmIdentifier.algorithm',
|
---|
| 59 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 60 | type: asn1.Type.OID,
|
---|
| 61 | constructed: false,
|
---|
| 62 | capture: 'encryptionOid'
|
---|
| 63 | }, {
|
---|
| 64 | name: 'AlgorithmIdentifier.parameters',
|
---|
| 65 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 66 | type: asn1.Type.SEQUENCE,
|
---|
| 67 | constructed: true,
|
---|
| 68 | captureAsn1: 'encryptionParams'
|
---|
| 69 | }]
|
---|
| 70 | }, {
|
---|
| 71 | // encryptedData
|
---|
| 72 | name: 'EncryptedPrivateKeyInfo.encryptedData',
|
---|
| 73 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 74 | type: asn1.Type.OCTETSTRING,
|
---|
| 75 | constructed: false,
|
---|
| 76 | capture: 'encryptedData'
|
---|
| 77 | }]
|
---|
| 78 | };
|
---|
| 79 |
|
---|
| 80 | // validator for a PBES2Algorithms structure
|
---|
| 81 | // Note: Currently only works w/PBKDF2 + AES encryption schemes
|
---|
| 82 | var PBES2AlgorithmsValidator = {
|
---|
| 83 | name: 'PBES2Algorithms',
|
---|
| 84 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 85 | type: asn1.Type.SEQUENCE,
|
---|
| 86 | constructed: true,
|
---|
| 87 | value: [{
|
---|
| 88 | name: 'PBES2Algorithms.keyDerivationFunc',
|
---|
| 89 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 90 | type: asn1.Type.SEQUENCE,
|
---|
| 91 | constructed: true,
|
---|
| 92 | value: [{
|
---|
| 93 | name: 'PBES2Algorithms.keyDerivationFunc.oid',
|
---|
| 94 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 95 | type: asn1.Type.OID,
|
---|
| 96 | constructed: false,
|
---|
| 97 | capture: 'kdfOid'
|
---|
| 98 | }, {
|
---|
| 99 | name: 'PBES2Algorithms.params',
|
---|
| 100 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 101 | type: asn1.Type.SEQUENCE,
|
---|
| 102 | constructed: true,
|
---|
| 103 | value: [{
|
---|
| 104 | name: 'PBES2Algorithms.params.salt',
|
---|
| 105 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 106 | type: asn1.Type.OCTETSTRING,
|
---|
| 107 | constructed: false,
|
---|
| 108 | capture: 'kdfSalt'
|
---|
| 109 | }, {
|
---|
| 110 | name: 'PBES2Algorithms.params.iterationCount',
|
---|
| 111 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 112 | type: asn1.Type.INTEGER,
|
---|
| 113 | constructed: false,
|
---|
| 114 | capture: 'kdfIterationCount'
|
---|
| 115 | }, {
|
---|
| 116 | name: 'PBES2Algorithms.params.keyLength',
|
---|
| 117 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 118 | type: asn1.Type.INTEGER,
|
---|
| 119 | constructed: false,
|
---|
| 120 | optional: true,
|
---|
| 121 | capture: 'keyLength'
|
---|
| 122 | }, {
|
---|
| 123 | // prf
|
---|
| 124 | name: 'PBES2Algorithms.params.prf',
|
---|
| 125 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 126 | type: asn1.Type.SEQUENCE,
|
---|
| 127 | constructed: true,
|
---|
| 128 | optional: true,
|
---|
| 129 | value: [{
|
---|
| 130 | name: 'PBES2Algorithms.params.prf.algorithm',
|
---|
| 131 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 132 | type: asn1.Type.OID,
|
---|
| 133 | constructed: false,
|
---|
| 134 | capture: 'prfOid'
|
---|
| 135 | }]
|
---|
| 136 | }]
|
---|
| 137 | }]
|
---|
| 138 | }, {
|
---|
| 139 | name: 'PBES2Algorithms.encryptionScheme',
|
---|
| 140 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 141 | type: asn1.Type.SEQUENCE,
|
---|
| 142 | constructed: true,
|
---|
| 143 | value: [{
|
---|
| 144 | name: 'PBES2Algorithms.encryptionScheme.oid',
|
---|
| 145 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 146 | type: asn1.Type.OID,
|
---|
| 147 | constructed: false,
|
---|
| 148 | capture: 'encOid'
|
---|
| 149 | }, {
|
---|
| 150 | name: 'PBES2Algorithms.encryptionScheme.iv',
|
---|
| 151 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 152 | type: asn1.Type.OCTETSTRING,
|
---|
| 153 | constructed: false,
|
---|
| 154 | capture: 'encIv'
|
---|
| 155 | }]
|
---|
| 156 | }]
|
---|
| 157 | };
|
---|
| 158 |
|
---|
| 159 | var pkcs12PbeParamsValidator = {
|
---|
| 160 | name: 'pkcs-12PbeParams',
|
---|
| 161 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 162 | type: asn1.Type.SEQUENCE,
|
---|
| 163 | constructed: true,
|
---|
| 164 | value: [{
|
---|
| 165 | name: 'pkcs-12PbeParams.salt',
|
---|
| 166 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 167 | type: asn1.Type.OCTETSTRING,
|
---|
| 168 | constructed: false,
|
---|
| 169 | capture: 'salt'
|
---|
| 170 | }, {
|
---|
| 171 | name: 'pkcs-12PbeParams.iterations',
|
---|
| 172 | tagClass: asn1.Class.UNIVERSAL,
|
---|
| 173 | type: asn1.Type.INTEGER,
|
---|
| 174 | constructed: false,
|
---|
| 175 | capture: 'iterations'
|
---|
| 176 | }]
|
---|
| 177 | };
|
---|
| 178 |
|
---|
| 179 | /**
|
---|
| 180 | * Encrypts a ASN.1 PrivateKeyInfo object, producing an EncryptedPrivateKeyInfo.
|
---|
| 181 | *
|
---|
| 182 | * PBES2Algorithms ALGORITHM-IDENTIFIER ::=
|
---|
| 183 | * { {PBES2-params IDENTIFIED BY id-PBES2}, ...}
|
---|
| 184 | *
|
---|
| 185 | * id-PBES2 OBJECT IDENTIFIER ::= {pkcs-5 13}
|
---|
| 186 | *
|
---|
| 187 | * PBES2-params ::= SEQUENCE {
|
---|
| 188 | * keyDerivationFunc AlgorithmIdentifier {{PBES2-KDFs}},
|
---|
| 189 | * encryptionScheme AlgorithmIdentifier {{PBES2-Encs}}
|
---|
| 190 | * }
|
---|
| 191 | *
|
---|
| 192 | * PBES2-KDFs ALGORITHM-IDENTIFIER ::=
|
---|
| 193 | * { {PBKDF2-params IDENTIFIED BY id-PBKDF2}, ... }
|
---|
| 194 | *
|
---|
| 195 | * PBES2-Encs ALGORITHM-IDENTIFIER ::= { ... }
|
---|
| 196 | *
|
---|
| 197 | * PBKDF2-params ::= SEQUENCE {
|
---|
| 198 | * salt CHOICE {
|
---|
| 199 | * specified OCTET STRING,
|
---|
| 200 | * otherSource AlgorithmIdentifier {{PBKDF2-SaltSources}}
|
---|
| 201 | * },
|
---|
| 202 | * iterationCount INTEGER (1..MAX),
|
---|
| 203 | * keyLength INTEGER (1..MAX) OPTIONAL,
|
---|
| 204 | * prf AlgorithmIdentifier {{PBKDF2-PRFs}} DEFAULT algid-hmacWithSHA1
|
---|
| 205 | * }
|
---|
| 206 | *
|
---|
| 207 | * @param obj the ASN.1 PrivateKeyInfo object.
|
---|
| 208 | * @param password the password to encrypt with.
|
---|
| 209 | * @param options:
|
---|
| 210 | * algorithm the encryption algorithm to use
|
---|
| 211 | * ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
|
---|
| 212 | * count the iteration count to use.
|
---|
| 213 | * saltSize the salt size to use.
|
---|
| 214 | * prfAlgorithm the PRF message digest algorithm to use
|
---|
| 215 | * ('sha1', 'sha224', 'sha256', 'sha384', 'sha512')
|
---|
| 216 | *
|
---|
| 217 | * @return the ASN.1 EncryptedPrivateKeyInfo.
|
---|
| 218 | */
|
---|
| 219 | pki.encryptPrivateKeyInfo = function(obj, password, options) {
|
---|
| 220 | // set default options
|
---|
| 221 | options = options || {};
|
---|
| 222 | options.saltSize = options.saltSize || 8;
|
---|
| 223 | options.count = options.count || 2048;
|
---|
| 224 | options.algorithm = options.algorithm || 'aes128';
|
---|
| 225 | options.prfAlgorithm = options.prfAlgorithm || 'sha1';
|
---|
| 226 |
|
---|
| 227 | // generate PBE params
|
---|
| 228 | var salt = forge.random.getBytesSync(options.saltSize);
|
---|
| 229 | var count = options.count;
|
---|
| 230 | var countBytes = asn1.integerToDer(count);
|
---|
| 231 | var dkLen;
|
---|
| 232 | var encryptionAlgorithm;
|
---|
| 233 | var encryptedData;
|
---|
| 234 | if(options.algorithm.indexOf('aes') === 0 || options.algorithm === 'des') {
|
---|
| 235 | // do PBES2
|
---|
| 236 | var ivLen, encOid, cipherFn;
|
---|
| 237 | switch(options.algorithm) {
|
---|
| 238 | case 'aes128':
|
---|
| 239 | dkLen = 16;
|
---|
| 240 | ivLen = 16;
|
---|
| 241 | encOid = oids['aes128-CBC'];
|
---|
| 242 | cipherFn = forge.aes.createEncryptionCipher;
|
---|
| 243 | break;
|
---|
| 244 | case 'aes192':
|
---|
| 245 | dkLen = 24;
|
---|
| 246 | ivLen = 16;
|
---|
| 247 | encOid = oids['aes192-CBC'];
|
---|
| 248 | cipherFn = forge.aes.createEncryptionCipher;
|
---|
| 249 | break;
|
---|
| 250 | case 'aes256':
|
---|
| 251 | dkLen = 32;
|
---|
| 252 | ivLen = 16;
|
---|
| 253 | encOid = oids['aes256-CBC'];
|
---|
| 254 | cipherFn = forge.aes.createEncryptionCipher;
|
---|
| 255 | break;
|
---|
| 256 | case 'des':
|
---|
| 257 | dkLen = 8;
|
---|
| 258 | ivLen = 8;
|
---|
| 259 | encOid = oids['desCBC'];
|
---|
| 260 | cipherFn = forge.des.createEncryptionCipher;
|
---|
| 261 | break;
|
---|
| 262 | default:
|
---|
| 263 | var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
|
---|
| 264 | error.algorithm = options.algorithm;
|
---|
| 265 | throw error;
|
---|
| 266 | }
|
---|
| 267 |
|
---|
| 268 | // get PRF message digest
|
---|
| 269 | var prfAlgorithm = 'hmacWith' + options.prfAlgorithm.toUpperCase();
|
---|
| 270 | var md = prfAlgorithmToMessageDigest(prfAlgorithm);
|
---|
| 271 |
|
---|
| 272 | // encrypt private key using pbe SHA-1 and AES/DES
|
---|
| 273 | var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen, md);
|
---|
| 274 | var iv = forge.random.getBytesSync(ivLen);
|
---|
| 275 | var cipher = cipherFn(dk);
|
---|
| 276 | cipher.start(iv);
|
---|
| 277 | cipher.update(asn1.toDer(obj));
|
---|
| 278 | cipher.finish();
|
---|
| 279 | encryptedData = cipher.output.getBytes();
|
---|
| 280 |
|
---|
| 281 | // get PBKDF2-params
|
---|
| 282 | var params = createPbkdf2Params(salt, countBytes, dkLen, prfAlgorithm);
|
---|
| 283 |
|
---|
| 284 | encryptionAlgorithm = asn1.create(
|
---|
| 285 | asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 286 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
| 287 | asn1.oidToDer(oids['pkcs5PBES2']).getBytes()),
|
---|
| 288 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 289 | // keyDerivationFunc
|
---|
| 290 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 291 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
| 292 | asn1.oidToDer(oids['pkcs5PBKDF2']).getBytes()),
|
---|
| 293 | // PBKDF2-params
|
---|
| 294 | params
|
---|
| 295 | ]),
|
---|
| 296 | // encryptionScheme
|
---|
| 297 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 298 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
| 299 | asn1.oidToDer(encOid).getBytes()),
|
---|
| 300 | // iv
|
---|
| 301 | asn1.create(
|
---|
| 302 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, iv)
|
---|
| 303 | ])
|
---|
| 304 | ])
|
---|
| 305 | ]);
|
---|
| 306 | } else if(options.algorithm === '3des') {
|
---|
| 307 | // Do PKCS12 PBE
|
---|
| 308 | dkLen = 24;
|
---|
| 309 |
|
---|
| 310 | var saltBytes = new forge.util.ByteBuffer(salt);
|
---|
| 311 | var dk = pki.pbe.generatePkcs12Key(password, saltBytes, 1, count, dkLen);
|
---|
| 312 | var iv = pki.pbe.generatePkcs12Key(password, saltBytes, 2, count, dkLen);
|
---|
| 313 | var cipher = forge.des.createEncryptionCipher(dk);
|
---|
| 314 | cipher.start(iv);
|
---|
| 315 | cipher.update(asn1.toDer(obj));
|
---|
| 316 | cipher.finish();
|
---|
| 317 | encryptedData = cipher.output.getBytes();
|
---|
| 318 |
|
---|
| 319 | encryptionAlgorithm = asn1.create(
|
---|
| 320 | asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 321 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
| 322 | asn1.oidToDer(oids['pbeWithSHAAnd3-KeyTripleDES-CBC']).getBytes()),
|
---|
| 323 | // pkcs-12PbeParams
|
---|
| 324 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 325 | // salt
|
---|
| 326 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
|
---|
| 327 | // iteration count
|
---|
| 328 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
---|
| 329 | countBytes.getBytes())
|
---|
| 330 | ])
|
---|
| 331 | ]);
|
---|
| 332 | } else {
|
---|
| 333 | var error = new Error('Cannot encrypt private key. Unknown encryption algorithm.');
|
---|
| 334 | error.algorithm = options.algorithm;
|
---|
| 335 | throw error;
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | // EncryptedPrivateKeyInfo
|
---|
| 339 | var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 340 | // encryptionAlgorithm
|
---|
| 341 | encryptionAlgorithm,
|
---|
| 342 | // encryptedData
|
---|
| 343 | asn1.create(
|
---|
| 344 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, encryptedData)
|
---|
| 345 | ]);
|
---|
| 346 | return rval;
|
---|
| 347 | };
|
---|
| 348 |
|
---|
| 349 | /**
|
---|
| 350 | * Decrypts a ASN.1 PrivateKeyInfo object.
|
---|
| 351 | *
|
---|
| 352 | * @param obj the ASN.1 EncryptedPrivateKeyInfo object.
|
---|
| 353 | * @param password the password to decrypt with.
|
---|
| 354 | *
|
---|
| 355 | * @return the ASN.1 PrivateKeyInfo on success, null on failure.
|
---|
| 356 | */
|
---|
| 357 | pki.decryptPrivateKeyInfo = function(obj, password) {
|
---|
| 358 | var rval = null;
|
---|
| 359 |
|
---|
| 360 | // get PBE params
|
---|
| 361 | var capture = {};
|
---|
| 362 | var errors = [];
|
---|
| 363 | if(!asn1.validate(obj, encryptedPrivateKeyValidator, capture, errors)) {
|
---|
| 364 | var error = new Error('Cannot read encrypted private key. ' +
|
---|
| 365 | 'ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
|
---|
| 366 | error.errors = errors;
|
---|
| 367 | throw error;
|
---|
| 368 | }
|
---|
| 369 |
|
---|
| 370 | // get cipher
|
---|
| 371 | var oid = asn1.derToOid(capture.encryptionOid);
|
---|
| 372 | var cipher = pki.pbe.getCipher(oid, capture.encryptionParams, password);
|
---|
| 373 |
|
---|
| 374 | // get encrypted data
|
---|
| 375 | var encrypted = forge.util.createBuffer(capture.encryptedData);
|
---|
| 376 |
|
---|
| 377 | cipher.update(encrypted);
|
---|
| 378 | if(cipher.finish()) {
|
---|
| 379 | rval = asn1.fromDer(cipher.output);
|
---|
| 380 | }
|
---|
| 381 |
|
---|
| 382 | return rval;
|
---|
| 383 | };
|
---|
| 384 |
|
---|
| 385 | /**
|
---|
| 386 | * Converts a EncryptedPrivateKeyInfo to PEM format.
|
---|
| 387 | *
|
---|
| 388 | * @param epki the EncryptedPrivateKeyInfo.
|
---|
| 389 | * @param maxline the maximum characters per line, defaults to 64.
|
---|
| 390 | *
|
---|
| 391 | * @return the PEM-formatted encrypted private key.
|
---|
| 392 | */
|
---|
| 393 | pki.encryptedPrivateKeyToPem = function(epki, maxline) {
|
---|
| 394 | // convert to DER, then PEM-encode
|
---|
| 395 | var msg = {
|
---|
| 396 | type: 'ENCRYPTED PRIVATE KEY',
|
---|
| 397 | body: asn1.toDer(epki).getBytes()
|
---|
| 398 | };
|
---|
| 399 | return forge.pem.encode(msg, {maxline: maxline});
|
---|
| 400 | };
|
---|
| 401 |
|
---|
| 402 | /**
|
---|
| 403 | * Converts a PEM-encoded EncryptedPrivateKeyInfo to ASN.1 format. Decryption
|
---|
| 404 | * is not performed.
|
---|
| 405 | *
|
---|
| 406 | * @param pem the EncryptedPrivateKeyInfo in PEM-format.
|
---|
| 407 | *
|
---|
| 408 | * @return the ASN.1 EncryptedPrivateKeyInfo.
|
---|
| 409 | */
|
---|
| 410 | pki.encryptedPrivateKeyFromPem = function(pem) {
|
---|
| 411 | var msg = forge.pem.decode(pem)[0];
|
---|
| 412 |
|
---|
| 413 | if(msg.type !== 'ENCRYPTED PRIVATE KEY') {
|
---|
| 414 | var error = new Error('Could not convert encrypted private key from PEM; ' +
|
---|
| 415 | 'PEM header type is "ENCRYPTED PRIVATE KEY".');
|
---|
| 416 | error.headerType = msg.type;
|
---|
| 417 | throw error;
|
---|
| 418 | }
|
---|
| 419 | if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
---|
| 420 | throw new Error('Could not convert encrypted private key from PEM; ' +
|
---|
| 421 | 'PEM is encrypted.');
|
---|
| 422 | }
|
---|
| 423 |
|
---|
| 424 | // convert DER to ASN.1 object
|
---|
| 425 | return asn1.fromDer(msg.body);
|
---|
| 426 | };
|
---|
| 427 |
|
---|
| 428 | /**
|
---|
| 429 | * Encrypts an RSA private key. By default, the key will be wrapped in
|
---|
| 430 | * a PrivateKeyInfo and encrypted to produce a PKCS#8 EncryptedPrivateKeyInfo.
|
---|
| 431 | * This is the standard, preferred way to encrypt a private key.
|
---|
| 432 | *
|
---|
| 433 | * To produce a non-standard PEM-encrypted private key that uses encapsulated
|
---|
| 434 | * headers to indicate the encryption algorithm (old-style non-PKCS#8 OpenSSL
|
---|
| 435 | * private key encryption), set the 'legacy' option to true. Note: Using this
|
---|
| 436 | * option will cause the iteration count to be forced to 1.
|
---|
| 437 | *
|
---|
| 438 | * Note: The 'des' algorithm is supported, but it is not considered to be
|
---|
| 439 | * secure because it only uses a single 56-bit key. If possible, it is highly
|
---|
| 440 | * recommended that a different algorithm be used.
|
---|
| 441 | *
|
---|
| 442 | * @param rsaKey the RSA key to encrypt.
|
---|
| 443 | * @param password the password to use.
|
---|
| 444 | * @param options:
|
---|
| 445 | * algorithm: the encryption algorithm to use
|
---|
| 446 | * ('aes128', 'aes192', 'aes256', '3des', 'des').
|
---|
| 447 | * count: the iteration count to use.
|
---|
| 448 | * saltSize: the salt size to use.
|
---|
| 449 | * legacy: output an old non-PKCS#8 PEM-encrypted+encapsulated
|
---|
| 450 | * headers (DEK-Info) private key.
|
---|
| 451 | *
|
---|
| 452 | * @return the PEM-encoded ASN.1 EncryptedPrivateKeyInfo.
|
---|
| 453 | */
|
---|
| 454 | pki.encryptRsaPrivateKey = function(rsaKey, password, options) {
|
---|
| 455 | // standard PKCS#8
|
---|
| 456 | options = options || {};
|
---|
| 457 | if(!options.legacy) {
|
---|
| 458 | // encrypt PrivateKeyInfo
|
---|
| 459 | var rval = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(rsaKey));
|
---|
| 460 | rval = pki.encryptPrivateKeyInfo(rval, password, options);
|
---|
| 461 | return pki.encryptedPrivateKeyToPem(rval);
|
---|
| 462 | }
|
---|
| 463 |
|
---|
| 464 | // legacy non-PKCS#8
|
---|
| 465 | var algorithm;
|
---|
| 466 | var iv;
|
---|
| 467 | var dkLen;
|
---|
| 468 | var cipherFn;
|
---|
| 469 | switch(options.algorithm) {
|
---|
| 470 | case 'aes128':
|
---|
| 471 | algorithm = 'AES-128-CBC';
|
---|
| 472 | dkLen = 16;
|
---|
| 473 | iv = forge.random.getBytesSync(16);
|
---|
| 474 | cipherFn = forge.aes.createEncryptionCipher;
|
---|
| 475 | break;
|
---|
| 476 | case 'aes192':
|
---|
| 477 | algorithm = 'AES-192-CBC';
|
---|
| 478 | dkLen = 24;
|
---|
| 479 | iv = forge.random.getBytesSync(16);
|
---|
| 480 | cipherFn = forge.aes.createEncryptionCipher;
|
---|
| 481 | break;
|
---|
| 482 | case 'aes256':
|
---|
| 483 | algorithm = 'AES-256-CBC';
|
---|
| 484 | dkLen = 32;
|
---|
| 485 | iv = forge.random.getBytesSync(16);
|
---|
| 486 | cipherFn = forge.aes.createEncryptionCipher;
|
---|
| 487 | break;
|
---|
| 488 | case '3des':
|
---|
| 489 | algorithm = 'DES-EDE3-CBC';
|
---|
| 490 | dkLen = 24;
|
---|
| 491 | iv = forge.random.getBytesSync(8);
|
---|
| 492 | cipherFn = forge.des.createEncryptionCipher;
|
---|
| 493 | break;
|
---|
| 494 | case 'des':
|
---|
| 495 | algorithm = 'DES-CBC';
|
---|
| 496 | dkLen = 8;
|
---|
| 497 | iv = forge.random.getBytesSync(8);
|
---|
| 498 | cipherFn = forge.des.createEncryptionCipher;
|
---|
| 499 | break;
|
---|
| 500 | default:
|
---|
| 501 | var error = new Error('Could not encrypt RSA private key; unsupported ' +
|
---|
| 502 | 'encryption algorithm "' + options.algorithm + '".');
|
---|
| 503 | error.algorithm = options.algorithm;
|
---|
| 504 | throw error;
|
---|
| 505 | }
|
---|
| 506 |
|
---|
| 507 | // encrypt private key using OpenSSL legacy key derivation
|
---|
| 508 | var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
|
---|
| 509 | var cipher = cipherFn(dk);
|
---|
| 510 | cipher.start(iv);
|
---|
| 511 | cipher.update(asn1.toDer(pki.privateKeyToAsn1(rsaKey)));
|
---|
| 512 | cipher.finish();
|
---|
| 513 |
|
---|
| 514 | var msg = {
|
---|
| 515 | type: 'RSA PRIVATE KEY',
|
---|
| 516 | procType: {
|
---|
| 517 | version: '4',
|
---|
| 518 | type: 'ENCRYPTED'
|
---|
| 519 | },
|
---|
| 520 | dekInfo: {
|
---|
| 521 | algorithm: algorithm,
|
---|
| 522 | parameters: forge.util.bytesToHex(iv).toUpperCase()
|
---|
| 523 | },
|
---|
| 524 | body: cipher.output.getBytes()
|
---|
| 525 | };
|
---|
| 526 | return forge.pem.encode(msg);
|
---|
| 527 | };
|
---|
| 528 |
|
---|
| 529 | /**
|
---|
| 530 | * Decrypts an RSA private key.
|
---|
| 531 | *
|
---|
| 532 | * @param pem the PEM-formatted EncryptedPrivateKeyInfo to decrypt.
|
---|
| 533 | * @param password the password to use.
|
---|
| 534 | *
|
---|
| 535 | * @return the RSA key on success, null on failure.
|
---|
| 536 | */
|
---|
| 537 | pki.decryptRsaPrivateKey = function(pem, password) {
|
---|
| 538 | var rval = null;
|
---|
| 539 |
|
---|
| 540 | var msg = forge.pem.decode(pem)[0];
|
---|
| 541 |
|
---|
| 542 | if(msg.type !== 'ENCRYPTED PRIVATE KEY' &&
|
---|
| 543 | msg.type !== 'PRIVATE KEY' &&
|
---|
| 544 | msg.type !== 'RSA PRIVATE KEY') {
|
---|
| 545 | var error = new Error('Could not convert private key from PEM; PEM header type ' +
|
---|
| 546 | 'is not "ENCRYPTED PRIVATE KEY", "PRIVATE KEY", or "RSA PRIVATE KEY".');
|
---|
| 547 | error.headerType = error;
|
---|
| 548 | throw error;
|
---|
| 549 | }
|
---|
| 550 |
|
---|
| 551 | if(msg.procType && msg.procType.type === 'ENCRYPTED') {
|
---|
| 552 | var dkLen;
|
---|
| 553 | var cipherFn;
|
---|
| 554 | switch(msg.dekInfo.algorithm) {
|
---|
| 555 | case 'DES-CBC':
|
---|
| 556 | dkLen = 8;
|
---|
| 557 | cipherFn = forge.des.createDecryptionCipher;
|
---|
| 558 | break;
|
---|
| 559 | case 'DES-EDE3-CBC':
|
---|
| 560 | dkLen = 24;
|
---|
| 561 | cipherFn = forge.des.createDecryptionCipher;
|
---|
| 562 | break;
|
---|
| 563 | case 'AES-128-CBC':
|
---|
| 564 | dkLen = 16;
|
---|
| 565 | cipherFn = forge.aes.createDecryptionCipher;
|
---|
| 566 | break;
|
---|
| 567 | case 'AES-192-CBC':
|
---|
| 568 | dkLen = 24;
|
---|
| 569 | cipherFn = forge.aes.createDecryptionCipher;
|
---|
| 570 | break;
|
---|
| 571 | case 'AES-256-CBC':
|
---|
| 572 | dkLen = 32;
|
---|
| 573 | cipherFn = forge.aes.createDecryptionCipher;
|
---|
| 574 | break;
|
---|
| 575 | case 'RC2-40-CBC':
|
---|
| 576 | dkLen = 5;
|
---|
| 577 | cipherFn = function(key) {
|
---|
| 578 | return forge.rc2.createDecryptionCipher(key, 40);
|
---|
| 579 | };
|
---|
| 580 | break;
|
---|
| 581 | case 'RC2-64-CBC':
|
---|
| 582 | dkLen = 8;
|
---|
| 583 | cipherFn = function(key) {
|
---|
| 584 | return forge.rc2.createDecryptionCipher(key, 64);
|
---|
| 585 | };
|
---|
| 586 | break;
|
---|
| 587 | case 'RC2-128-CBC':
|
---|
| 588 | dkLen = 16;
|
---|
| 589 | cipherFn = function(key) {
|
---|
| 590 | return forge.rc2.createDecryptionCipher(key, 128);
|
---|
| 591 | };
|
---|
| 592 | break;
|
---|
| 593 | default:
|
---|
| 594 | var error = new Error('Could not decrypt private key; unsupported ' +
|
---|
| 595 | 'encryption algorithm "' + msg.dekInfo.algorithm + '".');
|
---|
| 596 | error.algorithm = msg.dekInfo.algorithm;
|
---|
| 597 | throw error;
|
---|
| 598 | }
|
---|
| 599 |
|
---|
| 600 | // use OpenSSL legacy key derivation
|
---|
| 601 | var iv = forge.util.hexToBytes(msg.dekInfo.parameters);
|
---|
| 602 | var dk = forge.pbe.opensslDeriveBytes(password, iv.substr(0, 8), dkLen);
|
---|
| 603 | var cipher = cipherFn(dk);
|
---|
| 604 | cipher.start(iv);
|
---|
| 605 | cipher.update(forge.util.createBuffer(msg.body));
|
---|
| 606 | if(cipher.finish()) {
|
---|
| 607 | rval = cipher.output.getBytes();
|
---|
| 608 | } else {
|
---|
| 609 | return rval;
|
---|
| 610 | }
|
---|
| 611 | } else {
|
---|
| 612 | rval = msg.body;
|
---|
| 613 | }
|
---|
| 614 |
|
---|
| 615 | if(msg.type === 'ENCRYPTED PRIVATE KEY') {
|
---|
| 616 | rval = pki.decryptPrivateKeyInfo(asn1.fromDer(rval), password);
|
---|
| 617 | } else {
|
---|
| 618 | // decryption already performed above
|
---|
| 619 | rval = asn1.fromDer(rval);
|
---|
| 620 | }
|
---|
| 621 |
|
---|
| 622 | if(rval !== null) {
|
---|
| 623 | rval = pki.privateKeyFromAsn1(rval);
|
---|
| 624 | }
|
---|
| 625 |
|
---|
| 626 | return rval;
|
---|
| 627 | };
|
---|
| 628 |
|
---|
| 629 | /**
|
---|
| 630 | * Derives a PKCS#12 key.
|
---|
| 631 | *
|
---|
| 632 | * @param password the password to derive the key material from, null or
|
---|
| 633 | * undefined for none.
|
---|
| 634 | * @param salt the salt, as a ByteBuffer, to use.
|
---|
| 635 | * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
|
---|
| 636 | * @param iter the iteration count.
|
---|
| 637 | * @param n the number of bytes to derive from the password.
|
---|
| 638 | * @param md the message digest to use, defaults to SHA-1.
|
---|
| 639 | *
|
---|
| 640 | * @return a ByteBuffer with the bytes derived from the password.
|
---|
| 641 | */
|
---|
| 642 | pki.pbe.generatePkcs12Key = function(password, salt, id, iter, n, md) {
|
---|
| 643 | var j, l;
|
---|
| 644 |
|
---|
| 645 | if(typeof md === 'undefined' || md === null) {
|
---|
| 646 | if(!('sha1' in forge.md)) {
|
---|
| 647 | throw new Error('"sha1" hash algorithm unavailable.');
|
---|
| 648 | }
|
---|
| 649 | md = forge.md.sha1.create();
|
---|
| 650 | }
|
---|
| 651 |
|
---|
| 652 | var u = md.digestLength;
|
---|
| 653 | var v = md.blockLength;
|
---|
| 654 | var result = new forge.util.ByteBuffer();
|
---|
| 655 |
|
---|
| 656 | /* Convert password to Unicode byte buffer + trailing 0-byte. */
|
---|
| 657 | var passBuf = new forge.util.ByteBuffer();
|
---|
| 658 | if(password !== null && password !== undefined) {
|
---|
| 659 | for(l = 0; l < password.length; l++) {
|
---|
| 660 | passBuf.putInt16(password.charCodeAt(l));
|
---|
| 661 | }
|
---|
| 662 | passBuf.putInt16(0);
|
---|
| 663 | }
|
---|
| 664 |
|
---|
| 665 | /* Length of salt and password in BYTES. */
|
---|
| 666 | var p = passBuf.length();
|
---|
| 667 | var s = salt.length();
|
---|
| 668 |
|
---|
| 669 | /* 1. Construct a string, D (the "diversifier"), by concatenating
|
---|
| 670 | v copies of ID. */
|
---|
| 671 | var D = new forge.util.ByteBuffer();
|
---|
| 672 | D.fillWithByte(id, v);
|
---|
| 673 |
|
---|
| 674 | /* 2. Concatenate copies of the salt together to create a string S of length
|
---|
| 675 | v * ceil(s / v) bytes (the final copy of the salt may be trunacted
|
---|
| 676 | to create S).
|
---|
| 677 | Note that if the salt is the empty string, then so is S. */
|
---|
| 678 | var Slen = v * Math.ceil(s / v);
|
---|
| 679 | var S = new forge.util.ByteBuffer();
|
---|
| 680 | for(l = 0; l < Slen; l++) {
|
---|
| 681 | S.putByte(salt.at(l % s));
|
---|
| 682 | }
|
---|
| 683 |
|
---|
| 684 | /* 3. Concatenate copies of the password together to create a string P of
|
---|
| 685 | length v * ceil(p / v) bytes (the final copy of the password may be
|
---|
| 686 | truncated to create P).
|
---|
| 687 | Note that if the password is the empty string, then so is P. */
|
---|
| 688 | var Plen = v * Math.ceil(p / v);
|
---|
| 689 | var P = new forge.util.ByteBuffer();
|
---|
| 690 | for(l = 0; l < Plen; l++) {
|
---|
| 691 | P.putByte(passBuf.at(l % p));
|
---|
| 692 | }
|
---|
| 693 |
|
---|
| 694 | /* 4. Set I=S||P to be the concatenation of S and P. */
|
---|
| 695 | var I = S;
|
---|
| 696 | I.putBuffer(P);
|
---|
| 697 |
|
---|
| 698 | /* 5. Set c=ceil(n / u). */
|
---|
| 699 | var c = Math.ceil(n / u);
|
---|
| 700 |
|
---|
| 701 | /* 6. For i=1, 2, ..., c, do the following: */
|
---|
| 702 | for(var i = 1; i <= c; i++) {
|
---|
| 703 | /* a) Set Ai=H^r(D||I). (l.e. the rth hash of D||I, H(H(H(...H(D||I)))) */
|
---|
| 704 | var buf = new forge.util.ByteBuffer();
|
---|
| 705 | buf.putBytes(D.bytes());
|
---|
| 706 | buf.putBytes(I.bytes());
|
---|
| 707 | for(var round = 0; round < iter; round++) {
|
---|
| 708 | md.start();
|
---|
| 709 | md.update(buf.getBytes());
|
---|
| 710 | buf = md.digest();
|
---|
| 711 | }
|
---|
| 712 |
|
---|
| 713 | /* b) Concatenate copies of Ai to create a string B of length v bytes (the
|
---|
| 714 | final copy of Ai may be truncated to create B). */
|
---|
| 715 | var B = new forge.util.ByteBuffer();
|
---|
| 716 | for(l = 0; l < v; l++) {
|
---|
| 717 | B.putByte(buf.at(l % u));
|
---|
| 718 | }
|
---|
| 719 |
|
---|
| 720 | /* c) Treating I as a concatenation I0, I1, ..., Ik-1 of v-byte blocks,
|
---|
| 721 | where k=ceil(s / v) + ceil(p / v), modify I by setting
|
---|
| 722 | Ij=(Ij+B+1) mod 2v for each j. */
|
---|
| 723 | var k = Math.ceil(s / v) + Math.ceil(p / v);
|
---|
| 724 | var Inew = new forge.util.ByteBuffer();
|
---|
| 725 | for(j = 0; j < k; j++) {
|
---|
| 726 | var chunk = new forge.util.ByteBuffer(I.getBytes(v));
|
---|
| 727 | var x = 0x1ff;
|
---|
| 728 | for(l = B.length() - 1; l >= 0; l--) {
|
---|
| 729 | x = x >> 8;
|
---|
| 730 | x += B.at(l) + chunk.at(l);
|
---|
| 731 | chunk.setAt(l, x & 0xff);
|
---|
| 732 | }
|
---|
| 733 | Inew.putBuffer(chunk);
|
---|
| 734 | }
|
---|
| 735 | I = Inew;
|
---|
| 736 |
|
---|
| 737 | /* Add Ai to A. */
|
---|
| 738 | result.putBuffer(buf);
|
---|
| 739 | }
|
---|
| 740 |
|
---|
| 741 | result.truncate(result.length() - n);
|
---|
| 742 | return result;
|
---|
| 743 | };
|
---|
| 744 |
|
---|
| 745 | /**
|
---|
| 746 | * Get new Forge cipher object instance.
|
---|
| 747 | *
|
---|
| 748 | * @param oid the OID (in string notation).
|
---|
| 749 | * @param params the ASN.1 params object.
|
---|
| 750 | * @param password the password to decrypt with.
|
---|
| 751 | *
|
---|
| 752 | * @return new cipher object instance.
|
---|
| 753 | */
|
---|
| 754 | pki.pbe.getCipher = function(oid, params, password) {
|
---|
| 755 | switch(oid) {
|
---|
| 756 | case pki.oids['pkcs5PBES2']:
|
---|
| 757 | return pki.pbe.getCipherForPBES2(oid, params, password);
|
---|
| 758 |
|
---|
| 759 | case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
|
---|
| 760 | case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
|
---|
| 761 | return pki.pbe.getCipherForPKCS12PBE(oid, params, password);
|
---|
| 762 |
|
---|
| 763 | default:
|
---|
| 764 | var error = new Error('Cannot read encrypted PBE data block. Unsupported OID.');
|
---|
| 765 | error.oid = oid;
|
---|
| 766 | error.supportedOids = [
|
---|
| 767 | 'pkcs5PBES2',
|
---|
| 768 | 'pbeWithSHAAnd3-KeyTripleDES-CBC',
|
---|
| 769 | 'pbewithSHAAnd40BitRC2-CBC'
|
---|
| 770 | ];
|
---|
| 771 | throw error;
|
---|
| 772 | }
|
---|
| 773 | };
|
---|
| 774 |
|
---|
| 775 | /**
|
---|
| 776 | * Get new Forge cipher object instance according to PBES2 params block.
|
---|
| 777 | *
|
---|
| 778 | * The returned cipher instance is already started using the IV
|
---|
| 779 | * from PBES2 parameter block.
|
---|
| 780 | *
|
---|
| 781 | * @param oid the PKCS#5 PBKDF2 OID (in string notation).
|
---|
| 782 | * @param params the ASN.1 PBES2-params object.
|
---|
| 783 | * @param password the password to decrypt with.
|
---|
| 784 | *
|
---|
| 785 | * @return new cipher object instance.
|
---|
| 786 | */
|
---|
| 787 | pki.pbe.getCipherForPBES2 = function(oid, params, password) {
|
---|
| 788 | // get PBE params
|
---|
| 789 | var capture = {};
|
---|
| 790 | var errors = [];
|
---|
| 791 | if(!asn1.validate(params, PBES2AlgorithmsValidator, capture, errors)) {
|
---|
| 792 | var error = new Error('Cannot read password-based-encryption algorithm ' +
|
---|
| 793 | 'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
|
---|
| 794 | error.errors = errors;
|
---|
| 795 | throw error;
|
---|
| 796 | }
|
---|
| 797 |
|
---|
| 798 | // check oids
|
---|
| 799 | oid = asn1.derToOid(capture.kdfOid);
|
---|
| 800 | if(oid !== pki.oids['pkcs5PBKDF2']) {
|
---|
| 801 | var error = new Error('Cannot read encrypted private key. ' +
|
---|
| 802 | 'Unsupported key derivation function OID.');
|
---|
| 803 | error.oid = oid;
|
---|
| 804 | error.supportedOids = ['pkcs5PBKDF2'];
|
---|
| 805 | throw error;
|
---|
| 806 | }
|
---|
| 807 | oid = asn1.derToOid(capture.encOid);
|
---|
| 808 | if(oid !== pki.oids['aes128-CBC'] &&
|
---|
| 809 | oid !== pki.oids['aes192-CBC'] &&
|
---|
| 810 | oid !== pki.oids['aes256-CBC'] &&
|
---|
| 811 | oid !== pki.oids['des-EDE3-CBC'] &&
|
---|
| 812 | oid !== pki.oids['desCBC']) {
|
---|
| 813 | var error = new Error('Cannot read encrypted private key. ' +
|
---|
| 814 | 'Unsupported encryption scheme OID.');
|
---|
| 815 | error.oid = oid;
|
---|
| 816 | error.supportedOids = [
|
---|
| 817 | 'aes128-CBC', 'aes192-CBC', 'aes256-CBC', 'des-EDE3-CBC', 'desCBC'];
|
---|
| 818 | throw error;
|
---|
| 819 | }
|
---|
| 820 |
|
---|
| 821 | // set PBE params
|
---|
| 822 | var salt = capture.kdfSalt;
|
---|
| 823 | var count = forge.util.createBuffer(capture.kdfIterationCount);
|
---|
| 824 | count = count.getInt(count.length() << 3);
|
---|
| 825 | var dkLen;
|
---|
| 826 | var cipherFn;
|
---|
| 827 | switch(pki.oids[oid]) {
|
---|
| 828 | case 'aes128-CBC':
|
---|
| 829 | dkLen = 16;
|
---|
| 830 | cipherFn = forge.aes.createDecryptionCipher;
|
---|
| 831 | break;
|
---|
| 832 | case 'aes192-CBC':
|
---|
| 833 | dkLen = 24;
|
---|
| 834 | cipherFn = forge.aes.createDecryptionCipher;
|
---|
| 835 | break;
|
---|
| 836 | case 'aes256-CBC':
|
---|
| 837 | dkLen = 32;
|
---|
| 838 | cipherFn = forge.aes.createDecryptionCipher;
|
---|
| 839 | break;
|
---|
| 840 | case 'des-EDE3-CBC':
|
---|
| 841 | dkLen = 24;
|
---|
| 842 | cipherFn = forge.des.createDecryptionCipher;
|
---|
| 843 | break;
|
---|
| 844 | case 'desCBC':
|
---|
| 845 | dkLen = 8;
|
---|
| 846 | cipherFn = forge.des.createDecryptionCipher;
|
---|
| 847 | break;
|
---|
| 848 | }
|
---|
| 849 |
|
---|
| 850 | // get PRF message digest
|
---|
| 851 | var md = prfOidToMessageDigest(capture.prfOid);
|
---|
| 852 |
|
---|
| 853 | // decrypt private key using pbe with chosen PRF and AES/DES
|
---|
| 854 | var dk = forge.pkcs5.pbkdf2(password, salt, count, dkLen, md);
|
---|
| 855 | var iv = capture.encIv;
|
---|
| 856 | var cipher = cipherFn(dk);
|
---|
| 857 | cipher.start(iv);
|
---|
| 858 |
|
---|
| 859 | return cipher;
|
---|
| 860 | };
|
---|
| 861 |
|
---|
| 862 | /**
|
---|
| 863 | * Get new Forge cipher object instance for PKCS#12 PBE.
|
---|
| 864 | *
|
---|
| 865 | * The returned cipher instance is already started using the key & IV
|
---|
| 866 | * derived from the provided password and PKCS#12 PBE salt.
|
---|
| 867 | *
|
---|
| 868 | * @param oid The PKCS#12 PBE OID (in string notation).
|
---|
| 869 | * @param params The ASN.1 PKCS#12 PBE-params object.
|
---|
| 870 | * @param password The password to decrypt with.
|
---|
| 871 | *
|
---|
| 872 | * @return the new cipher object instance.
|
---|
| 873 | */
|
---|
| 874 | pki.pbe.getCipherForPKCS12PBE = function(oid, params, password) {
|
---|
| 875 | // get PBE params
|
---|
| 876 | var capture = {};
|
---|
| 877 | var errors = [];
|
---|
| 878 | if(!asn1.validate(params, pkcs12PbeParamsValidator, capture, errors)) {
|
---|
| 879 | var error = new Error('Cannot read password-based-encryption algorithm ' +
|
---|
| 880 | 'parameters. ASN.1 object is not a supported EncryptedPrivateKeyInfo.');
|
---|
| 881 | error.errors = errors;
|
---|
| 882 | throw error;
|
---|
| 883 | }
|
---|
| 884 |
|
---|
| 885 | var salt = forge.util.createBuffer(capture.salt);
|
---|
| 886 | var count = forge.util.createBuffer(capture.iterations);
|
---|
| 887 | count = count.getInt(count.length() << 3);
|
---|
| 888 |
|
---|
| 889 | var dkLen, dIvLen, cipherFn;
|
---|
| 890 | switch(oid) {
|
---|
| 891 | case pki.oids['pbeWithSHAAnd3-KeyTripleDES-CBC']:
|
---|
| 892 | dkLen = 24;
|
---|
| 893 | dIvLen = 8;
|
---|
| 894 | cipherFn = forge.des.startDecrypting;
|
---|
| 895 | break;
|
---|
| 896 |
|
---|
| 897 | case pki.oids['pbewithSHAAnd40BitRC2-CBC']:
|
---|
| 898 | dkLen = 5;
|
---|
| 899 | dIvLen = 8;
|
---|
| 900 | cipherFn = function(key, iv) {
|
---|
| 901 | var cipher = forge.rc2.createDecryptionCipher(key, 40);
|
---|
| 902 | cipher.start(iv, null);
|
---|
| 903 | return cipher;
|
---|
| 904 | };
|
---|
| 905 | break;
|
---|
| 906 |
|
---|
| 907 | default:
|
---|
| 908 | var error = new Error('Cannot read PKCS #12 PBE data block. Unsupported OID.');
|
---|
| 909 | error.oid = oid;
|
---|
| 910 | throw error;
|
---|
| 911 | }
|
---|
| 912 |
|
---|
| 913 | // get PRF message digest
|
---|
| 914 | var md = prfOidToMessageDigest(capture.prfOid);
|
---|
| 915 | var key = pki.pbe.generatePkcs12Key(password, salt, 1, count, dkLen, md);
|
---|
| 916 | md.start();
|
---|
| 917 | var iv = pki.pbe.generatePkcs12Key(password, salt, 2, count, dIvLen, md);
|
---|
| 918 |
|
---|
| 919 | return cipherFn(key, iv);
|
---|
| 920 | };
|
---|
| 921 |
|
---|
| 922 | /**
|
---|
| 923 | * OpenSSL's legacy key derivation function.
|
---|
| 924 | *
|
---|
| 925 | * See: http://www.openssl.org/docs/crypto/EVP_BytesToKey.html
|
---|
| 926 | *
|
---|
| 927 | * @param password the password to derive the key from.
|
---|
| 928 | * @param salt the salt to use, null for none.
|
---|
| 929 | * @param dkLen the number of bytes needed for the derived key.
|
---|
| 930 | * @param [options] the options to use:
|
---|
| 931 | * [md] an optional message digest object to use.
|
---|
| 932 | */
|
---|
| 933 | pki.pbe.opensslDeriveBytes = function(password, salt, dkLen, md) {
|
---|
| 934 | if(typeof md === 'undefined' || md === null) {
|
---|
| 935 | if(!('md5' in forge.md)) {
|
---|
| 936 | throw new Error('"md5" hash algorithm unavailable.');
|
---|
| 937 | }
|
---|
| 938 | md = forge.md.md5.create();
|
---|
| 939 | }
|
---|
| 940 | if(salt === null) {
|
---|
| 941 | salt = '';
|
---|
| 942 | }
|
---|
| 943 | var digests = [hash(md, password + salt)];
|
---|
| 944 | for(var length = 16, i = 1; length < dkLen; ++i, length += 16) {
|
---|
| 945 | digests.push(hash(md, digests[i - 1] + password + salt));
|
---|
| 946 | }
|
---|
| 947 | return digests.join('').substr(0, dkLen);
|
---|
| 948 | };
|
---|
| 949 |
|
---|
| 950 | function hash(md, bytes) {
|
---|
| 951 | return md.start().update(bytes).digest().getBytes();
|
---|
| 952 | }
|
---|
| 953 |
|
---|
| 954 | function prfOidToMessageDigest(prfOid) {
|
---|
| 955 | // get PRF algorithm, default to SHA-1
|
---|
| 956 | var prfAlgorithm;
|
---|
| 957 | if(!prfOid) {
|
---|
| 958 | prfAlgorithm = 'hmacWithSHA1';
|
---|
| 959 | } else {
|
---|
| 960 | prfAlgorithm = pki.oids[asn1.derToOid(prfOid)];
|
---|
| 961 | if(!prfAlgorithm) {
|
---|
| 962 | var error = new Error('Unsupported PRF OID.');
|
---|
| 963 | error.oid = prfOid;
|
---|
| 964 | error.supported = [
|
---|
| 965 | 'hmacWithSHA1', 'hmacWithSHA224', 'hmacWithSHA256', 'hmacWithSHA384',
|
---|
| 966 | 'hmacWithSHA512'];
|
---|
| 967 | throw error;
|
---|
| 968 | }
|
---|
| 969 | }
|
---|
| 970 | return prfAlgorithmToMessageDigest(prfAlgorithm);
|
---|
| 971 | }
|
---|
| 972 |
|
---|
| 973 | function prfAlgorithmToMessageDigest(prfAlgorithm) {
|
---|
| 974 | var factory = forge.md;
|
---|
| 975 | switch(prfAlgorithm) {
|
---|
| 976 | case 'hmacWithSHA224':
|
---|
| 977 | factory = forge.md.sha512;
|
---|
| 978 | case 'hmacWithSHA1':
|
---|
| 979 | case 'hmacWithSHA256':
|
---|
| 980 | case 'hmacWithSHA384':
|
---|
| 981 | case 'hmacWithSHA512':
|
---|
| 982 | prfAlgorithm = prfAlgorithm.substr(8).toLowerCase();
|
---|
| 983 | break;
|
---|
| 984 | default:
|
---|
| 985 | var error = new Error('Unsupported PRF algorithm.');
|
---|
| 986 | error.algorithm = prfAlgorithm;
|
---|
| 987 | error.supported = [
|
---|
| 988 | 'hmacWithSHA1', 'hmacWithSHA224', 'hmacWithSHA256', 'hmacWithSHA384',
|
---|
| 989 | 'hmacWithSHA512'];
|
---|
| 990 | throw error;
|
---|
| 991 | }
|
---|
| 992 | if(!factory || !(prfAlgorithm in factory)) {
|
---|
| 993 | throw new Error('Unknown hash algorithm: ' + prfAlgorithm);
|
---|
| 994 | }
|
---|
| 995 | return factory[prfAlgorithm].create();
|
---|
| 996 | }
|
---|
| 997 |
|
---|
| 998 | function createPbkdf2Params(salt, countBytes, dkLen, prfAlgorithm) {
|
---|
| 999 | var params = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 1000 | // salt
|
---|
| 1001 | asn1.create(
|
---|
| 1002 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, salt),
|
---|
| 1003 | // iteration count
|
---|
| 1004 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
---|
| 1005 | countBytes.getBytes())
|
---|
| 1006 | ]);
|
---|
| 1007 | // when PRF algorithm is not SHA-1 default, add key length and PRF algorithm
|
---|
| 1008 | if(prfAlgorithm !== 'hmacWithSHA1') {
|
---|
| 1009 | params.value.push(
|
---|
| 1010 | // key length
|
---|
| 1011 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
|
---|
| 1012 | forge.util.hexToBytes(dkLen.toString(16))),
|
---|
| 1013 | // AlgorithmIdentifier
|
---|
| 1014 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
| 1015 | // algorithm
|
---|
| 1016 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
| 1017 | asn1.oidToDer(pki.oids[prfAlgorithm]).getBytes()),
|
---|
| 1018 | // parameters (null)
|
---|
| 1019 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
|
---|
| 1020 | ]));
|
---|
| 1021 | }
|
---|
| 1022 | return params;
|
---|
| 1023 | }
|
---|