1 // Copyright 2015 Joyent, Inc.
8 var assert = require('assert-plus');
9 var asn1 = require('asn1');
10 var crypto = require('crypto');
11 var algs = require('../algs');
12 var utils = require('../utils');
13 var Key = require('../key');
14 var PrivateKey = require('../private-key');
16 var pkcs1 = require('./pkcs1');
17 var pkcs8 = require('./pkcs8');
18 var sshpriv = require('./ssh-private');
19 var rfc4253 = require('./rfc4253');
21 var errors = require('../errors');
24 * For reading we support both PKCS#1 and PKCS#8. If we find a private key,
25 * we just take the public component of it and use that.
27 function read(buf, options, forceType) {
29 if (typeof (buf) !== 'string') {
30 assert.buffer(buf, 'buf');
31 buf = buf.toString('ascii');
34 var lines = buf.trim().split('\n');
36 var m = lines[0].match(/*JSSTYLED*/
37 /[-]+[ ]*BEGIN ([A-Z0-9]+ )?(PUBLIC|PRIVATE) KEY[ ]*[-]+/);
38 assert.ok(m, 'invalid PEM header');
40 var m2 = lines[lines.length - 1].match(/*JSSTYLED*/
41 /[-]+[ ]*END ([A-Z0-9]+ )?(PUBLIC|PRIVATE) KEY[ ]*[-]+/);
42 assert.ok(m2, 'invalid PEM footer');
44 /* Begin and end banners must match key type */
45 assert.equal(m[2], m2[2]);
46 var type = m[2].toLowerCase();
50 /* They also must match algorithms, if given */
51 assert.equal(m[1], m2[1], 'PEM header and footer mismatch');
57 lines = lines.slice(1);
58 m = lines[0].match(/*JSSTYLED*/
59 /^([A-Za-z0-9-]+): (.+)$/);
62 headers[m[1].toLowerCase()] = m[2];
66 if (headers['proc-type']) {
67 var parts = headers['proc-type'].split(',');
68 if (parts[0] === '4' && parts[1] === 'ENCRYPTED') {
69 if (typeof (options.passphrase) === 'string') {
70 options.passphrase = new Buffer(
71 options.passphrase, 'utf-8');
73 if (!Buffer.isBuffer(options.passphrase)) {
74 throw (new errors.KeyEncryptedError(
75 options.filename, 'PEM'));
77 parts = headers['dek-info'].split(',');
78 assert.ok(parts.length === 2);
79 cipher = parts[0].toLowerCase();
80 iv = new Buffer(parts[1], 'hex');
81 key = utils.opensslKeyDeriv(cipher, iv,
82 options.passphrase, 1).key;
87 /* Chop off the first and last lines */
88 lines = lines.slice(0, -1).join('');
89 buf = new Buffer(lines, 'base64');
91 if (cipher && key && iv) {
92 var cipherStream = crypto.createDecipheriv(cipher, key, iv);
93 var chunk, chunks = [];
94 cipherStream.once('error', function (e) {
95 if (e.toString().indexOf('bad decrypt') !== -1) {
96 throw (new Error('Incorrect passphrase ' +
97 'supplied, could not decrypt key'));
101 cipherStream.write(buf);
103 while ((chunk = cipherStream.read()) !== null)
105 buf = Buffer.concat(chunks);
108 /* The new OpenSSH internal format abuses PEM headers */
109 if (alg && alg.toLowerCase() === 'openssh')
110 return (sshpriv.readSSHPrivate(type, buf));
111 if (alg && alg.toLowerCase() === 'ssh2')
112 return (rfc4253.readType(type, buf));
114 var der = new asn1.BerReader(buf);
115 der.originalInput = input;
118 * All of the PEM file types start with a sequence tag, so chop it
123 /* PKCS#1 type keys name an algorithm in the banner explicitly */
126 assert.strictEqual(forceType, 'pkcs1');
127 return (pkcs1.readPkcs1(alg, type, der));
130 assert.strictEqual(forceType, 'pkcs8');
131 return (pkcs8.readPkcs8(alg, type, der));
135 function write(key, options, type) {
138 var alg = {'ecdsa': 'EC', 'rsa': 'RSA', 'dsa': 'DSA'}[key.type];
141 var der = new asn1.BerWriter();
143 if (PrivateKey.isPrivateKey(key)) {
144 if (type && type === 'pkcs8') {
145 header = 'PRIVATE KEY';
146 pkcs8.writePkcs8(der, key);
149 assert.strictEqual(type, 'pkcs1');
150 header = alg + ' PRIVATE KEY';
151 pkcs1.writePkcs1(der, key);
154 } else if (Key.isKey(key)) {
155 if (type && type === 'pkcs1') {
156 header = alg + ' PUBLIC KEY';
157 pkcs1.writePkcs1(der, key);
160 assert.strictEqual(type, 'pkcs8');
161 header = 'PUBLIC KEY';
162 pkcs8.writePkcs8(der, key);
166 throw (new Error('key is not a Key or PrivateKey'));
169 var tmp = der.buffer.toString('base64');
170 var len = tmp.length + (tmp.length / 64) +
171 18 + 16 + header.length*2 + 10;
172 var buf = new Buffer(len);
174 o += buf.write('-----BEGIN ' + header + '-----\n', o);
175 for (var i = 0; i < tmp.length; ) {
177 if (limit > tmp.length)
179 o += buf.write(tmp.slice(i, limit), o);
183 o += buf.write('-----END ' + header + '-----\n', o);
185 return (buf.slice(0, o));