Skip to content

Commit

Permalink
TritonDataCenter/node-sshpk-agent#10 add support for rsa-sha2-256 sig…
Browse files Browse the repository at this point in the history
…natures

Reviewed by: Brittany Wald <brittany.wald@joyent.com>
Reviewed by: Cody Peter Mello <cody.mello@joyent.com>
  • Loading branch information
arekinath committed Apr 12, 2017
1 parent 941d67a commit 61aa616
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 13 deletions.
12 changes: 8 additions & 4 deletions lib/formats/openssh-cert.js
Expand Up @@ -206,10 +206,14 @@ function signAsync(cert, signer, done) {
done(err);
return;
}
if ((signature.type === 'rsa' || signature.type === 'dsa') &&
signature.hashAlgorithm !== 'sha1') {
done(new Error('RSA/DSA keys can only sign with ' +
'SHA-1 for OpenSSH certificates'));
try {
/*
* This will throw if the signature isn't of a
* type/algo that can be used for SSH.
*/
signature.toBuffer('ssh');
} catch (e) {
done(e);
return;
}
sig.signature = signature;
Expand Down
4 changes: 4 additions & 0 deletions lib/key.js
Expand Up @@ -171,6 +171,7 @@ Key.prototype.createVerify = function (hashAlgo) {
assert.ok(v, 'failed to create verifier');
var oldVerify = v.verify.bind(v);
var key = this.toBuffer('pkcs8');
var curve = this.curve;
var self = this;
v.verify = function (signature, fmt) {
if (Signature.isSignature(signature, [2, 0])) {
Expand All @@ -179,6 +180,9 @@ Key.prototype.createVerify = function (hashAlgo) {
if (signature.hashAlgorithm &&
signature.hashAlgorithm !== hashAlgo)
return (false);
if (signature.curve && self.type === 'ecdsa' &&
signature.curve !== curve)
return (false);
return (oldVerify(key, signature.toBuffer('asn1')));

} else if (typeof (signature) === 'string' ||
Expand Down
2 changes: 2 additions & 0 deletions lib/private-key.js
Expand Up @@ -166,12 +166,14 @@ PrivateKey.prototype.createSign = function (hashAlgo) {
var oldSign = v.sign.bind(v);
var key = this.toBuffer('pkcs1');
var type = this.type;
var curve = this.curve;
v.sign = function () {
var sig = oldSign(key);
if (typeof (sig) === 'string')
sig = new Buffer(sig, 'binary');
sig = Signature.parse(sig, type, 'asn1');
sig.hashAlgorithm = hashAlgo;
sig.curve = curve;
return (sig);
};
return (v);
Expand Down
84 changes: 76 additions & 8 deletions lib/signature.js
Expand Up @@ -26,6 +26,7 @@ function Signature(opts) {

this.type = opts.type;
this.hashAlgorithm = opts.hashAlgo;
this.curve = opts.curve;
this.parts = opts.parts;
this.part = partLookup;
}
Expand All @@ -36,18 +37,45 @@ Signature.prototype.toBuffer = function (format) {
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('ssh-' + this.type);
buf.writeString(stype);
buf.writePart(this.part.sig);
return (buf.toBuffer());
} else {
return (this.part.sig.data);
}
break;

case 'dsa':
case 'ecdsa':
Expand Down Expand Up @@ -126,11 +154,9 @@ Signature.parse = function (data, type, format) {
assert.ok(data.length > 0, 'signature must not be empty');
switch (opts.type) {
case 'rsa':
return (parseOneNum(data, type, format, opts,
'ssh-rsa'));
return (parseOneNum(data, type, format, opts));
case 'ed25519':
return (parseOneNum(data, type, format, opts,
'ssh-ed25519'));
return (parseOneNum(data, type, format, opts));

case 'dsa':
case 'ecdsa':
Expand All @@ -152,15 +178,38 @@ Signature.parse = function (data, type, format) {
}
};

function parseOneNum(data, type, format, opts, headType) {
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 (head === headType) {
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';
Expand Down Expand Up @@ -204,7 +253,26 @@ function parseECDSA(data, type, format, opts) {

var r, s;
var inner = buf.readBuffer();
if (inner.toString('ascii').match(/^ecdsa-/)) {
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});
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "sshpk",
"version": "1.12.0",
"version": "1.13.0",
"description": "A library for finding and using SSH public keys",
"main": "lib/index.js",
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions test/assets/openssh-rsa256.pub
@@ -0,0 +1 @@
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAQHOnkPfKVY+TTp/r4sUhp0QAAAAMBAAEAAACBANWaXdgNWqKYXJWAryuAQSK0D74arruteq8pXjRXkaVPs4ZAv6LTOtfyKQtHwikK9VyzfsXvwqiEX3AOPznieRxQJs2HQmKGivcjlpiWm8QWU7hLyeNXD0o6tj4v3/QAtEArpQ73qBPBE5bmj/yC3IPFQnEQXYOJbN6C6ZAlbVI3AAAAAAAAAAEAAAACAAAAEGhvc3RfdGVzdGluZy5yc2EAAAAPAAAAC3Rlc3RpbmcucnNhAAAAAFjkGCkAAAAAa7AbKQAAAAAAAAAAAAAAAAAAAJcAAAAHc3NoLXJzYQAAAAMBAAEAAACBANWaXdgNWqKYXJWAryuAQSK0D74arruteq8pXjRXkaVPs4ZAv6LTOtfyKQtHwikK9VyzfsXvwqiEX3AOPznieRxQJs2HQmKGivcjlpiWm8QWU7hLyeNXD0o6tj4v3/QAtEArpQ73qBPBE5bmj/yC3IPFQnEQXYOJbN6C6ZAlbVI3AAAAlAAAAAxyc2Etc2hhMi0yNTYAAACAjpKrSo5KSdFB0IAxzQWv7oW5d6BEgx4IaQ+FXh2Oii8fSeFPo9WreBdUJkPkREdqE3AAzJZjs0as8i5tbRQXEuCBXvlwS1km7JkyUAMwSMnSArPRccgwx0zpI6j2DLhwjA/ZI2YWdwoC4o6RYDP5q/o4eHtPjAjkrSCkuQkR3k8=
11 changes: 11 additions & 0 deletions test/certs.js
Expand Up @@ -331,3 +331,14 @@ test('example cert: lots of SAN (x509)', function (t) {
}));
t.end();
});

test('example cert: openssh rsa with sha256 (7.0p1+)', function (t) {
var cert = sshpk.parseCertificate(
fs.readFileSync(path.join(testDir, 'openssh-rsa256.pub')),
'openssh');
t.strictEqual(cert.subjectKey.type, 'rsa');
t.ok(cert.isSignedByKey(cert.subjectKey));
t.strictEqual(cert.signatures.openssh.signature.hashAlgorithm,
'sha256');
t.end();
});

0 comments on commit 61aa616

Please sign in to comment.