Initial commit
[yaffs-website] / node_modules / sshpk / lib / formats / pem.js
1 // Copyright 2015 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 algs = require('../algs');
12 var utils = require('../utils');
13 var Key = require('../key');
14 var PrivateKey = require('../private-key');
15
16 var pkcs1 = require('./pkcs1');
17 var pkcs8 = require('./pkcs8');
18 var sshpriv = require('./ssh-private');
19 var rfc4253 = require('./rfc4253');
20
21 var errors = require('../errors');
22
23 /*
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.
26  */
27 function read(buf, options, forceType) {
28         var input = buf;
29         if (typeof (buf) !== 'string') {
30                 assert.buffer(buf, 'buf');
31                 buf = buf.toString('ascii');
32         }
33
34         var lines = buf.trim().split('\n');
35
36         var m = lines[0].match(/*JSSTYLED*/
37             /[-]+[ ]*BEGIN ([A-Z0-9]+ )?(PUBLIC|PRIVATE) KEY[ ]*[-]+/);
38         assert.ok(m, 'invalid PEM header');
39
40         var m2 = lines[lines.length - 1].match(/*JSSTYLED*/
41             /[-]+[ ]*END ([A-Z0-9]+ )?(PUBLIC|PRIVATE) KEY[ ]*[-]+/);
42         assert.ok(m2, 'invalid PEM footer');
43
44         /* Begin and end banners must match key type */
45         assert.equal(m[2], m2[2]);
46         var type = m[2].toLowerCase();
47
48         var alg;
49         if (m[1]) {
50                 /* They also must match algorithms, if given */
51                 assert.equal(m[1], m2[1], 'PEM header and footer mismatch');
52                 alg = m[1].trim();
53         }
54
55         var headers = {};
56         while (true) {
57                 lines = lines.slice(1);
58                 m = lines[0].match(/*JSSTYLED*/
59                     /^([A-Za-z0-9-]+): (.+)$/);
60                 if (!m)
61                         break;
62                 headers[m[1].toLowerCase()] = m[2];
63         }
64
65         var cipher, key, iv;
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');
72                         }
73                         if (!Buffer.isBuffer(options.passphrase)) {
74                                 throw (new errors.KeyEncryptedError(
75                                     options.filename, 'PEM'));
76                         } else {
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;
83                         }
84                 }
85         }
86
87         /* Chop off the first and last lines */
88         lines = lines.slice(0, -1).join('');
89         buf = new Buffer(lines, 'base64');
90
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'));
98                         }
99                         throw (e);
100                 });
101                 cipherStream.write(buf);
102                 cipherStream.end();
103                 while ((chunk = cipherStream.read()) !== null)
104                         chunks.push(chunk);
105                 buf = Buffer.concat(chunks);
106         }
107
108         /* The new OpenSSH internal format abuses PEM headers */
109         if (alg && alg.toLowerCase() === 'openssh')
110                 return (sshpriv.readSSHPrivate(type, buf, options));
111         if (alg && alg.toLowerCase() === 'ssh2')
112                 return (rfc4253.readType(type, buf, options));
113
114         var der = new asn1.BerReader(buf);
115         der.originalInput = input;
116
117         /*
118          * All of the PEM file types start with a sequence tag, so chop it
119          * off here
120          */
121         der.readSequence();
122
123         /* PKCS#1 type keys name an algorithm in the banner explicitly */
124         if (alg) {
125                 if (forceType)
126                         assert.strictEqual(forceType, 'pkcs1');
127                 return (pkcs1.readPkcs1(alg, type, der));
128         } else {
129                 if (forceType)
130                         assert.strictEqual(forceType, 'pkcs8');
131                 return (pkcs8.readPkcs8(alg, type, der));
132         }
133 }
134
135 function write(key, options, type) {
136         assert.object(key);
137
138         var alg = {'ecdsa': 'EC', 'rsa': 'RSA', 'dsa': 'DSA'}[key.type];
139         var header;
140
141         var der = new asn1.BerWriter();
142
143         if (PrivateKey.isPrivateKey(key)) {
144                 if (type && type === 'pkcs8') {
145                         header = 'PRIVATE KEY';
146                         pkcs8.writePkcs8(der, key);
147                 } else {
148                         if (type)
149                                 assert.strictEqual(type, 'pkcs1');
150                         header = alg + ' PRIVATE KEY';
151                         pkcs1.writePkcs1(der, key);
152                 }
153
154         } else if (Key.isKey(key)) {
155                 if (type && type === 'pkcs1') {
156                         header = alg + ' PUBLIC KEY';
157                         pkcs1.writePkcs1(der, key);
158                 } else {
159                         if (type)
160                                 assert.strictEqual(type, 'pkcs8');
161                         header = 'PUBLIC KEY';
162                         pkcs8.writePkcs8(der, key);
163                 }
164
165         } else {
166                 throw (new Error('key is not a Key or PrivateKey'));
167         }
168
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);
173         var o = 0;
174         o += buf.write('-----BEGIN ' + header + '-----\n', o);
175         for (var i = 0; i < tmp.length; ) {
176                 var limit = i + 64;
177                 if (limit > tmp.length)
178                         limit = tmp.length;
179                 o += buf.write(tmp.slice(i, limit), o);
180                 buf[o++] = 10;
181                 i = limit;
182         }
183         o += buf.write('-----END ' + header + '-----\n', o);
184
185         return (buf.slice(0, o));
186 }