1 // Copyright 2015 Joyent, Inc.
3 module.exports = Signature;
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');
13 var InvalidAlgorithmError = errs.InvalidAlgorithmError;
14 var SignatureParseError = errs.SignatureParseError;
16 function Signature(opts) {
17 assert.object(opts, 'options');
18 assert.arrayOfObject(opts.parts, 'options.parts');
19 assert.string(opts.type, 'options.type');
22 for (var i = 0; i < opts.parts.length; ++i) {
23 var part = opts.parts[i];
24 partLookup[part.name] = part;
27 this.type = opts.type;
28 this.hashAlgorithm = opts.hashAlgo;
29 this.parts = opts.parts;
30 this.part = partLookup;
33 Signature.prototype.toBuffer = function (format) {
34 if (format === undefined)
36 assert.string(format, 'format');
43 if (format === 'ssh') {
44 buf = new SSHBuffer({});
45 buf.writeString('ssh-' + this.type);
46 buf.writePart(this.part.sig);
47 return (buf.toBuffer());
49 return (this.part.sig.data);
55 if (format === 'asn1') {
56 var der = new asn1.BerWriter();
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);
64 } else if (format === 'ssh' && this.type === 'dsa') {
65 buf = new SSHBuffer({});
66 buf.writeString('ssh-dss');
68 if (r.length > 20 && r[0] === 0x00)
71 if (s.length > 20 && s[0] === 0x00)
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'));
79 buf.writeBuffer(Buffer.concat([r, s]));
80 return (buf.toBuffer());
81 } else if (format === 'ssh' && this.type === 'ecdsa') {
82 var inner = new SSHBuffer({});
85 inner.writePart(this.part.s);
87 buf = new SSHBuffer({});
88 /* XXX: find a more proper way to do this? */
92 var sz = r.length * 8;
99 buf.writeString('ecdsa-sha2-' + curve);
100 buf.writeBuffer(inner.toBuffer());
101 return (buf.toBuffer());
103 throw (new Error('Invalid signature format'));
105 throw (new Error('Invalid signature data'));
109 Signature.prototype.toString = function (format) {
110 assert.optionalString(format, 'format');
111 return (this.toBuffer(format).toString('base64'));
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');
122 opts.type = type.toLowerCase();
126 assert.ok(data.length > 0, 'signature must not be empty');
129 return (parseOneNum(data, type, format, opts,
132 return (parseOneNum(data, type, format, opts,
137 if (format === 'asn1')
138 return (parseDSAasn1(data, type, format, opts));
139 else if (opts.type === 'dsa')
140 return (parseDSA(data, type, format, opts));
142 return (parseECDSA(data, type, format, opts));
145 throw (new InvalidAlgorithmError(type));
149 if (e instanceof InvalidAlgorithmError)
151 throw (new SignatureParseError(type, format, e));
155 function parseOneNum(data, type, format, opts, headType) {
156 if (format === 'ssh') {
158 var buf = new SSHBuffer({buffer: data});
159 var head = buf.readString();
163 if (head === headType) {
164 var sig = buf.readPart();
165 assert.ok(buf.atEnd(), 'extra trailing bytes');
167 opts.parts.push(sig);
168 return (new Signature(opts));
171 opts.parts.push({name: 'sig', data: data});
172 return (new Signature(opts));
175 function parseDSAasn1(data, type, format, opts) {
176 var der = new asn1.BerReader(data);
178 var r = der.readString(asn1.Ber.Integer, true);
179 var s = der.readString(asn1.Ber.Integer, true);
181 opts.parts.push({name: 'r', data: utils.mpNormalize(r)});
182 opts.parts.push({name: 's', data: utils.mpNormalize(s)});
184 return (new Signature(opts));
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');
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));
202 function parseECDSA(data, type, format, opts) {
203 var buf = new SSHBuffer({buffer: data});
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});
217 assert.ok(buf.atEnd(), 'extra trailing bytes');
224 return (new Signature(opts));
227 Signature.isSignature = function (obj, ver) {
228 return (utils.isCompatible(obj, Signature, ver));
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
238 Signature.prototype._sshpkApiVersion = [2, 1];
240 Signature._oldVersionDetect = function (obj) {
241 assert.func(obj.toBuffer);
242 if (obj.hasOwnProperty('hashAlgorithm'))