Skip to content

Commit d268fc9

Browse files
author
Alex Wilson
committed
Multi-version runtime detection, isKey/isPrivateKey etc
1 parent f245b4f commit d268fc9

16 files changed

+238
-36
lines changed

README.md

+36
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,15 @@ Parameters
129129
- `name` -- Optional name for the key being parsed (eg. the filename that
130130
was opened). Used to generate Error messages
131131

132+
### `Key.isKey(obj)`
133+
134+
Returns `true` if the given object is a valid `Key` object created by a version
135+
of `sshpk` compatible with this one.
136+
137+
Parameters
138+
139+
- `obj` -- Object to identify
140+
132141
### `Key#type`
133142

134143
String, the type of key. Valid options are `rsa`, `dsa`, `ecdsa`.
@@ -221,6 +230,15 @@ Parameters
221230
- `name` -- Optional name for the key being parsed (eg. the filename that
222231
was opened). Used to generate Error messages
223232

233+
### `PrivateKey.isPrivateKey(obj)`
234+
235+
Returns `true` if the given object is a valid `PrivateKey` object created by a
236+
version of `sshpk` compatible with this one.
237+
238+
Parameters
239+
240+
- `obj` -- Object to identify
241+
224242
### `PrivateKey#type`
225243

226244
String, the type of key. Valid options are `rsa`, `dsa`, `ecdsa`.
@@ -306,6 +324,15 @@ Parameters
306324
support to. If `fingerprint` uses a hash algorithm not on
307325
this list, throws `InvalidAlgorithmError`.
308326

327+
### `Fingerprint.isFingerprint(obj)`
328+
329+
Returns `true` if the given object is a valid `Fingerprint` object created by a
330+
version of `sshpk` compatible with this one.
331+
332+
Parameters
333+
334+
- `obj` -- Object to identify
335+
309336
### `Fingerprint#toString([format])`
310337

311338
Returns a fingerprint as a string, in the given format.
@@ -345,6 +372,15 @@ Parameters
345372
are `rsa`, `dsa`, `ecdsa`
346373
- `format` -- a String, either `asn1` or `ssh`
347374

375+
### `Signature.isSignature(obj)`
376+
377+
Returns `true` if the given object is a valid `Signature` object created by a
378+
version of `sshpk` compatible with this one.
379+
380+
Parameters
381+
382+
- `obj` -- Object to identify
383+
348384
### `Signature#toBuffer([format = 'asn1'])`
349385

350386
Converts a Signature to the given format and returns it as a Buffer.

bin/sshpk-conv

+1-1
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ if (require.main === module) {
166166
});
167167
} else {
168168
var kind = 'public';
169-
if (key instanceof sshpk.PrivateKey)
169+
if (PrivateKey.isPrivateKey(key))
170170
kind = 'private';
171171
console.log('%s: a %d bit %s %s key', inFileName,
172172
key.size, key.type.toUpperCase(), kind);

lib/dhe.js

+5-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ var CRYPTO_HAVE_ECDH = (crypto.createECDH !== undefined &&
1717
var ecdh, ec, jsbn;
1818

1919
function DiffieHellman(key) {
20-
this._isPriv = (key instanceof PrivateKey);
20+
utils.assertCompatible(key, Key, [1, 4], 'key');
21+
this._isPriv = PrivateKey.isPrivateKey(key, [1, 3]);
2122
this._algo = key.type;
2223
this._curve = key.curve;
2324
this._key = key;
@@ -102,11 +103,9 @@ DiffieHellman.prototype.getKey = DiffieHellman.prototype.getPrivateKey;
102103

103104
DiffieHellman.prototype._keyCheck = function (pk, isPub) {
104105
assert.object(pk, 'key');
105-
if (!isPub) {
106-
assert.ok(pk instanceof PrivateKey,
107-
'key must be a sshpk.PrivateKey');
108-
}
109-
assert.ok(pk instanceof Key, 'key must be a sshpk.Key');
106+
if (!isPub)
107+
utils.assertCompatible(pk, PrivateKey, [1, 3], 'key');
108+
utils.assertCompatible(pk, Key, [1, 4], 'key');
110109

111110
if (pk.type !== this._algo) {
112111
throw (new Error('A ' + pk.type + ' key cannot be used in ' +

lib/ed-compat.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,17 @@ Verifier.prototype.update = function (chunk) {
3939

4040
Verifier.prototype.verify = function (signature, fmt) {
4141
var sig;
42-
if (typeof (signature) === 'object' &&
43-
signature instanceof Signature)
42+
if (Signature.isSignature(signature, [2, 0])) {
4443
sig = signature.toBuffer('raw');
45-
else if (typeof (signature) === 'string')
44+
45+
} else if (typeof (signature) === 'string') {
4646
sig = new Buffer(signature, 'base64');
47+
48+
} else if (Signature.isSignature(signature, [1, 0])) {
49+
throw (new Error('signature was created by too old ' +
50+
'a version of sshpk and cannot be verified'));
51+
}
52+
4753
assert.buffer(sig);
4854
return (nacl.sign.detached.verify(
4955
new Uint8Array(Buffer.concat(this.chunks)),

lib/fingerprint.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var algs = require('./algs');
77
var crypto = require('crypto');
88
var errs = require('./errors');
99
var Key = require('./key');
10+
var utils = require('./utils');
1011

1112
var FingerprintFormatError = errs.FingerprintFormatError;
1213
var InvalidAlgorithmError = errs.InvalidAlgorithmError;
@@ -45,10 +46,7 @@ Fingerprint.prototype.toString = function (format) {
4546

4647
Fingerprint.prototype.matches = function (key) {
4748
assert.object(key, 'key');
48-
/* Defer until runtime due to circular deps */
49-
if (Key === undefined)
50-
Key = require('./key');
51-
assert.ok(key instanceof Key, 'key');
49+
utils.assertCompatible(key, Key, [1, 0], 'key');
5250

5351
var theirHash = key.hash(this.algorithm);
5452
var theirHash2 = crypto.createHash(this.algorithm).
@@ -123,3 +121,20 @@ function base64Strip(s) {
123121
function sshBase64Format(alg, h) {
124122
return (alg.toUpperCase() + ':' + base64Strip(h));
125123
}
124+
125+
Fingerprint.isFingerprint = function (obj, ver) {
126+
return (utils.isCompatible(obj, Fingerprint, ver));
127+
};
128+
129+
/*
130+
* API versions for Fingerprint:
131+
* [1,0] -- initial ver
132+
* [1,1] -- first tagged ver
133+
*/
134+
Fingerprint.prototype._sshpkApiVersion = [1, 1];
135+
136+
Fingerprint._oldVersionDetect = function (obj) {
137+
assert.func(obj.toString);
138+
assert.func(obj.matches);
139+
return ([1, 0]);
140+
};

lib/formats/pem.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ function write(key, type) {
106106

107107
var der = new asn1.BerWriter();
108108

109-
if (key instanceof PrivateKey) {
109+
if (PrivateKey.isPrivateKey(key)) {
110110
if (type && type === 'pkcs8') {
111111
header = 'PRIVATE KEY';
112112
pkcs8.writePkcs8(der, key);
@@ -117,7 +117,7 @@ function write(key, type) {
117117
pkcs1.writePkcs1(der, key);
118118
}
119119

120-
} else if (key instanceof Key) {
120+
} else if (Key.isKey(key)) {
121121
if (type && type === 'pkcs1') {
122122
header = alg + ' PUBLIC KEY';
123123
pkcs1.writePkcs1(der, key);

lib/formats/pkcs1.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -219,19 +219,19 @@ function writePkcs1(der, key) {
219219

220220
switch (key.type) {
221221
case 'rsa':
222-
if (key instanceof PrivateKey)
222+
if (PrivateKey.isPrivateKey(key))
223223
writePkcs1RSAPrivate(der, key);
224224
else
225225
writePkcs1RSAPublic(der, key);
226226
break;
227227
case 'dsa':
228-
if (key instanceof PrivateKey)
228+
if (PrivateKey.isPrivateKey(key))
229229
writePkcs1DSAPrivate(der, key);
230230
else
231231
writePkcs1DSAPublic(der, key);
232232
break;
233233
case 'ecdsa':
234-
if (key instanceof PrivateKey)
234+
if (PrivateKey.isPrivateKey(key))
235235
writePkcs1ECDSAPrivate(der, key);
236236
else
237237
writePkcs1ECDSAPublic(der, key);

lib/formats/pkcs8.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ function readPkcs8ECDSAPublic(der) {
330330
function writePkcs8(der, key) {
331331
der.startSequence();
332332

333-
if (key instanceof PrivateKey) {
333+
if (PrivateKey.isPrivateKey(key)) {
334334
var sillyInt = new Buffer(1);
335335
sillyInt[0] = 0x0;
336336
der.writeBuffer(sillyInt, asn1.Ber.Integer);
@@ -340,21 +340,21 @@ function writePkcs8(der, key) {
340340
switch (key.type) {
341341
case 'rsa':
342342
der.writeOID('1.2.840.113549.1.1.1');
343-
if (key instanceof PrivateKey)
343+
if (PrivateKey.isPrivateKey(key))
344344
writePkcs8RSAPrivate(key, der);
345345
else
346346
writePkcs8RSAPublic(key, der);
347347
break;
348348
case 'dsa':
349349
der.writeOID('1.2.840.10040.4.1');
350-
if (key instanceof PrivateKey)
350+
if (PrivateKey.isPrivateKey(key))
351351
writePkcs8DSAPrivate(key, der);
352352
else
353353
writePkcs8DSAPublic(key, der);
354354
break;
355355
case 'ecdsa':
356356
der.writeOID('1.2.840.10045.2.1');
357-
if (key instanceof PrivateKey)
357+
if (PrivateKey.isPrivateKey(key))
358358
writePkcs8ECDSAPrivate(key, der);
359359
else
360360
writePkcs8ECDSAPublic(key, der);

lib/formats/rfc4253.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ function write(key) {
127127
var i;
128128

129129
var algInfo = algs.info[key.type];
130-
if (key instanceof PrivateKey)
130+
if (PrivateKey.isPrivateKey(key))
131131
algInfo = algs.privInfo[key.type];
132132
var parts = algInfo.parts;
133133

lib/formats/ssh-private.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,13 @@ function readSSHPrivate(type, buf) {
7878

7979
function write(key) {
8080
var pubKey;
81-
if (key instanceof PrivateKey)
81+
if (PrivateKey.isPrivateKey(key))
8282
pubKey = key.toPublic();
8383
else
8484
pubKey = key;
8585

8686
var privBuf;
87-
if (key instanceof PrivateKey) {
87+
if (PrivateKey.isPrivateKey(key)) {
8888
privBuf = new SSHBuffer({});
8989
var checkInt = crypto.randomBytes(4).readUInt32BE(0);
9090
privBuf.writeInt(checkInt);
@@ -113,7 +113,7 @@ function write(key) {
113113
buf = buf.toBuffer();
114114

115115
var header;
116-
if (key instanceof PrivateKey)
116+
if (PrivateKey.isPrivateKey(key))
117117
header = 'OPENSSH PRIVATE KEY';
118118
else
119119
header = 'OPENSSH PUBLIC KEY';

lib/formats/ssh.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ function read(buf) {
9797

9898
function write(key) {
9999
assert.object(key);
100-
if (key instanceof PrivateKey)
101-
throw (new Error('Private keys are not supported'));
100+
if (!Key.isKey(key))
101+
throw (new Error('Must be a public key'));
102102

103103
var parts = [];
104104
var alg = rfc4253.keyTypeToAlg(key);

lib/key.js

+47-3
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,25 @@ Key.prototype.createVerify = function (hashAlgo) {
176176
var oldVerify = v.verify.bind(v);
177177
var key = this.toBuffer('pkcs8');
178178
v.verify = function (signature, fmt) {
179-
if (typeof (signature) === 'object' &&
180-
signature instanceof Signature)
179+
if (Signature.isSignature(signature, [2, 0])) {
181180
return (oldVerify(key, signature.toBuffer('asn1')));
182-
return (oldVerify(key, signature, fmt));
181+
182+
} else if (typeof (signature) === 'string' ||
183+
Buffer.isBuffer(signature)) {
184+
return (oldVerify(key, signature, fmt));
185+
186+
/*
187+
* Avoid doing this on valid arguments, walking the prototype
188+
* chain can be quite slow.
189+
*/
190+
} else if (Signature.isSignature(signature, [1, 0])) {
191+
throw (new Error('signature was created by too old ' +
192+
'a version of sshpk and cannot be verified'));
193+
194+
} else {
195+
throw (new TypeError('signature must be a string, ' +
196+
'Buffer, or Signature object'));
197+
}
183198
};
184199
return (v);
185200
};
@@ -214,3 +229,32 @@ Key.parse = function (data, format, name) {
214229
throw (new KeyParseError(name, format, e));
215230
}
216231
};
232+
233+
Key.isKey = function (obj, ver) {
234+
return (utils.isCompatible(obj, Key, ver));
235+
};
236+
237+
/*
238+
* API versions for Key:
239+
* [1,0] -- initial ver, may take Signature for createVerify or may not
240+
* [1,1] -- added pkcs1, pkcs8 formats
241+
* [1,2] -- added auto, ssh-private, openssh formats
242+
* [1,3] -- added defaultHashAlgorithm
243+
* [1,4] -- added ed support, createDH
244+
* [1,5] -- first explicitly tagged version
245+
*/
246+
Key.prototype._sshpkApiVersion = [1, 5];
247+
248+
Key._oldVersionDetect = function (obj) {
249+
assert.func(obj.toBuffer);
250+
assert.func(obj.fingerprint);
251+
if (obj.createDH)
252+
return ([1, 4]);
253+
if (obj.defaultHashAlgorithm)
254+
return ([1, 3]);
255+
if (obj.formats['auto'])
256+
return ([1, 2]);
257+
if (obj.formats['pkcs1'])
258+
return ([1, 1]);
259+
return ([1, 0]);
260+
};

lib/private-key.js

+26
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,29 @@ PrivateKey.parse = function (data, format, name) {
196196
throw (new KeyParseError(name, format, e));
197197
}
198198
};
199+
200+
PrivateKey.isPrivateKey = function (obj, ver) {
201+
return (utils.isCompatible(obj, PrivateKey, ver));
202+
};
203+
204+
/*
205+
* API versions for PrivateKey:
206+
* [1,0] -- initial ver
207+
* [1,1] -- added auto, pkcs[18], openssh/ssh-private formats
208+
* [1,2] -- added defaultHashAlgorithm
209+
* [1,3] -- added derive, ed, createDH
210+
* [1,4] -- first tagged version
211+
*/
212+
PrivateKey.prototype._sshpkApiVersion = [1, 4];
213+
214+
PrivateKey._oldVersionDetect = function (obj) {
215+
assert.func(obj.toPublic);
216+
assert.func(obj.createSign);
217+
if (obj.derive)
218+
return ([1, 3]);
219+
if (obj.defaultHashAlgorithm)
220+
return ([1, 2]);
221+
if (obj.formats['auto'])
222+
return ([1, 1]);
223+
return ([1, 0]);
224+
};

lib/signature.js

+20
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,23 @@ function parseECDSA(data, type, format, opts) {
215215
opts.parts.push(s);
216216
return (new Signature(opts));
217217
}
218+
219+
Signature.isSignature = function (obj, ver) {
220+
return (utils.isCompatible(obj, Signature, ver));
221+
};
222+
223+
/*
224+
* API versions for Signature:
225+
* [1,0] -- initial ver
226+
* [2,0] -- support for rsa in full ssh format, compat with sshpk-agent
227+
* hashAlgorithm property
228+
* [2,1] -- first tagged version
229+
*/
230+
Signature.prototype._sshpkApiVersion = [2, 1];
231+
232+
Signature._oldVersionDetect = function (obj) {
233+
assert.func(obj.toBuffer);
234+
if (obj.hasOwnProperty('hashAlgorithm'))
235+
return ([2, 0]);
236+
return ([1, 0]);
237+
};

0 commit comments

Comments
 (0)