Skip to content

Commit 3bd4c38

Browse files
author
Alex Wilson
committed
#25 want support for extKeyUsage in X.509
Reviewed by: Robert Mustacchi <rm@joyent.com>
1 parent 3ae5356 commit 3bd4c38

6 files changed

Lines changed: 345 additions & 12 deletions

File tree

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,10 +408,10 @@ and the OpenSSH certificate format. This feature is intended to be used mainly
408408
to access basic metadata about certificates, extract public keys from them, and
409409
also to generate simple self-signed certificates from an existing key.
410410

411-
Notably, there is no implementation of CA chain-of-trust verification, and no
412-
support for key usage restrictions (or other kinds of restrictions). Please do
413-
the security world a favour, and DO NOT use this code for certificate
414-
verification in the traditional X.509 CA chain style.
411+
Notably, there is no implementation of CA chain-of-trust verification, and only
412+
very minimal support for key usage restrictions. Please do the security world
413+
a favour, and DO NOT use this code for certificate verification in the
414+
traditional X.509 CA chain style.
415415

416416
### `parseCertificate(data, format)`
417417

@@ -436,6 +436,7 @@ Parameters
436436
certificate validity period. If given
437437
`lifetime` will be ignored
438438
- `serial` -- optional Buffer, the serial number of the certificate
439+
- `purposes` -- optional Array of String, X.509 key usage restrictions
439440

440441
### `createCertificate(subject, key, issuer, issuerKey[, options])`
441442

@@ -452,6 +453,7 @@ Parameters
452453
certificate validity period. If given
453454
`lifetime` will be ignored
454455
- `serial` -- optional Buffer, the serial number of the certificate
456+
- `purposes` -- optional Array of String, X.509 key usage restrictions
455457

456458
### `Certificate#subjects`
457459

@@ -475,6 +477,23 @@ May be `undefined` if the issuer's key is unknown (e.g. on an X509 certificate).
475477
The serial number of the certificate. As this is normally a 64-bit or wider
476478
integer, it is returned as a Buffer.
477479

480+
### `Certificate#purposes`
481+
482+
Array of Strings indicating the X.509 key usage purposes that this certificate
483+
is valid for. The possible strings at the moment are:
484+
485+
* `'signature'` -- key can be used for digital signatures
486+
* `'identity'` -- key can be used to attest about the identity of the signer
487+
(X.509 calls this `nonRepudiation`)
488+
* `'codeSigning'` -- key can be used to sign executable code
489+
* `'keyEncryption'` -- key can be used to encrypt other keys
490+
* `'encryption'` -- key can be used to encrypt data (only applies for RSA)
491+
* `'keyAgreement'` -- key can be used for key exchange protocols such as
492+
Diffie-Hellman
493+
* `'ca'` -- key can be used to sign other certificates (is a Certificate
494+
Authority)
495+
* `'crl'` -- key can be used to sign Certificate Revocation Lists (CRLs)
496+
478497
### `Certificate#isExpired([when])`
479498

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

lib/certificate.js

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ function Certificate(opts) {
3939
assert.date(opts.validFrom, 'options.validFrom');
4040
assert.date(opts.validUntil, 'optons.validUntil');
4141

42+
assert.optionalArrayOfString(opts.purposes, 'options.purposes');
43+
4244
this._hashCache = {};
4345

4446
this.subjects = opts.subjects;
@@ -49,6 +51,7 @@ function Certificate(opts) {
4951
this.serial = opts.serial;
5052
this.validFrom = opts.validFrom;
5153
this.validUntil = opts.validUntil;
54+
this.purposes = opts.purposes;
5255
}
5356

5457
Certificate.formats = formats;
@@ -108,6 +111,10 @@ Certificate.prototype.isSignedBy = function (issuerCert) {
108111

109112
if (!this.issuer.equals(issuerCert.subjects[0]))
110113
return (false);
114+
if (this.issuer.purposes && this.issuer.purposes.length > 0 &&
115+
this.issuer.purposes.indexOf('ca') === -1) {
116+
return (false);
117+
}
111118

112119
return (this.isSignedByKey(issuerCert.subjectKey));
113120
};
@@ -180,6 +187,47 @@ Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
180187
if (serial === undefined)
181188
serial = new Buffer('0000000000000001', 'hex');
182189

190+
var purposes = options.purposes;
191+
if (purposes === undefined)
192+
purposes = [];
193+
194+
if (purposes.indexOf('signature') === -1)
195+
purposes.push('signature');
196+
197+
/* Self-signed certs are always CAs. */
198+
if (purposes.indexOf('ca') === -1)
199+
purposes.push('ca');
200+
if (purposes.indexOf('crl') === -1)
201+
purposes.push('crl');
202+
203+
/*
204+
* If we weren't explicitly given any other purposes, do the sensible
205+
* thing and add some basic ones depending on the subject type.
206+
*/
207+
if (purposes.length <= 3) {
208+
var hostSubjects = subjects.filter(function (subject) {
209+
return (subject.type === 'host');
210+
});
211+
var userSubjects = subjects.filter(function (subject) {
212+
return (subject.type === 'user');
213+
});
214+
if (hostSubjects.length > 0) {
215+
if (purposes.indexOf('serverAuth') === -1)
216+
purposes.push('serverAuth');
217+
}
218+
if (userSubjects.length > 0) {
219+
if (purposes.indexOf('clientAuth') === -1)
220+
purposes.push('clientAuth');
221+
}
222+
if (userSubjects.length > 0 || hostSubjects.length > 0) {
223+
if (purposes.indexOf('keyAgreement') === -1)
224+
purposes.push('keyAgreement');
225+
if (key.type === 'rsa' &&
226+
purposes.indexOf('encryption') === -1)
227+
purposes.push('encryption');
228+
}
229+
}
230+
183231
var cert = new Certificate({
184232
subjects: subjects,
185233
issuer: subjects[0],
@@ -188,7 +236,8 @@ Certificate.createSelfSigned = function (subjectOrSubjects, key, options) {
188236
signatures: {},
189237
serial: serial,
190238
validFrom: validFrom,
191-
validUntil: validUntil
239+
validUntil: validUntil,
240+
purposes: purposes
192241
});
193242
cert.signWith(key);
194243

@@ -236,6 +285,42 @@ Certificate.create =
236285
if (serial === undefined)
237286
serial = new Buffer('0000000000000001', 'hex');
238287

288+
var purposes = options.purposes;
289+
if (purposes === undefined)
290+
purposes = [];
291+
292+
if (purposes.indexOf('signature') === -1)
293+
purposes.push('signature');
294+
295+
if (options.ca === true) {
296+
if (purposes.indexOf('ca') === -1)
297+
purposes.push('ca');
298+
if (purposes.indexOf('crl') === -1)
299+
purposes.push('crl');
300+
}
301+
302+
var hostSubjects = subjects.filter(function (subject) {
303+
return (subject.type === 'host');
304+
});
305+
var userSubjects = subjects.filter(function (subject) {
306+
return (subject.type === 'user');
307+
});
308+
if (hostSubjects.length > 0) {
309+
if (purposes.indexOf('serverAuth') === -1)
310+
purposes.push('serverAuth');
311+
}
312+
if (userSubjects.length > 0) {
313+
if (purposes.indexOf('clientAuth') === -1)
314+
purposes.push('clientAuth');
315+
}
316+
if (userSubjects.length > 0 || hostSubjects.length > 0) {
317+
if (purposes.indexOf('keyAgreement') === -1)
318+
purposes.push('keyAgreement');
319+
if (key.type === 'rsa' &&
320+
purposes.indexOf('encryption') === -1)
321+
purposes.push('encryption');
322+
}
323+
239324
var cert = new Certificate({
240325
subjects: subjects,
241326
issuer: issuer,
@@ -244,7 +329,8 @@ Certificate.create =
244329
signatures: {},
245330
serial: serial,
246331
validFrom: validFrom,
247-
validUntil: validUntil
332+
validUntil: validUntil,
333+
purposes: purposes
248334
});
249335
cert.signWith(issuerKey);
250336

0 commit comments

Comments
 (0)