Skip to content

Commit

Permalink
#54 want API for accessing x509 extensions
Browse files Browse the repository at this point in the history
Reviewed by: Cody Peter Mello <cody.mello@joyent.com>
Approved by: Cody Peter Mello <cody.mello@joyent.com>
  • Loading branch information
Alex Wilson committed Oct 10, 2018
1 parent 1088992 commit 6b68d49
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 10 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,24 @@ is valid for. The possible strings at the moment are:
Authority)
* `'crl'` -- key can be used to sign Certificate Revocation Lists (CRLs)

### `Certificate#getExtension(nameOrOid)`

Retrieves information about a certificate extension, if present, or returns
`undefined` if not. The string argument `nameOrOid` should be either the OID
(for X509 extensions) or the name (for OpenSSH extensions) of the extension
to retrieve.

The object returned will have the following properties:

* `format` -- String, set to either `'x509'` or `'openssh'`
* `name` or `oid` -- String, only one set based on value of `format`
* `data` -- Buffer, the raw data inside the extension

### `Certificate#getExtensions()`

Returns an Array of all present certificate extensions, in the same manner and
format as `getExtension()`.

### `Certificate#isExpired([when])`

Tests whether the Certificate is currently expired (i.e. the `validFrom` and
Expand Down
34 changes: 33 additions & 1 deletion lib/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,37 @@ Certificate.prototype.isSignedBy = function (issuerCert) {
return (this.isSignedByKey(issuerCert.subjectKey));
};

Certificate.prototype.getExtension = function (keyOrOid) {
assert.string(keyOrOid, 'keyOrOid');
var ext = this.getExtensions().filter(function (maybeExt) {
if (maybeExt.format === 'x509')
return (maybeExt.oid === keyOrOid);
if (maybeExt.format === 'openssh')
return (maybeExt.name === keyOrOid);
return (false);
})[0];
return (ext);
};

Certificate.prototype.getExtensions = function () {
var exts = [];
var x509 = this.signatures.x509;
if (x509 && x509.extras && x509.extras.exts) {
x509.extras.exts.forEach(function (ext) {
ext.format = 'x509';
exts.push(ext);
});
}
var openssh = this.signatures.openssh;
if (openssh && openssh.exts) {
openssh.exts.forEach(function (ext) {
ext.format = 'openssh';
exts.push(ext);
});
}
return (exts);
};

Certificate.prototype.isSignedByKey = function (issuerKey) {
utils.assertCompatible(issuerKey, Key, [1, 2], 'issuerKey');

Expand Down Expand Up @@ -370,8 +401,9 @@ Certificate.isCertificate = function (obj, ver) {
/*
* API versions for Certificate:
* [1,0] -- initial ver
* [1,1] -- openssh format now unpacks extensions
*/
Certificate.prototype._sshpkApiVersion = [1, 0];
Certificate.prototype._sshpkApiVersion = [1, 1];

Certificate._oldVersionDetect = function (obj) {
return ([1, 0]);
Expand Down
45 changes: 37 additions & 8 deletions lib/formats/openssh-cert.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,23 @@ function fromBuffer(data, algo, partial) {
cert.validFrom = int64ToDate(sshbuf.readInt64());
cert.validUntil = int64ToDate(sshbuf.readInt64());

cert.signatures.openssh.critical = sshbuf.readBuffer();
cert.signatures.openssh.exts = sshbuf.readBuffer();
var exts = [];
var extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
var ext;
while (!extbuf.atEnd()) {
ext = { critical: true };
ext.name = extbuf.readString();
ext.data = extbuf.readBuffer();
exts.push(ext);
}
extbuf = new SSHBuffer({ buffer: sshbuf.readBuffer() });
while (!extbuf.atEnd()) {
ext = { critical: false };
ext.name = extbuf.readString();
ext.data = extbuf.readBuffer();
exts.push(ext);
}
cert.signatures.openssh.exts = exts;

/* reserved */
sshbuf.readBuffer();
Expand Down Expand Up @@ -278,13 +293,27 @@ function toBuffer(cert, noSig) {
buf.writeInt64(dateToInt64(cert.validFrom));
buf.writeInt64(dateToInt64(cert.validUntil));

if (sig.critical === undefined)
sig.critical = Buffer.alloc(0);
buf.writeBuffer(sig.critical);
var exts = sig.exts;
if (exts === undefined)
exts = [];

if (sig.exts === undefined)
sig.exts = Buffer.alloc(0);
buf.writeBuffer(sig.exts);
var extbuf = new SSHBuffer({});
exts.forEach(function (ext) {
if (ext.critical !== true)
return;
extbuf.writeString(ext.name);
extbuf.writeBuffer(ext.data);
});
buf.writeBuffer(extbuf.toBuffer());

extbuf = new SSHBuffer({});
exts.forEach(function (ext) {
if (ext.critical === true)
return;
extbuf.writeString(ext.name);
extbuf.writeBuffer(ext.data);
});
buf.writeBuffer(extbuf.toBuffer());

/* reserved */
buf.writeBuffer(Buffer.alloc(0));
Expand Down
3 changes: 2 additions & 1 deletion lib/formats/x509.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,8 @@ function readExtension(cert, buf, der) {
var extId = der.readOID();
var id;
var sig = cert.signatures.x509;
sig.extras.exts = [];
if (!sig.extras.exts)
sig.extras.exts = [];

var critical;
if (der.peek() === asn1.Ber.Boolean)
Expand Down
1 change: 1 addition & 0 deletions test/assets/openssh-exts.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgGJnHtSaSYoqtvjv5k2BeP4bKCQv3CVvjcG0EMbY/iToAAAAIbmlzdHAyNTYAAABBBK9+hFGVZ9RT61pg8t7EGgkvduhPr/CBYfx+5rQFEROj8EjkoGIH2xypHOHBz0WikK5hYcwTM5YMvnNxuU0h4+cAAAAAAAAAAAAAAAEAAAAIdXNlcl9mb28AAAAHAAAAA2ZvbwAAAABbvVB4AAAAAF2dMrUAAAAiAAAADWZvcmNlLWNvbW1hbmQAAAANAAAACWZvb2JhcmNtZAAAAHAAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAIgAAAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEECFVIfBIMjMd5ibZFUmEIh/HhvrCvFr7fLOsva912h4J0TaGeHHL2OuHXFYHRRToZ9bZSI+5kGIdabZiCMXfI7aTYv7gT8uMNzZbw9qApwP91ZxJwTOkGikvhCvdhzMmDAAAAhAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAAaQAAADEAwZVZk2dsVAy2w3dJMbMfNsP9sYEW5Qa5DRDAddpRV3yL9Sb318KwYzfeuRFoCl/HAAAAMCnLGQ23ZHJhxCVpmtlSeuAKC2lgoqK2UNsOPDUOFg2p74dqnWsBjaUi9Ddj0HfHkA== id_ecdsa2
16 changes: 16 additions & 0 deletions test/assets/yubikey.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICRDCCASygAwIBAgIRAO0gfWbAn5zoJg4edq5vmYcwDQYJKoZIhvcNAQELBQAw
ITEfMB0GA1UEAwwWWXViaWNvIFBJViBBdHRlc3RhdGlvbjAgFw0xNjAzMTQwMDAw
MDBaGA8yMDUyMDQxNzAwMDAwMFowJTEjMCEGA1UEAwwaWXViaUtleSBQSVYgQXR0
ZXN0YXRpb24gOWUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASfpEFqoePaZIAs
L2xkEdVZ67pgQWIkgaKDxQr+QidA/j+5DjStb515FJZ8qYAF64mVrZjaJgD3Outp
9G5eJWvzozwwOjARBgorBgEEAYLECgMDBAMEAwMwEwYKKwYBBAGCxAoDBwQFAgNP
jfEwEAYKKwYBBAGCxAoDCAQCAQEwDQYJKoZIhvcNAQELBQADggEBAKa+hH9ExP4I
1g40Qzi6+xaB7K0nmlE4xXLAceVeBebIKMDFGdbJpcMGxw5K4GmcMlaYLxKUbUdX
uQBZ5LZSiHALxinF/0Lpn1I8SS4txgy5JvSJxMyaWCuupvQ0/zKk+eTryrsO52WX
RLYNdOwwlVHqhu2cNoZ7F0AynEfTwIksCcpiJX3UI8YmOnBUarJpWG2M98HbQUXx
0oLgy5I46xtY+vbMtw7tn42BjJF10RV+99VNsnUagbzCgULVLWnbaYUARA7k+Doc
MWNMYnZPS5ouvHnAJGFER9/v0YELGuyt/dDCU/qPUY95UW6lXJSO2Iy41E+libW7
s4MpuZzf5rw=
-----END CERTIFICATE-----

57 changes: 57 additions & 0 deletions test/certs.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var sinon = require('sinon');
var asn1 = require('asn1');
var SSHBuffer = require('../lib/ssh-buffer');

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

Expand Down Expand Up @@ -250,6 +252,17 @@ test('example cert: digicert ca (x509)', function (t) {
t.strictEqual(cert.subjects.length, 1);
t.deepEqual(cert.purposes.sort(),
['ca', 'clientAuth', 'crl', 'serverAuth', 'signature']);
var exts = cert.getExtensions();
t.strictEqual(exts.length, 8);
exts.forEach(function (ext) {
t.strictEqual(ext.format, 'x509');
t.strictEqual(typeof (ext.oid), 'string');
});
var basicExt = cert.getExtension('2.5.29.19');
t.strictEqual(basicExt.oid, '2.5.29.19');
t.strictEqual(basicExt.critical, true);
t.strictEqual(basicExt.format, 'x509');
t.strictEqual(basicExt.pathLen, 0);
t.end();
});

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

t.end();
});

test('example cert: yubikey attestation cert', function (t) {
var cert = sshpk.parseCertificate(
fs.readFileSync(path.join(testDir, 'yubikey.pem')),
'pem');
t.strictEqual(cert.subjectKey.type, 'ecdsa');
t.strictEqual(cert.subjects[0].cn, 'YubiKey PIV Attestation 9e');

var serialExt = cert.getExtension('1.3.6.1.4.1.41482.3.7');
t.ok(serialExt);
var der = new asn1.Ber.Reader(serialExt.data);
t.strictEqual(der.readInt(), 5213681);

var policyExt = cert.getExtension('1.3.6.1.4.1.41482.3.8');
t.ok(policyExt);
t.strictEqual(policyExt.data[0], 0x01); /* never require PIN */
t.strictEqual(policyExt.data[1], 0x01); /* never require touch */

t.end();
});

test('example cert: openssh extensions', function (t) {
var cert = sshpk.parseCertificate(
fs.readFileSync(path.join(testDir, 'openssh-exts.pub')),
'openssh');
t.strictEqual(cert.subjectKey.type, 'ecdsa');
t.strictEqual(cert.subjects[0].uid, 'foo');

var forceCmdExt = cert.getExtension('force-command');
t.ok(forceCmdExt);
t.strictEqual(forceCmdExt.name, 'force-command');
t.strictEqual(forceCmdExt.critical, true);

var cmdbuf = new SSHBuffer({ buffer: forceCmdExt.data });
var cmd = cmdbuf.readString();
t.strictEqual(cmd, 'foobarcmd');
t.ok(cmdbuf.atEnd());

t.ok(cert.getExtension('permit-port-forwarding'));
t.notOk(cert.getExtension('source-address'));
t.notOk(cert.getExtension('permit-pty'));

t.end();
});

0 comments on commit 6b68d49

Please sign in to comment.