trouble-in-terror-town/node_modules/sshpk/lib/signature.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

314 lines
7.8 KiB
JavaScript

// Copyright 2015 Joyent, Inc.
module.exports = Signature;
var assert = require('assert-plus');
var Buffer = require('safer-buffer').Buffer;
var algs = require('./algs');
var crypto = require('crypto');
var errs = require('./errors');
var utils = require('./utils');
var asn1 = require('asn1');
var SSHBuffer = require('./ssh-buffer');
var InvalidAlgorithmError = errs.InvalidAlgorithmError;
var SignatureParseError = errs.SignatureParseError;
function Signature(opts) {
assert.object(opts, 'options');
assert.arrayOfObject(opts.parts, 'options.parts');
assert.string(opts.type, 'options.type');
var partLookup = {};
for (var i = 0; i < opts.parts.length; ++i) {
var part = opts.parts[i];
partLookup[part.name] = part;
}
this.type = opts.type;
this.hashAlgorithm = opts.hashAlgo;
this.curve = opts.curve;
this.parts = opts.parts;
this.part = partLookup;
}
Signature.prototype.toBuffer = function (format) {
if (format === undefined)
format = 'asn1';
assert.string(format, 'format');
var buf;
var stype = 'ssh-' + this.type;
switch (this.type) {
case 'rsa':
switch (this.hashAlgorithm) {
case 'sha256':
stype = 'rsa-sha2-256';
break;
case 'sha512':
stype = 'rsa-sha2-512';
break;
case 'sha1':
case undefined:
break;
default:
throw (new Error('SSH signature ' +
'format does not support hash ' +
'algorithm ' + this.hashAlgorithm));
}
if (format === 'ssh') {
buf = new SSHBuffer({});
buf.writeString(stype);
buf.writePart(this.part.sig);
return (buf.toBuffer());
} else {
return (this.part.sig.data);
}
break;
case 'ed25519':
if (format === 'ssh') {
buf = new SSHBuffer({});
buf.writeString(stype);
buf.writePart(this.part.sig);
return (buf.toBuffer());
} else {
return (this.part.sig.data);
}
break;
case 'dsa':
case 'ecdsa':
var r, s;
if (format === 'asn1') {
var der = new asn1.BerWriter();
der.startSequence();
r = utils.mpNormalize(this.part.r.data);
s = utils.mpNormalize(this.part.s.data);
der.writeBuffer(r, asn1.Ber.Integer);
der.writeBuffer(s, asn1.Ber.Integer);
der.endSequence();
return (der.buffer);
} else if (format === 'ssh' && this.type === 'dsa') {
buf = new SSHBuffer({});
buf.writeString('ssh-dss');
r = this.part.r.data;
if (r.length > 20 && r[0] === 0x00)
r = r.slice(1);
s = this.part.s.data;
if (s.length > 20 && s[0] === 0x00)
s = s.slice(1);
if ((this.hashAlgorithm &&
this.hashAlgorithm !== 'sha1') ||
r.length + s.length !== 40) {
throw (new Error('OpenSSH only supports ' +
'DSA signatures with SHA1 hash'));
}
buf.writeBuffer(Buffer.concat([r, s]));
return (buf.toBuffer());
} else if (format === 'ssh' && this.type === 'ecdsa') {
var inner = new SSHBuffer({});
r = this.part.r.data;
inner.writeBuffer(r);
inner.writePart(this.part.s);
buf = new SSHBuffer({});
/* XXX: find a more proper way to do this? */
var curve;
if (r[0] === 0x00)
r = r.slice(1);
var sz = r.length * 8;
if (sz === 256)
curve = 'nistp256';
else if (sz === 384)
curve = 'nistp384';
else if (sz === 528)
curve = 'nistp521';
buf.writeString('ecdsa-sha2-' + curve);
buf.writeBuffer(inner.toBuffer());
return (buf.toBuffer());
}
throw (new Error('Invalid signature format'));
default:
throw (new Error('Invalid signature data'));
}
};
Signature.prototype.toString = function (format) {
assert.optionalString(format, 'format');
return (this.toBuffer(format).toString('base64'));
};
Signature.parse = function (data, type, format) {
if (typeof (data) === 'string')
data = Buffer.from(data, 'base64');
assert.buffer(data, 'data');
assert.string(format, 'format');
assert.string(type, 'type');
var opts = {};
opts.type = type.toLowerCase();
opts.parts = [];
try {
assert.ok(data.length > 0, 'signature must not be empty');
switch (opts.type) {
case 'rsa':
return (parseOneNum(data, type, format, opts));
case 'ed25519':
return (parseOneNum(data, type, format, opts));
case 'dsa':
case 'ecdsa':
if (format === 'asn1')
return (parseDSAasn1(data, type, format, opts));
else if (opts.type === 'dsa')
return (parseDSA(data, type, format, opts));
else
return (parseECDSA(data, type, format, opts));
default:
throw (new InvalidAlgorithmError(type));
}
} catch (e) {
if (e instanceof InvalidAlgorithmError)
throw (e);
throw (new SignatureParseError(type, format, e));
}
};
function parseOneNum(data, type, format, opts) {
if (format === 'ssh') {
try {
var buf = new SSHBuffer({buffer: data});
var head = buf.readString();
} catch (e) {
/* fall through */
}
if (buf !== undefined) {
var msg = 'SSH signature does not match expected ' +
'type (expected ' + type + ', got ' + head + ')';
switch (head) {
case 'ssh-rsa':
assert.strictEqual(type, 'rsa', msg);
opts.hashAlgo = 'sha1';
break;
case 'rsa-sha2-256':
assert.strictEqual(type, 'rsa', msg);
opts.hashAlgo = 'sha256';
break;
case 'rsa-sha2-512':
assert.strictEqual(type, 'rsa', msg);
opts.hashAlgo = 'sha512';
break;
case 'ssh-ed25519':
assert.strictEqual(type, 'ed25519', msg);
opts.hashAlgo = 'sha512';
break;
default:
throw (new Error('Unknown SSH signature ' +
'type: ' + head));
}
var sig = buf.readPart();
assert.ok(buf.atEnd(), 'extra trailing bytes');
sig.name = 'sig';
opts.parts.push(sig);
return (new Signature(opts));
}
}
opts.parts.push({name: 'sig', data: data});
return (new Signature(opts));
}
function parseDSAasn1(data, type, format, opts) {
var der = new asn1.BerReader(data);
der.readSequence();
var r = der.readString(asn1.Ber.Integer, true);
var s = der.readString(asn1.Ber.Integer, true);
opts.parts.push({name: 'r', data: utils.mpNormalize(r)});
opts.parts.push({name: 's', data: utils.mpNormalize(s)});
return (new Signature(opts));
}
function parseDSA(data, type, format, opts) {
if (data.length != 40) {
var buf = new SSHBuffer({buffer: data});
var d = buf.readBuffer();
if (d.toString('ascii') === 'ssh-dss')
d = buf.readBuffer();
assert.ok(buf.atEnd(), 'extra trailing bytes');
assert.strictEqual(d.length, 40, 'invalid inner length');
data = d;
}
opts.parts.push({name: 'r', data: data.slice(0, 20)});
opts.parts.push({name: 's', data: data.slice(20, 40)});
return (new Signature(opts));
}
function parseECDSA(data, type, format, opts) {
var buf = new SSHBuffer({buffer: data});
var r, s;
var inner = buf.readBuffer();
var stype = inner.toString('ascii');
if (stype.slice(0, 6) === 'ecdsa-') {
var parts = stype.split('-');
assert.strictEqual(parts[0], 'ecdsa');
assert.strictEqual(parts[1], 'sha2');
opts.curve = parts[2];
switch (opts.curve) {
case 'nistp256':
opts.hashAlgo = 'sha256';
break;
case 'nistp384':
opts.hashAlgo = 'sha384';
break;
case 'nistp521':
opts.hashAlgo = 'sha512';
break;
default:
throw (new Error('Unsupported ECDSA curve: ' +
opts.curve));
}
inner = buf.readBuffer();
assert.ok(buf.atEnd(), 'extra trailing bytes on outer');
buf = new SSHBuffer({buffer: inner});
r = buf.readPart();
} else {
r = {data: inner};
}
s = buf.readPart();
assert.ok(buf.atEnd(), 'extra trailing bytes');
r.name = 'r';
s.name = 's';
opts.parts.push(r);
opts.parts.push(s);
return (new Signature(opts));
}
Signature.isSignature = function (obj, ver) {
return (utils.isCompatible(obj, Signature, ver));
};
/*
* API versions for Signature:
* [1,0] -- initial ver
* [2,0] -- support for rsa in full ssh format, compat with sshpk-agent
* hashAlgorithm property
* [2,1] -- first tagged version
*/
Signature.prototype._sshpkApiVersion = [2, 1];
Signature._oldVersionDetect = function (obj) {
assert.func(obj.toBuffer);
if (obj.hasOwnProperty('hashAlgorithm'))
return ([2, 0]);
return ([1, 0]);
};