Skip to content

Commit 6b68d49

Browse files
author
Alex Wilson
committed
#54 want API for accessing x509 extensions
Reviewed by: Cody Peter Mello <[email protected]> Approved by: Cody Peter Mello <[email protected]>
1 parent 1088992 commit 6b68d49

File tree

7 files changed

+164
-10
lines changed

7 files changed

+164
-10
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,24 @@ is valid for. The possible strings at the moment are:
507507
Authority)
508508
* `'crl'` -- key can be used to sign Certificate Revocation Lists (CRLs)
509509

510+
### `Certificate#getExtension(nameOrOid)`
511+
512+
Retrieves information about a certificate extension, if present, or returns
513+
`undefined` if not. The string argument `nameOrOid` should be either the OID
514+
(for X509 extensions) or the name (for OpenSSH extensions) of the extension
515+
to retrieve.
516+
517+
The object returned will have the following properties:
518+
519+
* `format` -- String, set to either `'x509'` or `'openssh'`
520+
* `name` or `oid` -- String, only one set based on value of `format`
521+
* `data` -- Buffer, the raw data inside the extension
522+
523+
### `Certificate#getExtensions()`
524+
525+
Returns an Array of all present certificate extensions, in the same manner and
526+
format as `getExtension()`.
527+
510528
### `Certificate#isExpired([when])`
511529

512530
Tests whether the Certificate is currently expired (i.e. the `validFrom` and

lib/certificate.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,37 @@ Certificate.prototype.isSignedBy = function (issuerCert) {
120120
return (this.isSignedByKey(issuerCert.subjectKey));
121121
};
122122

123+
Certificate.prototype.getExtension = function (keyOrOid) {
124+
assert.string(keyOrOid, 'keyOrOid');
125+
var ext = this.getExtensions().filter(function (maybeExt) {
126+
if (maybeExt.format === 'x509')
127+
return (maybeExt.oid === keyOrOid);
128+
if (maybeExt.format === 'openssh')
129+
return (maybeExt.name === keyOrOid);
130+
return (false);
131+
})[0];
132+
return (ext);
133+
};
134+
135+
Certificate.prototype.getExtensions = function () {
136+
var exts = [];
137+
var x509 = this.signatures.x509;
138+
if (x509 && x509.extras && x509.extras.exts) {
139+
x509.extras.exts.forEach(function (ext) {
140+
ext.format = 'x509';
141+
exts.push(ext);
142+
});
143+
}
144+
var openssh = this.signatures.openssh;
145+
if (openssh && openssh.exts) {
146+
openssh.exts.forEach(function (ext) {
147+
ext.format = 'openssh';
148+
exts.push(ext);
149+
});
150+
}
151+
return (exts);
152+
};
153+
123154
Certificate.prototype.isSignedByKey = function (issuerKey) {
124155
utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');
125156

@@ -370,8 +401,9 @@ Certificate.isCertificate = function (obj, ver) {
370401
/*
371402
* API versions for Certificate:
372403
* [1,0] -- initial ver
404+
* [1,1] -- openssh format now unpacks extensions
373405
*/
374-
Certificate.prototype._sshpkApiVersion = [1, 0];
406+
Certificate.prototype._sshpkApiVersion = [1, 1];
375407

376408
Certificate._oldVersionDetect = function (obj) {
377409
return ([1, 0]);

lib/formats/openssh-cert.js

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,23 @@ function fromBuffer(data, algo, partial) {
122122
cert.validFrom = int64ToDate(sshbuf.readInt64());
123123
cert.validUntil = int64ToDate(sshbuf.readInt64());
124124

125-
cert.signatures.openssh.critical = sshbuf.readBuffer();
126-
cert.signatures.openssh.exts = sshbuf.readBuffer();
125+
var exts = [];
126+
var extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
127+
var ext;
128+
while (!extbuf.atEnd()) {
129+
ext = { critical: true };
130+
ext.name = extbuf.readString();
131+
ext.data = extbuf.readBuffer();
132+
exts.push(ext);
133+
}
134+
extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
135+
while (!extbuf.atEnd()) {
136+
ext = { critical: false };
137+
ext.name = extbuf.readString();
138+
ext.data = extbuf.readBuffer();
139+
exts.push(ext);
140+
}
141+
cert.signatures.openssh.exts = exts;
127142

128143
/* reserved */
129144
sshbuf.readBuffer();
@@ -278,13 +293,27 @@ function toBuffer(cert, noSig) {
278293
buf.writeInt64(dateToInt64(cert.validFrom));
279294
buf.writeInt64(dateToInt64(cert.validUntil));
280295

281-
if (sig.critical === undefined)
282-
sig.critical = Buffer.alloc(0);
283-
buf.writeBuffer(sig.critical);
296+
var exts = sig.exts;
297+
if (exts === undefined)
298+
exts = [];
284299

285-
if (sig.exts === undefined)
286-
sig.exts = Buffer.alloc(0);
287-
buf.writeBuffer(sig.exts);
300+
var extbuf = new SSHBuffer({});
301+
exts.forEach(function (ext) {
302+
if (ext.critical !== true)
303+
return;
304+
extbuf.writeString(ext.name);
305+
extbuf.writeBuffer(ext.data);
306+
});
307+
buf.writeBuffer(extbuf.toBuffer());
308+
309+
extbuf = new SSHBuffer({});
310+
exts.forEach(function (ext) {
311+
if (ext.critical === true)
312+
return;
313+
extbuf.writeString(ext.name);
314+
extbuf.writeBuffer(ext.data);
315+
});
316+
buf.writeBuffer(extbuf.toBuffer());
288317

289318
/* reserved */
290319
buf.writeBuffer(Buffer.alloc(0));

lib/formats/x509.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ function readExtension(cert, buf, der) {
242242
var extId = der.readOID();
243243
var id;
244244
var sig = cert.signatures.x509;
245-
sig.extras.exts = [];
245+
if (!sig.extras.exts)
246+
sig.extras.exts = [];
246247

247248
var critical;
248249
if (der.peek() === asn1.Ber.Boolean)

test/assets/openssh-exts.pub

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[email protected] AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgGJnHtSaSYoqtvjv5k2BeP4bKCQv3CVvjcG0EMbY/iToAAAAIbmlzdHAyNTYAAABBBK9+hFGVZ9RT61pg8t7EGgkvduhPr/CBYfx+5rQFEROj8EjkoGIH2xypHOHBz0WikK5hYcwTM5YMvnNxuU0h4+cAAAAAAAAAAAAAAAEAAAAIdXNlcl9mb28AAAAHAAAAA2ZvbwAAAABbvVB4AAAAAF2dMrUAAAAiAAAADWZvcmNlLWNvbW1hbmQAAAANAAAACWZvb2JhcmNtZAAAAHAAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEECFVIfBIMjMd5ibZFUmEIh/HhvrCvFr7fLOsva912h4J0TaGeHHL2OuHXFYHRRToZ9bZSI+5kGIdabZiCMXfI7aTYv7gT8uMNzZbw9qApwP91ZxJwTOkGikvhCvdhzMmDAAAAhAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAaQAAADEAwZVZk2dsVAy2w3dJMbMfNsP9sYEW5Qa5DRDAddpRV3yL9Sb318KwYzfeuRFoCl/HAAAAMCnLGQ23ZHJhxCVpmtlSeuAKC2lgoqK2UNsOPDUOFg2p74dqnWsBjaUi9Ddj0HfHkA== id_ecdsa2

test/assets/yubikey.pem

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIICRDCCASygAwIBAgIRAO0gfWbAn5zoJg4edq5vmYcwDQYJKoZIhvcNAQELBQAw
3+
ITEfMB0GA1UEAwwWWXViaWNvIFBJViBBdHRlc3RhdGlvbjAgFw0xNjAzMTQwMDAw
4+
MDBaGA8yMDUyMDQxNzAwMDAwMFowJTEjMCEGA1UEAwwaWXViaUtleSBQSVYgQXR0
5+
ZXN0YXRpb24gOWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASfpEFqoePaZIAs
6+
L2xkEdVZ67pgQWIkgaKDxQr+QidA/j+5DjStb515FJZ8qYAF64mVrZjaJgD3Outp
7+
9G5eJWvzozwwOjARBgorBgEEAYLECgMDBAMEAwMwEwYKKwYBBAGCxAoDBwQFAgNP
8+
jfEwEAYKKwYBBAGCxAoDCAQCAQEwDQYJKoZIhvcNAQELBQADggEBAKa+hH9ExP4I
9+
1g40Qzi6+xaB7K0nmlE4xXLAceVeBebIKMDFGdbJpcMGxw5K4GmcMlaYLxKUbUdX
10+
uQBZ5LZSiHALxinF/0Lpn1I8SS4txgy5JvSJxMyaWCuupvQ0/zKk+eTryrsO52WX
11+
RLYNdOwwlVHqhu2cNoZ7F0AynEfTwIksCcpiJX3UI8YmOnBUarJpWG2M98HbQUXx
12+
0oLgy5I46xtY+vbMtw7tn42BjJF10RV+99VNsnUagbzCgULVLWnbaYUARA7k+Doc
13+
MWNMYnZPS5ouvHnAJGFER9/v0YELGuyt/dDCU/qPUY95UW6lXJSO2Iy41E+libW7
14+
s4MpuZzf5rw=
15+
-----END CERTIFICATE-----
16+

test/certs.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ var fs = require('fs');
77
var path = require('path');
88
var crypto = require('crypto');
99
var sinon = require('sinon');
10+
var asn1 = require('asn1');
11+
var SSHBuffer = require('../lib/ssh-buffer');
1012

1113
var testDir = path.join(__dirname, 'assets');
1214

@@ -250,6 +252,17 @@ test('example cert: digicert ca (x509)', function (t) {
250252
t.strictEqual(cert.subjects.length, 1);
251253
t.deepEqual(cert.purposes.sort(),
252254
['ca', 'clientAuth', 'crl', 'serverAuth', 'signature']);
255+
var exts = cert.getExtensions();
256+
t.strictEqual(exts.length, 8);
257+
exts.forEach(function (ext) {
258+
t.strictEqual(ext.format, 'x509');
259+
t.strictEqual(typeof (ext.oid), 'string');
260+
});
261+
var basicExt = cert.getExtension('2.5.29.19');
262+
t.strictEqual(basicExt.oid, '2.5.29.19');
263+
t.strictEqual(basicExt.critical, true);
264+
t.strictEqual(basicExt.format, 'x509');
265+
t.strictEqual(basicExt.pathLen, 0);
253266
t.end();
254267
});
255268

@@ -386,3 +399,47 @@ test('cert with doubled-up DN attribute', function (t) {
386399

387400
t.end();
388401
});
402+
403+
test('example cert: yubikey attestation cert', function (t) {
404+
var cert = sshpk.parseCertificate(
405+
fs.readFileSync(path.join(testDir, 'yubikey.pem')),
406+
'pem');
407+
t.strictEqual(cert.subjectKey.type, 'ecdsa');
408+
t.strictEqual(cert.subjects[0].cn, 'YubiKey PIV Attestation 9e');
409+
410+
var serialExt = cert.getExtension('1.3.6.1.4.1.41482.3.7');
411+
t.ok(serialExt);
412+
var der = new asn1.Ber.Reader(serialExt.data);
413+
t.strictEqual(der.readInt(), 5213681);
414+
415+
var policyExt = cert.getExtension('1.3.6.1.4.1.41482.3.8');
416+
t.ok(policyExt);
417+
t.strictEqual(policyExt.data[0], 0x01); /* never require PIN */
418+
t.strictEqual(policyExt.data[1], 0x01); /* never require touch */
419+
420+
t.end();
421+
});
422+
423+
test('example cert: openssh extensions', function (t) {
424+
var cert = sshpk.parseCertificate(
425+
fs.readFileSync(path.join(testDir, 'openssh-exts.pub')),
426+
'openssh');
427+
t.strictEqual(cert.subjectKey.type, 'ecdsa');
428+
t.strictEqual(cert.subjects[0].uid, 'foo');
429+
430+
var forceCmdExt = cert.getExtension('force-command');
431+
t.ok(forceCmdExt);
432+
t.strictEqual(forceCmdExt.name, 'force-command');
433+
t.strictEqual(forceCmdExt.critical, true);
434+
435+
var cmdbuf = new SSHBuffer({ buffer: forceCmdExt.data });
436+
var cmd = cmdbuf.readString();
437+
t.strictEqual(cmd, 'foobarcmd');
438+
t.ok(cmdbuf.atEnd());
439+
440+
t.ok(cert.getExtension('permit-port-forwarding'));
441+
t.notOk(cert.getExtension('source-address'));
442+
t.notOk(cert.getExtension('permit-pty'));
443+
444+
t.end();
445+
});

0 commit comments

Comments
 (0)