sshpk
Advanced tools
Comparing version 1.2.1 to 1.3.0
@@ -18,2 +18,14 @@ // Copyright 2015 Joyent, Inc. | ||
var algPrivInfo = { | ||
'dsa': { | ||
parts: ['p', 'q', 'g', 'y', 'x'] | ||
}, | ||
'rsa': { | ||
parts: ['n', 'e', 'd', 'iqmp', 'p', 'q'] | ||
}, | ||
'ecdsa': { | ||
parts: ['curve', 'Q', 'd'] | ||
} | ||
}; | ||
var hashAlgs = { | ||
@@ -91,2 +103,3 @@ 'md5': true, | ||
size: 521, | ||
pkcs8oid: '1.3.132.0.35', | ||
p: new Buffer(( | ||
@@ -128,4 +141,5 @@ '01ffffff ffffffff ffffffff ffffffff' + | ||
info: algInfo, | ||
privInfo: algPrivInfo, | ||
hashAlgs: hashAlgs, | ||
curves: curves | ||
}; |
// Copyright 2015 Joyent, Inc. | ||
module.exports = Fingerprint; | ||
var assert = require('assert-plus'); | ||
@@ -7,3 +9,3 @@ var algs = require('./algs'); | ||
var errs = require('./errors'); | ||
var Key; | ||
var Key = require('./key'); | ||
@@ -122,3 +124,1 @@ var FingerprintFormatError = errs.FingerprintFormatError; | ||
} | ||
module.exports = Fingerprint; |
// Copyright 2015 Joyent, Inc. | ||
module.exports = { | ||
read: read, | ||
write: write | ||
}; | ||
var assert = require('assert-plus'); | ||
@@ -7,4 +12,8 @@ var asn1 = require('asn1'); | ||
var utils = require('../utils'); | ||
var Key; | ||
var Key = require('../key'); | ||
var PrivateKey = require('../private-key'); | ||
var pkcs1 = require('./pkcs1'); | ||
var pkcs8 = require('./pkcs8'); | ||
/* | ||
@@ -14,3 +23,3 @@ * For reading we support both PKCS#1 and PKCS#8. If we find a private key, | ||
*/ | ||
function read(buf) { | ||
function read(buf, forceType) { | ||
var input = buf; | ||
@@ -24,13 +33,9 @@ if (typeof (buf) !== 'string') { | ||
/* Defer until runtime due to circular deps */ | ||
if (Key === undefined) | ||
Key = require('../key'); | ||
var m = lines[0].match(/*JSSTYLED*/ | ||
/BEGIN ([A-Z]+ )?(PUBLIC|PRIVATE) KEY/); | ||
assert.ok(m); | ||
assert.ok(m, 'invalid PEM header'); | ||
var m2 = lines[lines.length - 2].match(/*JSSTYLED*/ | ||
/END ([A-Z]+ )?(PUBLIC|PRIVATE) KEY/); | ||
assert.ok(m2); | ||
assert.ok(m2, 'invalid PEM footer'); | ||
@@ -44,3 +49,3 @@ /* Begin and end banners must match key type */ | ||
/* They also must match algorithms, if given */ | ||
assert.equal(m[1], m2[1]); | ||
assert.equal(m[1], m2[1], 'PEM header and footer mismatch'); | ||
alg = m[1].trim(); | ||
@@ -81,342 +86,53 @@ } | ||
/* PKCS#1 type keys name an algorithm in the banner explicitly */ | ||
if (alg) | ||
return (readPkcs1(alg, type, der)); | ||
else | ||
return (readPkcs8(alg, type, der)); | ||
} | ||
/* Helper to read in a single mpint */ | ||
function readMPInt(der, nm) { | ||
assert.strictEqual(der.peek(), asn1.Ber.Integer, | ||
nm + ' is not an Integer'); | ||
return (der.readString(asn1.Ber.Integer, true)); | ||
} | ||
function readPkcs1(alg, type, der) { | ||
switch (alg) { | ||
case 'RSA': | ||
if (type === 'public') | ||
return (readPkcs1RSAPublic(der)); | ||
else if (type === 'private') | ||
return (readPkcs1RSAPrivate(der)); | ||
throw (new Error('Unknown key type: ' + type)); | ||
case 'DSA': | ||
if (type === 'private') | ||
return (readPkcs1DSAPrivate(der)); | ||
throw (new Error('DSA public keys cannot be PKCS#1')); | ||
case 'EC': | ||
if (type === 'private') | ||
return (readPkcs1ECDSAPrivate(der)); | ||
throw (new Error('ECDSA public keys cannot be PKCS#1')); | ||
default: | ||
throw (new Error('Unknown key algo: ' + alg)); | ||
if (alg) { | ||
if (forceType) | ||
assert.strictEqual(forceType, 'pkcs1'); | ||
return (pkcs1.readPkcs1(alg, type, der)); | ||
} else { | ||
if (forceType) | ||
assert.strictEqual(forceType, 'pkcs8'); | ||
return (pkcs8.readPkcs8(alg, type, der)); | ||
} | ||
} | ||
function readPkcs1RSAPublic(der) { | ||
// modulus and exponent | ||
var n = readMPInt(der, 'modulus'); | ||
var e = readMPInt(der, 'exponent'); | ||
function write(key, type) { | ||
assert.object(key); | ||
// now, make the key | ||
var key = { | ||
type: 'rsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'e', data: e }, | ||
{ name: 'n', data: n } | ||
] | ||
}; | ||
var alg = {'ecdsa': 'EC', 'rsa': 'RSA', 'dsa': 'DSA'}[key.type]; | ||
var header; | ||
return (new Key(key)); | ||
} | ||
var der = new asn1.BerWriter(); | ||
function readPkcs1RSAPrivate(der) { | ||
var version = readMPInt(der, 'version'); | ||
assert.strictEqual(version.readUInt8(0), 0); | ||
// modulus then public exponent | ||
var n = readMPInt(der, 'modulus'); | ||
var e = readMPInt(der, 'public exponent'); | ||
// now, make the key | ||
var key = { | ||
type: 'rsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'e', data: e }, | ||
{ name: 'n', data: n } | ||
] | ||
}; | ||
return (new Key(key)); | ||
} | ||
function readPkcs1DSAPrivate(der) { | ||
var version = readMPInt(der, 'version'); | ||
assert.strictEqual(version.readUInt8(0), 0); | ||
var p = readMPInt(der, 'p'); | ||
var q = readMPInt(der, 'q'); | ||
var g = readMPInt(der, 'g'); | ||
var y = readMPInt(der, 'y'); | ||
// now, make the key | ||
var key = { | ||
type: 'dsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'p', data: p }, | ||
{ name: 'q', data: q }, | ||
{ name: 'g', data: g }, | ||
{ name: 'y', data: y } | ||
] | ||
}; | ||
return (new Key(key)); | ||
} | ||
function readPkcs1ECDSAPrivate(der) { | ||
var version = readMPInt(der, 'version'); | ||
assert.strictEqual(version.readUInt8(0), 1); | ||
// private key | ||
der.readString(asn1.Ber.OctetString, true); | ||
der.readSequence(0xa0); | ||
var curveOid = der.readOID(); | ||
var curve; | ||
switch (curveOid) { | ||
case '1.2.840.10045.3.1.7': | ||
curve = 'nistp256'; | ||
break; | ||
case '1.3.132.0.34': | ||
curve = 'nistp384'; | ||
break; | ||
case '1.3.132.0.35': | ||
curve = 'nistp521'; | ||
break; | ||
default: | ||
throw (new Error('Unknown ECDSA curve oid: ' + curveOid)); | ||
} | ||
der.readSequence(0xa1); | ||
var Q = der.readString(asn1.Ber.BitString, true); | ||
if (Q[0] === 0x0) | ||
Q = Q.slice(1); | ||
var key = { | ||
type: 'ecdsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'curve', data: new Buffer(curve) }, | ||
{ name: 'Q', data: Q } | ||
] | ||
}; | ||
return (new Key(key)); | ||
} | ||
function readPkcs8(alg, type, der) { | ||
if (type !== 'public') | ||
throw (new Error('PKCS#8 only supports public keys')); | ||
der.readSequence(); | ||
var oid = der.readOID(); | ||
switch (oid) { | ||
case '1.2.840.113549.1.1.1': | ||
return (readPkcs8RSA(der)); | ||
case '1.2.840.10040.4.1': | ||
return (readPkcs8DSA(der)); | ||
case '1.2.840.10045.2.1': | ||
return (readPkcs8ECDSA(der)); | ||
default: | ||
throw (new Error('Unknown key type OID ' + oid)); | ||
} | ||
} | ||
function readPkcs8RSA(der) { | ||
// Null -- XXX this probably isn't good practice | ||
der.readByte(); | ||
der.readByte(); | ||
// bit string sequence | ||
der.readSequence(0x03); | ||
der.readByte(); | ||
der.readSequence(); | ||
// modulus | ||
var n = readMPInt(der, 'modulus'); | ||
var e = readMPInt(der, 'exponent'); | ||
// now, make the key | ||
var key = { | ||
type: 'rsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'e', data: e }, | ||
{ name: 'n', data: n } | ||
] | ||
}; | ||
return (new Key(key)); | ||
} | ||
function readPkcs8DSA(der) { | ||
der.readSequence(); | ||
var p = readMPInt(der, 'p'); | ||
var q = readMPInt(der, 'q'); | ||
var g = readMPInt(der, 'g'); | ||
// bit string sequence | ||
der.readSequence(0x03); | ||
der.readByte(); | ||
var y = readMPInt(der, 'y'); | ||
// now, make the key | ||
var key = { | ||
type: 'dsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'p', data: p }, | ||
{ name: 'q', data: q }, | ||
{ name: 'g', data: g }, | ||
{ name: 'y', data: y } | ||
] | ||
}; | ||
return (new Key(key)); | ||
} | ||
/* Count leading zero bits on a buffer */ | ||
function countZeros(buf) { | ||
var o = 0, obit = 8; | ||
while (o < buf.length) { | ||
var mask = (1 << obit); | ||
if ((buf[o] & mask) === mask) | ||
break; | ||
obit--; | ||
if (obit < 0) { | ||
o++; | ||
obit = 8; | ||
if (key instanceof PrivateKey) { | ||
if (type && type === 'pkcs8') { | ||
header = 'PRIVATE KEY'; | ||
pkcs8.writePkcs8(der, key); | ||
} else { | ||
if (type) | ||
assert.strictEqual(type, 'pkcs1'); | ||
header = alg + ' PRIVATE KEY'; | ||
pkcs1.writePkcs1(der, key); | ||
} | ||
} | ||
return (o*8 + (8 - obit) - 1); | ||
} | ||
function readPkcs8ECDSA(der) { | ||
// ECParameters sequence | ||
der.readSequence(); | ||
var version = der.readString(asn1.Ber.Integer, true); | ||
assert.strictEqual(version[0], 1, 'ECDSA key not version 1'); | ||
var curve = {}; | ||
// FieldID sequence | ||
der.readSequence(); | ||
var fieldTypeOid = der.readOID(); | ||
assert.strictEqual(fieldTypeOid, '1.2.840.10045.1.1', | ||
'ECDSA key is not from a prime-field'); | ||
var p = curve.p = der.readString(asn1.Ber.Integer, true); | ||
/* | ||
* p always starts with a 1 bit, so count the zeros to get its | ||
* real size. | ||
*/ | ||
curve.size = p.length * 8 - countZeros(p); | ||
// Curve sequence | ||
der.readSequence(); | ||
curve.a = der.readString(asn1.Ber.OctetString, true); | ||
curve.b = der.readString(asn1.Ber.OctetString, true); | ||
if (der.peek() === asn1.Ber.BitString) | ||
curve.s = der.readString(asn1.Ber.BitString, true); | ||
// Combined Gx and Gy | ||
curve.G = der.readString(asn1.Ber.OctetString, true); | ||
assert.strictEqual(curve.G[0], 0x4, 'uncompressed G is required'); | ||
curve.n = der.readString(asn1.Ber.Integer, true); | ||
curve.h = der.readString(asn1.Ber.Integer, true); | ||
assert.strictEqual(curve.h[0], 0x1, 'a cofactor=1 curve is required'); | ||
var curveName; | ||
var curveNames = Object.keys(algs.curves); | ||
for (var j = 0; j < curveNames.length; ++j) { | ||
var c = curveNames[j]; | ||
var cd = algs.curves[c]; | ||
var ks = Object.keys(cd); | ||
var equal = true; | ||
for (var i = 0; i < ks.length; ++i) { | ||
var k = ks[i]; | ||
if (typeof (cd[k]) === 'object') { | ||
if (!cd[k].equals(curve[k])) { | ||
equal = false; | ||
break; | ||
} | ||
} else { | ||
if (cd[k] !== curve[k]) { | ||
equal = false; | ||
break; | ||
} | ||
} | ||
} else if (key instanceof Key) { | ||
if (type && type === 'pkcs1') { | ||
header = alg + ' PUBLIC KEY'; | ||
pkcs1.writePkcs1(der, key); | ||
} else { | ||
if (type) | ||
assert.strictEqual(type, 'pkcs8'); | ||
header = 'PUBLIC KEY'; | ||
pkcs8.writePkcs8(der, key); | ||
} | ||
if (equal) { | ||
curveName = c; | ||
break; | ||
} | ||
} | ||
assert.string(curveName, 'a known elliptic curve'); | ||
var Q = der.readString(asn1.Ber.BitString, true); | ||
if (Q[0] === 0x0) | ||
Q = Q.slice(1); | ||
var key = { | ||
type: 'ecdsa', | ||
source: der.originalInput, | ||
parts: [ | ||
{ name: 'curve', data: new Buffer(curveName) }, | ||
{ name: 'Q', data: Q } | ||
] | ||
}; | ||
return (new Key(key)); | ||
} | ||
/* For writing, we only support PKCS#8 */ | ||
function write(key) { | ||
assert.object(key); | ||
var der = new asn1.BerWriter(); | ||
der.startSequence(); | ||
der.startSequence(); | ||
switch (key.type) { | ||
case 'rsa': | ||
der.writeOID('1.2.840.113549.1.1.1'); | ||
der.writeNull(); | ||
der.endSequence(); | ||
writePkcs8RSA(key, der); | ||
break; | ||
case 'dsa': | ||
der.writeOID('1.2.840.10040.4.1'); | ||
writePkcs8DSA(key, der); | ||
break; | ||
case 'ecdsa': | ||
der.writeOID('1.2.840.10045.2.1'); | ||
writePkcs8ECDSA(key, der); | ||
break; | ||
default: | ||
throw (new Error('Unsupported key type: ' + key.type)); | ||
} else { | ||
throw (new Error('key is not a Key or PrivateKey')); | ||
} | ||
der.endSequence(); | ||
var tmp = der.buffer.toString('base64'); | ||
var len = tmp.length + (tmp.length / 64) + 28*2 + 10; | ||
var len = tmp.length + (tmp.length / 64) + | ||
18 + 16 + header.length*2 + 10; | ||
var buf = new Buffer(len); | ||
var o = 0; | ||
o += buf.write('-----BEGIN PUBLIC KEY-----\n', o); | ||
o += buf.write('-----BEGIN ' + header + '-----\n', o); | ||
for (var i = 0; i < tmp.length; ) { | ||
@@ -430,89 +146,5 @@ var limit = i + 64; | ||
} | ||
o += buf.write('-----END PUBLIC KEY-----\n', o); | ||
o += buf.write('-----END ' + header + '-----\n', o); | ||
return (buf.slice(0, o)); | ||
} | ||
function writePkcs8RSA(key, der) { | ||
der.startSequence(asn1.Ber.BitString); | ||
der.writeByte(0x00); | ||
der.startSequence(); | ||
der.writeBuffer(key.part.n.data, asn1.Ber.Integer); | ||
der.writeBuffer(key.part.e.data, asn1.Ber.Integer); | ||
der.endSequence(); | ||
der.endSequence(); | ||
} | ||
function writePkcs8DSA(key, der) { | ||
der.startSequence(); | ||
der.writeBuffer(key.part.p.data, asn1.Ber.Integer); | ||
der.writeBuffer(key.part.q.data, asn1.Ber.Integer); | ||
der.writeBuffer(key.part.g.data, asn1.Ber.Integer); | ||
der.endSequence(); | ||
der.endSequence(); | ||
der.startSequence(asn1.Ber.BitString); | ||
der.writeByte(0x00); | ||
der.writeBuffer(key.part.y.data, asn1.Ber.Integer); | ||
der.endSequence(); | ||
} | ||
function writePkcs8ECDSA(key, der) { | ||
var curve = algs.curves[key.curve]; | ||
if (curve.pkcs8oid) { | ||
/* This one has a name in pkcs#8, so just write the oid */ | ||
der.writeOID(curve.pkcs8oid); | ||
} else { | ||
// ECParameters sequence | ||
der.startSequence(); | ||
var version = new Buffer(1); | ||
version.writeUInt8(1, 0); | ||
der.writeBuffer(version, asn1.Ber.Integer); | ||
// FieldID sequence | ||
der.startSequence(); | ||
der.writeOID('1.2.840.10045.1.1'); // prime-field | ||
der.writeBuffer(curve.p, asn1.Ber.Integer); | ||
der.endSequence(); | ||
// Curve sequence | ||
der.startSequence(); | ||
var a = curve.p; | ||
if (a[0] === 0x0) | ||
a = a.slice(1); | ||
der.writeBuffer(a, asn1.Ber.OctetString); | ||
der.writeBuffer(curve.b, asn1.Ber.OctetString); | ||
der.writeBuffer(curve.s, asn1.Ber.BitString); | ||
der.endSequence(); | ||
der.writeBuffer(curve.G, asn1.Ber.OctetString); | ||
der.writeBuffer(curve.n, asn1.Ber.Integer); | ||
var h = curve.h; | ||
if (!h) { | ||
h = new Buffer(1); | ||
h[0] = 1; | ||
} | ||
der.writeBuffer(h, asn1.Ber.Integer); | ||
// ECParameters | ||
der.endSequence(); | ||
} | ||
der.endSequence(); | ||
var Q = key.part.Q.data; | ||
var pre = new Buffer(1); | ||
pre[0] = 0x0; | ||
if (Q[0] === 0x04) | ||
Q = Buffer.concat([pre, Q]); | ||
der.writeBuffer(Q, asn1.Ber.BitString); | ||
} | ||
module.exports = { | ||
read: read, | ||
write: write | ||
}; |
// Copyright 2015 Joyent, Inc. | ||
module.exports = { | ||
read: read.bind(undefined, false, undefined), | ||
write: write, | ||
/* semi-private api, used by sshpk-agent */ | ||
readPartial: read.bind(undefined, true), | ||
/* shared with ssh format */ | ||
keyTypeToAlg: keyTypeToAlg, | ||
algToKeyType: algToKeyType | ||
}; | ||
var assert = require('assert-plus'); | ||
var algInfos = require('../algs').info; | ||
var Key; | ||
var algs = require('../algs'); | ||
var Key = require('../key'); | ||
var PrivateKey = require('../private-key'); | ||
@@ -31,3 +43,11 @@ function algToKeyType(alg) { | ||
function read(buf) { | ||
function readLV(buf, offset, parts) { | ||
var len = buf.readUInt32BE(offset); | ||
offset += 4; | ||
parts.push({data: buf.slice(offset, offset + len)}); | ||
offset += len; | ||
return (offset); | ||
} | ||
function read(partial, type, buf) { | ||
if (typeof (buf) === 'string') | ||
@@ -37,20 +57,12 @@ buf = new Buffer(buf); | ||
/* Defer until runtime due to circular deps */ | ||
if (Key === undefined) | ||
Key = require('../key'); | ||
var key = {}; | ||
key._rfc4253Cache = buf; | ||
var parts = key.parts = []; | ||
var offset = 0; | ||
while (offset < buf.length) { | ||
var len = buf.readUInt32BE(offset); | ||
offset += 4; | ||
parts.push({data: buf.slice(offset, offset + len)}); | ||
offset += len; | ||
} | ||
offset = readLV(buf, offset, parts); | ||
assert.ok(parts.length > 1); | ||
assert.ok(parts[0].data.length > 0); | ||
assert.ok(parts.length >= 1, | ||
'key must have at least one part'); | ||
assert.ok(parts[0].data.length > 0, | ||
'first key part must not be empty'); | ||
@@ -63,4 +75,24 @@ var alg = parts[0].data.toString(); | ||
var algInfo = algInfos[key.type]; | ||
var partCount = algs.info[key.type].parts.length; | ||
if (type && type === 'private') | ||
partCount = algs.privInfo[key.type].parts.length; | ||
while (offset < buf.length && parts.length < partCount) | ||
offset = readLV(buf, offset, parts); | ||
while (!partial && offset < buf.length) | ||
offset = readLV(buf, offset, parts); | ||
assert.ok(parts.length >= 1, | ||
'key must have at least one part'); | ||
key._rfc4253Cache = buf.slice(0, offset); | ||
var Constructor = Key; | ||
var algInfo = algs.info[key.type]; | ||
if (type === 'private' || algInfo.parts.length !== parts.length) { | ||
algInfo = algs.privInfo[key.type]; | ||
Constructor = PrivateKey; | ||
} | ||
assert.strictEqual(algInfo.parts.length, parts.length); | ||
if (key.type === 'ecdsa') { | ||
@@ -72,7 +104,6 @@ var res = /^ecdsa-sha2-(.+)$/.exec(alg); | ||
assert.strictEqual(algInfo.parts.length, parts.length); | ||
for (var i = 0; i < algInfo.parts.length; ++i) | ||
parts[i].name = algInfo.parts[i]; | ||
return (new Key(key)); | ||
return (new Constructor(key)); | ||
} | ||
@@ -86,5 +117,11 @@ | ||
var algInfo = algs.info[key.type]; | ||
if (key instanceof PrivateKey) | ||
algInfo = algs.privInfo[key.type]; | ||
var parts = algInfo.parts; | ||
var size = 0; | ||
for (i = 0; i < key.parts.length; ++i) | ||
size += 4 + key.parts[i].data.length; | ||
for (i = 0; i < parts.length; ++i) | ||
/* +1 in case it's a bignum that needs a leading 0 */ | ||
size += 4 + 1 + key.part[parts[i]].data.length; | ||
size += alg.length + 4; | ||
@@ -99,6 +136,14 @@ | ||
for (i = 0; i < key.parts.length; ++i) { | ||
buf.writeUInt32BE(key.parts[i].data.length, o); | ||
o += 4; | ||
o += key.parts[i].data.copy(buf, o); | ||
for (i = 0; i < parts.length; ++i) { | ||
var data = key.part[parts[i]].data; | ||
if (parts[i] !== 'curve' && (data[0] & 0x80) == 0x80) { | ||
buf.writeUInt32BE(data.length + 1, o); | ||
o += 4; | ||
buf[o++] = 0; | ||
o += data.copy(buf, o); | ||
} else { | ||
buf.writeUInt32BE(data.length, o); | ||
o += 4; | ||
o += data.copy(buf, o); | ||
} | ||
} | ||
@@ -109,10 +154,1 @@ buf = buf.slice(0, o); | ||
} | ||
module.exports = { | ||
read: read, | ||
write: write, | ||
/* shared with ssh format */ | ||
keyTypeToAlg: keyTypeToAlg, | ||
algToKeyType: algToKeyType | ||
}; |
@@ -6,2 +6,3 @@ // Copyright 2015 Joyent, Inc. | ||
var Signature = require('./signature'); | ||
var PrivateKey = require('./private-key'); | ||
var errs = require('./errors'); | ||
@@ -17,2 +18,4 @@ | ||
parseSignature: Signature.parse, | ||
PrivateKey: PrivateKey, | ||
parsePrivateKey: PrivateKey.parse, | ||
@@ -19,0 +22,0 @@ /* errors */ |
// Copyright 2015 Joyent, Inc. | ||
module.exports = Key; | ||
var assert = require('assert-plus'); | ||
@@ -9,2 +11,3 @@ var algs = require('./algs'); | ||
var errs = require('./errors'); | ||
var PrivateKey = require('./private-key'); | ||
@@ -16,2 +19,4 @@ var InvalidAlgorithmError = errs.InvalidAlgorithmError; | ||
formats['pem'] = require('./formats/pem'); | ||
formats['pkcs1'] = require('./formats/pkcs1'); | ||
formats['pkcs8'] = require('./formats/pkcs8'); | ||
formats['rfc4253'] = require('./formats/rfc4253'); | ||
@@ -36,34 +41,44 @@ formats['ssh'] = require('./formats/ssh'); | ||
Object.defineProperties(this, { | ||
type: { enumerable: true, value: opts.type }, | ||
parts: { value: opts.parts }, | ||
part: { value: partLookup }, | ||
comment: { enumerable: true, writable: true, | ||
value: opts.comment }, | ||
source: { value: opts.source }, | ||
this.type = opts.type; | ||
this.parts = opts.parts; | ||
this.part = partLookup; | ||
this.comment = undefined; | ||
this.source = opts.source; | ||
/* To make things faster when all you want is the fingerprint */ | ||
_rfc4253Cache: { writable: true, | ||
value: opts._rfc4253Cache }, | ||
_hashCache: { writable: true, value: {} } | ||
}); | ||
/* for speeding up hashing/fingerprint operations */ | ||
this._rfc4253Cache = opts._rfc4253Cache; | ||
this._hashCache = {}; | ||
var sz; | ||
this.curve = undefined; | ||
if (this.type === 'ecdsa') { | ||
var curve = this.part.curve.data.toString(); | ||
Object.defineProperties(this, { | ||
curve: { enumerable: true, value: curve }, | ||
size: { enumerable: true, | ||
value: algs.curves[curve].size } | ||
}); | ||
this.curve = curve; | ||
sz = algs.curves[curve].size; | ||
} else { | ||
var szPart = this.part[algInfo.sizePart]; | ||
var sz = szPart.data.length; | ||
sz = szPart.data.length; | ||
if (szPart.data[0] === 0x0) | ||
sz--; | ||
Object.defineProperties(this, { | ||
size: { enumerable: true, value: sz * 8 } | ||
}); | ||
sz = sz * 8; | ||
} | ||
this.size = sz; | ||
} | ||
Key.formats = formats; | ||
Object.defineProperty(Key.prototype, 'type', | ||
{enumerable: true, writable: true}); | ||
Object.defineProperty(Key.prototype, 'parts', {writable: true}); | ||
Object.defineProperty(Key.prototype, 'part', {writable: true}); | ||
Object.defineProperty(Key.prototype, 'comment', | ||
{enumerable: true, writable: true}); | ||
Object.defineProperty(Key.prototype, 'source', {writable: true}); | ||
Object.defineProperty(Key.prototype, '_rfc4253Cache', {writable: true}); | ||
Object.defineProperty(Key.prototype, '_hashCache', {writable: true}); | ||
Object.defineProperty(Key.prototype, 'size', | ||
{enumerable: true, writable: true}); | ||
Object.defineProperty(Key.prototype, 'curve', | ||
{enumerable: true, writable: true}); | ||
Key.prototype.toBuffer = function (format) { | ||
@@ -149,3 +164,3 @@ if (format === undefined) | ||
var oldVerify = v.verify.bind(v); | ||
var key = this.toBuffer('pem'); | ||
var key = this.toBuffer('pkcs8'); | ||
v.verify = function (signature, fmt) { | ||
@@ -173,2 +188,6 @@ if (typeof (signature) === 'object' && | ||
var k = formats[format].read(data); | ||
if (k instanceof PrivateKey) | ||
k = k.toPublic(); | ||
if (!k.comment) | ||
k.comment = name; | ||
return (k); | ||
@@ -179,3 +198,1 @@ } catch (e) { | ||
}; | ||
module.exports = Key; |
// Copyright 2015 Joyent, Inc. | ||
module.exports = Signature; | ||
var assert = require('assert-plus'); | ||
@@ -220,3 +222,1 @@ var algs = require('./algs'); | ||
} | ||
module.exports = Signature; |
// Copyright 2015 Joyent, Inc. | ||
module.exports = { | ||
bufferSplit: bufferSplit, | ||
addRSAMissing: addRSAMissing, | ||
calculateDSAPublic: calculateDSAPublic | ||
}; | ||
var assert = require('assert-plus'); | ||
var PrivateKey = require('./private-key'); | ||
@@ -33,4 +40,78 @@ function bufferSplit(buf, chr) { | ||
module.exports = { | ||
bufferSplit: bufferSplit | ||
}; | ||
function calculateDSAPublic(g, p, x) { | ||
assert.buffer(g); | ||
assert.buffer(p); | ||
assert.buffer(x); | ||
try { | ||
var bigInt = require('big-integer'); | ||
} catch (e) { | ||
throw (new Error('To load a PKCS#8 format DSA private key, ' + | ||
'the node big-integer library is required.')); | ||
} | ||
g = bigInt(g.toString('hex'), 16); | ||
p = bigInt(p.toString('hex'), 16); | ||
x = bigInt(x.toString('hex'), 16); | ||
var y = modexp(g, x, p); | ||
y = new Buffer(y.toString(16), 'hex'); | ||
if ((y[0] & 0x80) == 0x80) { | ||
var b = new Buffer(y.length + 1); | ||
b[0] = 0x00; | ||
y.copy(b, 1); | ||
y = b; | ||
} | ||
return (y); | ||
/* Bruce Schneier's modular exponentiation algorithm */ | ||
function modexp(base, exp, mod) { | ||
var res = bigInt(1); | ||
base = base.mod(mod); | ||
while (exp.gt(0)) { | ||
if (exp.isOdd()) | ||
res = res.times(base).mod(mod); | ||
exp = exp.shiftRight(1); | ||
base = base.square().mod(mod); | ||
} | ||
return (res); | ||
} | ||
} | ||
function addRSAMissing(key) { | ||
assert.object(key); | ||
assert.ok(key instanceof PrivateKey); | ||
try { | ||
var bigInt = require('big-integer'); | ||
} catch (e) { | ||
throw (new Error('To write a PEM private key from ' + | ||
'this source, the node big-integer lib is required.')); | ||
} | ||
var d = bigInt(key.part.d.data.toString('hex'), 16); | ||
var b; | ||
if (!key.part.dmodp) { | ||
var p = bigInt(key.part.p.data.toString('hex'), 16); | ||
var dmodp = d.mod(p.minus(1)); | ||
dmodp = new Buffer(dmodp.toString(16), 'hex'); | ||
if ((dmodp[0] & 0x80) == 0x80) { | ||
b = new Buffer(dmodp.length + 1); | ||
b[0] = 0x00; | ||
dmodp.copy(b, 1); | ||
dmodp = b; | ||
} | ||
key.part.dmodp = {name: 'dmodp', data: dmodp}; | ||
key.parts.push(key.part.dmodp); | ||
} | ||
if (!key.part.dmodq) { | ||
var q = bigInt(key.part.q.data.toString('hex'), 16); | ||
var dmodq = d.mod(q.minus(1)); | ||
dmodq = new Buffer(dmodq.toString(16), 'hex'); | ||
if ((dmodq[0] & 0x80) == 0x80) { | ||
b = new Buffer(dmodq.length + 1); | ||
b[0] = 0x00; | ||
dmodq.copy(b, 1); | ||
dmodq = b; | ||
} | ||
key.part.dmodq = {name: 'dmodq', data: dmodq}; | ||
key.parts.push(key.part.dmodq); | ||
} | ||
} |
{ | ||
"name": "sshpk", | ||
"version": "1.2.1", | ||
"version": "1.3.0", | ||
"description": "A library for finding and using SSH public keys", | ||
@@ -37,5 +37,8 @@ "main": "lib/index.js", | ||
"dependencies": { | ||
"asn1": "0.2.2", | ||
"assert-plus": "0.1.5" | ||
"asn1": ">=0.2.3 <0.3.0", | ||
"assert-plus": ">=0.1.5 <0.2.0" | ||
}, | ||
"optionalDependencies": { | ||
"big-integer": ">=1.6.0 <2.0.0" | ||
}, | ||
"devDependencies": { | ||
@@ -42,0 +45,0 @@ "tape": ">=3.5.0 <4.0.0", |
143
README.md
sshpk | ||
========= | ||
Parse, convert, fingerprint and use SSH public keys in pure node -- no | ||
`ssh-keygen` or other external dependencies. | ||
Parse, convert, fingerprint and use SSH keys (both public and private) in pure | ||
node -- no `ssh-keygen` or other external dependencies. | ||
Supports RSA, DSA and ECDSA (nistp-\*) key types. Can also parse SSH private | ||
keys in PEM format and output their public half. | ||
Supports RSA, DSA and ECDSA (nistp-\*) key types, in PEM (PKCS#1, PKCS#8) and | ||
OpenSSH formats. | ||
@@ -15,3 +15,3 @@ This library has been extracted from | ||
[`node-ssh-fingerprint`](https://github.com/bahamas10/node-ssh-fingerprint) | ||
(work by Dave Eddy), with some additions (including ECDSA support) by | ||
(work by Dave Eddy), with additions (including ECDSA support) by | ||
[Alex Wilson](https://github.com/arekinath). | ||
@@ -61,23 +61,36 @@ | ||
```js | ||
/* Read in a PEM public key (PKCS#8) */ | ||
/* Read in a PEM public key */ | ||
var keyPem = fs.readFileSync('id_rsa.pem'); | ||
var key = sshpk.parseKey(keyPem, 'pem'); | ||
/* Convert to PEM PKCS#8 public key format */ | ||
var pemBuf = key.toBuffer('pkcs8'); | ||
/* Read in an OpenSSH/PEM *private* key, will load just the public half */ | ||
/* Convert to SSH public key format (and return as a string) */ | ||
var sshKey = key.toString('ssh'); | ||
``` | ||
Signing and verifying: | ||
```js | ||
/* Read in an OpenSSH/PEM *private* key */ | ||
var keyPriv = fs.readFileSync('id_ecdsa'); | ||
var key = sshpk.parseKey(keyPriv, 'pem'); | ||
var key = sshpk.parsePrivateKey(keyPriv, 'pem'); | ||
var data = 'some data'; | ||
/* Convert to PEM PKCS#8 public key format */ | ||
var pemBuf = key.toBuffer('pem'); | ||
/* Sign some data with the key */ | ||
var s = key.createSign('sha1'); | ||
s.update(data); | ||
var signature = s.sign(); | ||
/* Convert to SSH public key format (and return as a string) */ | ||
var sshKey = key.toString('ssh'); | ||
/* Now load the public key (could also use just key.toPublic()) */ | ||
var keyPub = fs.readFileSync('id_ecdsa.pub'); | ||
key = sshpk.parseKey(keyPub, 'ssh'); | ||
/* Make a crypto.Verifier with this key */ | ||
var v = key.createVerify('sha1'); | ||
v.update('some data here'); | ||
v.update(data); | ||
var valid = v.verify(signature); | ||
/* => true! */ | ||
``` | ||
@@ -101,2 +114,4 @@ | ||
## Public keys | ||
### `parseKey(data[, format = 'ssh'[, name]])` | ||
@@ -111,3 +126,4 @@ | ||
both PKCS#1 and PKCS#8), `rfc4253` (raw OpenSSH wire format, as | ||
returned by `ssh-agent`, for example), `ssh` (OpenSSH format) | ||
returned by `ssh-agent`, for example), `ssh` (OpenSSH format), | ||
`pkcs1`, `pkcs8` | ||
- `name` -- Optional name for the key being parsed (eg. the filename that | ||
@@ -177,2 +193,79 @@ was opened). Used to generate Error messages | ||
## Private keys | ||
### `parsePrivateKey(data[, format = 'pem'[, name]])` | ||
Parses a private key from a given data format and returns a new | ||
`PrivateKey` object. | ||
Parameters | ||
- `data` -- Either a Buffer or String, containing the key | ||
- `format` -- String name of format to use, valid options are `pem` (supports | ||
both PKCS#1 and PKCS#8), `rfc4253` (raw OpenSSH wire format, as | ||
returned by `ssh-agent`, for example), `pkcs1`, `pkcs8` | ||
- `name` -- Optional name for the key being parsed (eg. the filename that | ||
was opened). Used to generate Error messages | ||
### `PrivateKey#type` | ||
String, the type of key. Valid options are `rsa`, `dsa`, `ecdsa`. | ||
### `PrivateKey#size` | ||
Integer, "size" of the key in bits. For RSA/DSA this is the size of the modulus; | ||
for ECDSA this is the bit size of the curve in use. | ||
### `PrivateKey#curve` | ||
Only present if `this.type === 'ecdsa'`, string containing the name of the | ||
named curve used with this key. Possible values include `nistp256`, `nistp384` | ||
and `nistp521`. | ||
### `PrivateKey#toBuffer([format = 'pkcs1'])` | ||
Convert the key into a given data format and return the serialized key as | ||
a Buffer. | ||
Parameters | ||
- `format` -- String name of format to use, valid options are `pkcs8`, `pkcs1`, | ||
`rfc4253`, `pem` (same as `pkcs1`) | ||
### `PrivateKey#toString([format = 'pkcs1'])` | ||
Same as `this.toBuffer(format).toString()`. | ||
### `PrivateKey#toPublic()` | ||
Extract just the public part of this private key, and return it as a `Key` | ||
object. | ||
### `PrivateKey#fingerprint([algorithm = 'sha256'])` | ||
Same as `this.toPublic().fingerprint()`. | ||
### `PrivateKey#createVerify([hashAlgorithm])` | ||
Same as `this.toPublic().createVerify()`. | ||
### `PrivateKey#createSign([hashAlgorithm])` | ||
Creates a `crypto.Sign` specialized to use this PrivateKey (and the correct | ||
key algorithm to match it). The returned Signer has the same API as a regular | ||
one, except that the `sign()` function takes no arguments, and returns a | ||
`Signature` object. | ||
Parameters | ||
- `hashAlgorithm` -- optional String name of hash algorithm to use, any | ||
supported by OpenSSL are valid, usually including | ||
`sha1`, `sha256`. | ||
`v.sign()` Parameters | ||
- none | ||
## Fingerprints | ||
### `parseFingerprint(fingerprint[, algorithms])` | ||
@@ -209,11 +302,13 @@ | ||
## Signatures | ||
### `parseSignature(signature, algorithm, format)` | ||
Parses a signature in a given format, createing a `Signature` object. Useful | ||
for converting between the SSH and ASN.1 (PKCS/OpenSSL) signature formats for | ||
DSA and ECDSA. | ||
Parses a signature in a given format, creating a `Signature` object. Useful | ||
for converting between the SSH and ASN.1 (PKCS/OpenSSL) signature formats, and | ||
also returned as output from `PrivateKey#createSign().sign()`. | ||
A Signature object can also be passed to a verifier produced by | ||
`Key#createVerify()` and it will automatically be converted into the correct | ||
format for verification. | ||
`Key#createVerify()` and it will automatically be converted internally into the | ||
correct format for verification. | ||
@@ -225,3 +320,3 @@ Parameters | ||
- `algorithm` -- a String, name of the algorithm to be used, possible values | ||
are `rsa`, `dsa` and `ecdsa` | ||
are `rsa`, `dsa`, `ecdsa` | ||
- `format` -- a String, either `asn1` or `ssh` | ||
@@ -276,1 +371,7 @@ | ||
- `innerErr` -- the inner Error thrown by the format parser | ||
Friends of sshpk | ||
---------------- | ||
* [`sshpk-agent`](https://github.com/arekinath/node-sshpk-agent) is a library | ||
for speaking the `ssh-agent` protocol from node.js, which uses `sshpk` |
75
test.js
@@ -5,10 +5,73 @@ var fs = require('fs'); | ||
var keyPem = fs.readFileSync('foo.pem'); | ||
var key = sk.Key.parse(keyPem, 'pem'); | ||
var sshf = require('ssh-fingerprint'); | ||
var httpsig = require('http-signature'); | ||
console.log('a %d bit %s key', key.size, key.type); | ||
console.log(key.toString('ssh')); | ||
console.log(key.fingerprint('sha256').toString()); | ||
var keyPem = fs.readFileSync('foo'); | ||
var keyPubPem = fs.readFileSync('foo.pem'); | ||
var keySsh = fs.readFileSync('foo.pub'); | ||
var Benchmark = require('benchmark'); | ||
var key = sk.Key.parse(keyPem, 'pem', './foo.pem'); | ||
console.log('-> using a %d bit %s key', key.size, key.type); | ||
console.log('-> fingerprint: %s', key.fingerprint('sha256').toString()); | ||
var fp = sk.Fingerprint.parse('MD5:0a:b6:37:a4:5f:e7:94:d3:31:e4:72:91:fc:0c:bb:2f'); | ||
console.log(fp.matches(key)); | ||
var keySshString = keySsh.toString(); | ||
var keyPemString = keyPem.toString(); | ||
var suite = new Benchmark.Suite(); | ||
suite.add('PrivateKey.parse(pem)', function() { | ||
sk.PrivateKey.parse(keyPem, 'pem'); | ||
}); | ||
suite.add('PrivateKey.parse(pkcs1)', function() { | ||
sk.PrivateKey.parse(keyPem, 'pkcs1'); | ||
}); | ||
suite.add('Key.parse(pem)', function() { | ||
sk.Key.parse(keyPubPem, 'pem'); | ||
}); | ||
suite.add('Key.parse(ssh)', function() { | ||
sk.Key.parse(keySsh, 'ssh'); | ||
}); | ||
suite.add('Key#fingerprint(md5).toString', function() { | ||
key.fingerprint('md5').toString(); | ||
}); | ||
suite.add('Key.parse(ssh)#fingerprint(md5).toString', function() { | ||
var k = sk.Key.parse(keySsh, 'ssh'); | ||
k.fingerprint('md5').toString(); | ||
}); | ||
suite.add('ufdsgen', function() { | ||
var k = sk.Key.parse(keySsh, 'ssh'); | ||
k.fingerprint('md5').toString('hex'); | ||
k.fingerprint('sha1').toString('base64'); | ||
k.fingerprint('sha256').toString('base64'); | ||
}); | ||
suite.add('Fingerprint.parse', function() { | ||
sk.Fingerprint.parse('MD5:0a:b6:37:a4:5f:e7:94:d3:31:e4:72:91:fc:0c:bb:2f'); | ||
}); | ||
suite.add('Fingerprint#matches(Key)', function() { | ||
fp.matches(key); | ||
}); | ||
suite.add('Key#toBuffer(pem)', function() { | ||
key.toBuffer('pem'); | ||
}); | ||
suite.add('Key#toBuffer(ssh)', function() { | ||
key.toBuffer('ssh'); | ||
}); | ||
suite.add('ssh-fingerprint(md5)', function() { | ||
sshf.calculate(keySshString, {algorithm: 'md5'}); | ||
}); | ||
suite.add('node-http-sig.pemToRsaSSHKey', function() { | ||
httpsig.pemToRsaSSHKey(keyPemString, 'foo@foo.com'); | ||
}); | ||
suite.add('node-http-sig.sshKeyFingerprint', function() { | ||
httpsig.sshKeyFingerprint(keySshString); | ||
}); | ||
suite.on('cycle', function(evt) { | ||
console.log(String(evt.target)); | ||
}); | ||
suite.run({async: true}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
2386808
24
1961
370
3
1
+ Addedasn1@0.2.6(transitive)
+ Addedbig-integer@1.6.52(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
- Removedasn1@0.2.2(transitive)
Updatedasn1@>=0.2.3 <0.3.0
Updatedassert-plus@>=0.1.5 <0.2.0