pem.js (7463B)
1 // Copyright 2018 Joyent, Inc. 2 3 module.exports = { 4 read: read, 5 write: write 6 }; 7 8 var assert = require('assert-plus'); 9 var asn1 = require('asn1'); 10 var crypto = require('crypto'); 11 var Buffer = require('safer-buffer').Buffer; 12 var algs = require('../algs'); 13 var utils = require('../utils'); 14 var Key = require('../key'); 15 var PrivateKey = require('../private-key'); 16 17 var pkcs1 = require('./pkcs1'); 18 var pkcs8 = require('./pkcs8'); 19 var sshpriv = require('./ssh-private'); 20 var rfc4253 = require('./rfc4253'); 21 22 var errors = require('../errors'); 23 24 var OID_PBES2 = '1.2.840.113549.1.5.13'; 25 var OID_PBKDF2 = '1.2.840.113549.1.5.12'; 26 27 var OID_TO_CIPHER = { 28 '1.2.840.113549.3.7': '3des-cbc', 29 '2.16.840.1.101.3.4.1.2': 'aes128-cbc', 30 '2.16.840.1.101.3.4.1.42': 'aes256-cbc' 31 }; 32 var CIPHER_TO_OID = {}; 33 Object.keys(OID_TO_CIPHER).forEach(function (k) { 34 CIPHER_TO_OID[OID_TO_CIPHER[k]] = k; 35 }); 36 37 var OID_TO_HASH = { 38 '1.2.840.113549.2.7': 'sha1', 39 '1.2.840.113549.2.9': 'sha256', 40 '1.2.840.113549.2.11': 'sha512' 41 }; 42 var HASH_TO_OID = {}; 43 Object.keys(OID_TO_HASH).forEach(function (k) { 44 HASH_TO_OID[OID_TO_HASH[k]] = k; 45 }); 46 47 /* 48 * For reading we support both PKCS#1 and PKCS#8. If we find a private key, 49 * we just take the public component of it and use that. 50 */ 51 function read(buf, options, forceType) { 52 var input = buf; 53 if (typeof (buf) !== 'string') { 54 assert.buffer(buf, 'buf'); 55 buf = buf.toString('ascii'); 56 } 57 58 var lines = buf.trim().split(/[\r\n]+/g); 59 60 var m; 61 var si = -1; 62 while (!m && si < lines.length) { 63 m = lines[++si].match(/*JSSTYLED*/ 64 /[-]+[ ]*BEGIN ([A-Z0-9][A-Za-z0-9]+ )?(PUBLIC|PRIVATE) KEY[ ]*[-]+/); 65 } 66 assert.ok(m, 'invalid PEM header'); 67 68 var m2; 69 var ei = lines.length; 70 while (!m2 && ei > 0) { 71 m2 = lines[--ei].match(/*JSSTYLED*/ 72 /[-]+[ ]*END ([A-Z0-9][A-Za-z0-9]+ )?(PUBLIC|PRIVATE) KEY[ ]*[-]+/); 73 } 74 assert.ok(m2, 'invalid PEM footer'); 75 76 /* Begin and end banners must match key type */ 77 assert.equal(m[2], m2[2]); 78 var type = m[2].toLowerCase(); 79 80 var alg; 81 if (m[1]) { 82 /* They also must match algorithms, if given */ 83 assert.equal(m[1], m2[1], 'PEM header and footer mismatch'); 84 alg = m[1].trim(); 85 } 86 87 lines = lines.slice(si, ei + 1); 88 89 var headers = {}; 90 while (true) { 91 lines = lines.slice(1); 92 m = lines[0].match(/*JSSTYLED*/ 93 /^([A-Za-z0-9-]+): (.+)$/); 94 if (!m) 95 break; 96 headers[m[1].toLowerCase()] = m[2]; 97 } 98 99 /* Chop off the first and last lines */ 100 lines = lines.slice(0, -1).join(''); 101 buf = Buffer.from(lines, 'base64'); 102 103 var cipher, key, iv; 104 if (headers['proc-type']) { 105 var parts = headers['proc-type'].split(','); 106 if (parts[0] === '4' && parts[1] === 'ENCRYPTED') { 107 if (typeof (options.passphrase) === 'string') { 108 options.passphrase = Buffer.from( 109 options.passphrase, 'utf-8'); 110 } 111 if (!Buffer.isBuffer(options.passphrase)) { 112 throw (new errors.KeyEncryptedError( 113 options.filename, 'PEM')); 114 } else { 115 parts = headers['dek-info'].split(','); 116 assert.ok(parts.length === 2); 117 cipher = parts[0].toLowerCase(); 118 iv = Buffer.from(parts[1], 'hex'); 119 key = utils.opensslKeyDeriv(cipher, iv, 120 options.passphrase, 1).key; 121 } 122 } 123 } 124 125 if (alg && alg.toLowerCase() === 'encrypted') { 126 var eder = new asn1.BerReader(buf); 127 var pbesEnd; 128 eder.readSequence(); 129 130 eder.readSequence(); 131 pbesEnd = eder.offset + eder.length; 132 133 var method = eder.readOID(); 134 if (method !== OID_PBES2) { 135 throw (new Error('Unsupported PEM/PKCS8 encryption ' + 136 'scheme: ' + method)); 137 } 138 139 eder.readSequence(); /* PBES2-params */ 140 141 eder.readSequence(); /* keyDerivationFunc */ 142 var kdfEnd = eder.offset + eder.length; 143 var kdfOid = eder.readOID(); 144 if (kdfOid !== OID_PBKDF2) 145 throw (new Error('Unsupported PBES2 KDF: ' + kdfOid)); 146 eder.readSequence(); 147 var salt = eder.readString(asn1.Ber.OctetString, true); 148 var iterations = eder.readInt(); 149 var hashAlg = 'sha1'; 150 if (eder.offset < kdfEnd) { 151 eder.readSequence(); 152 var hashAlgOid = eder.readOID(); 153 hashAlg = OID_TO_HASH[hashAlgOid]; 154 if (hashAlg === undefined) { 155 throw (new Error('Unsupported PBKDF2 hash: ' + 156 hashAlgOid)); 157 } 158 } 159 eder._offset = kdfEnd; 160 161 eder.readSequence(); /* encryptionScheme */ 162 var cipherOid = eder.readOID(); 163 cipher = OID_TO_CIPHER[cipherOid]; 164 if (cipher === undefined) { 165 throw (new Error('Unsupported PBES2 cipher: ' + 166 cipherOid)); 167 } 168 iv = eder.readString(asn1.Ber.OctetString, true); 169 170 eder._offset = pbesEnd; 171 buf = eder.readString(asn1.Ber.OctetString, true); 172 173 if (typeof (options.passphrase) === 'string') { 174 options.passphrase = Buffer.from( 175 options.passphrase, 'utf-8'); 176 } 177 if (!Buffer.isBuffer(options.passphrase)) { 178 throw (new errors.KeyEncryptedError( 179 options.filename, 'PEM')); 180 } 181 182 var cinfo = utils.opensshCipherInfo(cipher); 183 184 cipher = cinfo.opensslName; 185 key = utils.pbkdf2(hashAlg, salt, iterations, cinfo.keySize, 186 options.passphrase); 187 alg = undefined; 188 } 189 190 if (cipher && key && iv) { 191 var cipherStream = crypto.createDecipheriv(cipher, key, iv); 192 var chunk, chunks = []; 193 cipherStream.once('error', function (e) { 194 if (e.toString().indexOf('bad decrypt') !== -1) { 195 throw (new Error('Incorrect passphrase ' + 196 'supplied, could not decrypt key')); 197 } 198 throw (e); 199 }); 200 cipherStream.write(buf); 201 cipherStream.end(); 202 while ((chunk = cipherStream.read()) !== null) 203 chunks.push(chunk); 204 buf = Buffer.concat(chunks); 205 } 206 207 /* The new OpenSSH internal format abuses PEM headers */ 208 if (alg && alg.toLowerCase() === 'openssh') 209 return (sshpriv.readSSHPrivate(type, buf, options)); 210 if (alg && alg.toLowerCase() === 'ssh2') 211 return (rfc4253.readType(type, buf, options)); 212 213 var der = new asn1.BerReader(buf); 214 der.originalInput = input; 215 216 /* 217 * All of the PEM file types start with a sequence tag, so chop it 218 * off here 219 */ 220 der.readSequence(); 221 222 /* PKCS#1 type keys name an algorithm in the banner explicitly */ 223 if (alg) { 224 if (forceType) 225 assert.strictEqual(forceType, 'pkcs1'); 226 return (pkcs1.readPkcs1(alg, type, der)); 227 } else { 228 if (forceType) 229 assert.strictEqual(forceType, 'pkcs8'); 230 return (pkcs8.readPkcs8(alg, type, der)); 231 } 232 } 233 234 function write(key, options, type) { 235 assert.object(key); 236 237 var alg = { 238 'ecdsa': 'EC', 239 'rsa': 'RSA', 240 'dsa': 'DSA', 241 'ed25519': 'EdDSA' 242 }[key.type]; 243 var header; 244 245 var der = new asn1.BerWriter(); 246 247 if (PrivateKey.isPrivateKey(key)) { 248 if (type && type === 'pkcs8') { 249 header = 'PRIVATE KEY'; 250 pkcs8.writePkcs8(der, key); 251 } else { 252 if (type) 253 assert.strictEqual(type, 'pkcs1'); 254 header = alg + ' PRIVATE KEY'; 255 pkcs1.writePkcs1(der, key); 256 } 257 258 } else if (Key.isKey(key)) { 259 if (type && type === 'pkcs1') { 260 header = alg + ' PUBLIC KEY'; 261 pkcs1.writePkcs1(der, key); 262 } else { 263 if (type) 264 assert.strictEqual(type, 'pkcs8'); 265 header = 'PUBLIC KEY'; 266 pkcs8.writePkcs8(der, key); 267 } 268 269 } else { 270 throw (new Error('key is not a Key or PrivateKey')); 271 } 272 273 var tmp = der.buffer.toString('base64'); 274 var len = tmp.length + (tmp.length / 64) + 275 18 + 16 + header.length*2 + 10; 276 var buf = Buffer.alloc(len); 277 var o = 0; 278 o += buf.write('-----BEGIN ' + header + '-----\n', o); 279 for (var i = 0; i < tmp.length; ) { 280 var limit = i + 64; 281 if (limit > tmp.length) 282 limit = tmp.length; 283 o += buf.write(tmp.slice(i, limit), o); 284 buf[o++] = 10; 285 i = limit; 286 } 287 o += buf.write('-----END ' + header + '-----\n', o); 288 289 return (buf.slice(0, o)); 290 }