twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

signature.js (7989B)


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