Initial commit
[yaffs-website] / node_modules / sshpk / lib / signature.js
1 // Copyright 2015 Joyent, Inc.
2
3 module.exports = Signature;
4
5 var assert = require('assert-plus');
6 var algs = require('./algs');
7 var crypto = require('crypto');
8 var errs = require('./errors');
9 var utils = require('./utils');
10 var asn1 = require('asn1');
11 var SSHBuffer = require('./ssh-buffer');
12
13 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
14 var SignatureParseError = errs.SignatureParseError;
15
16 function Signature(opts) {
17         assert.object(opts, 'options');
18         assert.arrayOfObject(opts.parts, 'options.parts');
19         assert.string(opts.type, 'options.type');
20
21         var partLookup = {};
22         for (var i = 0; i < opts.parts.length; ++i) {
23                 var part = opts.parts[i];
24                 partLookup[part.name] = part;
25         }
26
27         this.type = opts.type;
28         this.hashAlgorithm = opts.hashAlgo;
29         this.parts = opts.parts;
30         this.part = partLookup;
31 }
32
33 Signature.prototype.toBuffer = function (format) {
34         if (format === undefined)
35                 format = 'asn1';
36         assert.string(format, 'format');
37
38         var buf;
39
40         switch (this.type) {
41         case 'rsa':
42         case 'ed25519':
43                 if (format === 'ssh') {
44                         buf = new SSHBuffer({});
45                         buf.writeString('ssh-' + this.type);
46                         buf.writePart(this.part.sig);
47                         return (buf.toBuffer());
48                 } else {
49                         return (this.part.sig.data);
50                 }
51
52         case 'dsa':
53         case 'ecdsa':
54                 var r, s;
55                 if (format === 'asn1') {
56                         var der = new asn1.BerWriter();
57                         der.startSequence();
58                         r = utils.mpNormalize(this.part.r.data);
59                         s = utils.mpNormalize(this.part.s.data);
60                         der.writeBuffer(r, asn1.Ber.Integer);
61                         der.writeBuffer(s, asn1.Ber.Integer);
62                         der.endSequence();
63                         return (der.buffer);
64                 } else if (format === 'ssh' && this.type === 'dsa') {
65                         buf = new SSHBuffer({});
66                         buf.writeString('ssh-dss');
67                         r = this.part.r.data;
68                         if (r.length > 20 && r[0] === 0x00)
69                                 r = r.slice(1);
70                         s = this.part.s.data;
71                         if (s.length > 20 && s[0] === 0x00)
72                                 s = s.slice(1);
73                         if ((this.hashAlgorithm &&
74                             this.hashAlgorithm !== 'sha1') ||
75                             r.length + s.length !== 40) {
76                                 throw (new Error('OpenSSH only supports ' +
77                                     'DSA signatures with SHA1 hash'));
78                         }
79                         buf.writeBuffer(Buffer.concat([r, s]));
80                         return (buf.toBuffer());
81                 } else if (format === 'ssh' && this.type === 'ecdsa') {
82                         var inner = new SSHBuffer({});
83                         r = this.part.r.data;
84                         inner.writeBuffer(r);
85                         inner.writePart(this.part.s);
86
87                         buf = new SSHBuffer({});
88                         /* XXX: find a more proper way to do this? */
89                         var curve;
90                         if (r[0] === 0x00)
91                                 r = r.slice(1);
92                         var sz = r.length * 8;
93                         if (sz === 256)
94                                 curve = 'nistp256';
95                         else if (sz === 384)
96                                 curve = 'nistp384';
97                         else if (sz === 528)
98                                 curve = 'nistp521';
99                         buf.writeString('ecdsa-sha2-' + curve);
100                         buf.writeBuffer(inner.toBuffer());
101                         return (buf.toBuffer());
102                 }
103                 throw (new Error('Invalid signature format'));
104         default:
105                 throw (new Error('Invalid signature data'));
106         }
107 };
108
109 Signature.prototype.toString = function (format) {
110         assert.optionalString(format, 'format');
111         return (this.toBuffer(format).toString('base64'));
112 };
113
114 Signature.parse = function (data, type, format) {
115         if (typeof (data) === 'string')
116                 data = new Buffer(data, 'base64');
117         assert.buffer(data, 'data');
118         assert.string(format, 'format');
119         assert.string(type, 'type');
120
121         var opts = {};
122         opts.type = type.toLowerCase();
123         opts.parts = [];
124
125         try {
126                 assert.ok(data.length > 0, 'signature must not be empty');
127                 switch (opts.type) {
128                 case 'rsa':
129                         return (parseOneNum(data, type, format, opts,
130                             'ssh-rsa'));
131                 case 'ed25519':
132                         return (parseOneNum(data, type, format, opts,
133                             'ssh-ed25519'));
134
135                 case 'dsa':
136                 case 'ecdsa':
137                         if (format === 'asn1')
138                                 return (parseDSAasn1(data, type, format, opts));
139                         else if (opts.type === 'dsa')
140                                 return (parseDSA(data, type, format, opts));
141                         else
142                                 return (parseECDSA(data, type, format, opts));
143
144                 default:
145                         throw (new InvalidAlgorithmError(type));
146                 }
147
148         } catch (e) {
149                 if (e instanceof InvalidAlgorithmError)
150                         throw (e);
151                 throw (new SignatureParseError(type, format, e));
152         }
153 };
154
155 function parseOneNum(data, type, format, opts, headType) {
156         if (format === 'ssh') {
157                 try {
158                         var buf = new SSHBuffer({buffer: data});
159                         var head = buf.readString();
160                 } catch (e) {
161                         /* fall through */
162                 }
163                 if (head === headType) {
164                         var sig = buf.readPart();
165                         assert.ok(buf.atEnd(), 'extra trailing bytes');
166                         sig.name = 'sig';
167                         opts.parts.push(sig);
168                         return (new Signature(opts));
169                 }
170         }
171         opts.parts.push({name: 'sig', data: data});
172         return (new Signature(opts));
173 }
174
175 function parseDSAasn1(data, type, format, opts) {
176         var der = new asn1.BerReader(data);
177         der.readSequence();
178         var r = der.readString(asn1.Ber.Integer, true);
179         var s = der.readString(asn1.Ber.Integer, true);
180
181         opts.parts.push({name: 'r', data: utils.mpNormalize(r)});
182         opts.parts.push({name: 's', data: utils.mpNormalize(s)});
183
184         return (new Signature(opts));
185 }
186
187 function parseDSA(data, type, format, opts) {
188         if (data.length != 40) {
189                 var buf = new SSHBuffer({buffer: data});
190                 var d = buf.readBuffer();
191                 if (d.toString('ascii') === 'ssh-dss')
192                         d = buf.readBuffer();
193                 assert.ok(buf.atEnd(), 'extra trailing bytes');
194                 assert.strictEqual(d.length, 40, 'invalid inner length');
195                 data = d;
196         }
197         opts.parts.push({name: 'r', data: data.slice(0, 20)});
198         opts.parts.push({name: 's', data: data.slice(20, 40)});
199         return (new Signature(opts));
200 }
201
202 function parseECDSA(data, type, format, opts) {
203         var buf = new SSHBuffer({buffer: data});
204
205         var r, s;
206         var inner = buf.readBuffer();
207         if (inner.toString('ascii').match(/^ecdsa-/)) {
208                 inner = buf.readBuffer();
209                 assert.ok(buf.atEnd(), 'extra trailing bytes on outer');
210                 buf = new SSHBuffer({buffer: inner});
211                 r = buf.readPart();
212         } else {
213                 r = {data: inner};
214         }
215
216         s = buf.readPart();
217         assert.ok(buf.atEnd(), 'extra trailing bytes');
218
219         r.name = 'r';
220         s.name = 's';
221
222         opts.parts.push(r);
223         opts.parts.push(s);
224         return (new Signature(opts));
225 }
226
227 Signature.isSignature = function (obj, ver) {
228         return (utils.isCompatible(obj, Signature, ver));
229 };
230
231 /*
232  * API versions for Signature:
233  * [1,0] -- initial ver
234  * [2,0] -- support for rsa in full ssh format, compat with sshpk-agent
235  *          hashAlgorithm property
236  * [2,1] -- first tagged version
237  */
238 Signature.prototype._sshpkApiVersion = [2, 1];
239
240 Signature._oldVersionDetect = function (obj) {
241         assert.func(obj.toBuffer);
242         if (obj.hasOwnProperty('hashAlgorithm'))
243                 return ([2, 0]);
244         return ([1, 0]);
245 };