support for x509 name constraints extension
According the rfc, one can (and should) define a name constraints for CA certificates, to indicate a name space within which all subject names in subsequent certificates in a certification path MUST be located.
I failed to find out how to define such extension with forge. Did I miss something or the support for this extension is currently missing in the forge library?
It's currently not implemented. However, I ran into the same requirement and painstakingly made some code that implements it. The code I wrote only supports name constraints for IPs but I will look into implementing the rest of the section when I have the time. For the time being, you could modify the following code to fit your needs:
import {asn1, md, pki, util} from 'node-forge';
import * as forge from 'node-forge';
interface GenerateNameConstraintsInput {
/**
* Array of excluded IPs with their respective subnet mask (e.g. 1.1.1.1.255.255.255.255).
*/
excluded?: Array<string>
/**
* Array of permitted IPs with their respective subnet mask (e.g. 1.1.1.1.255.255.255.255).
*/
permitted?: Array<string>
}
/**
* Convert a variable octet length string to bytes. e.g. `1.2.3.4.255.255.255.0`
*/
function ipv4ToBytes(ip: string): forge.Bytes {
return ip.split('.').reduce((buffer, part) => {
buffer.putByte(parseInt(part, 10))
return buffer;
}, util.createBuffer()).getBytes()
}
/**
* Generate name constraints in conformance with
* [RFC 5280 § 4.2.1.10](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10), limited to
* IPs only.
*/
function generateNameConstraints(input: GenerateNameConstraintsInput): asn1.Asn1 {
const ipsToSequence = (ips: Array<string>) => ips.map(permitted => {
return asn1.create(
asn1.Class.UNIVERSAL,
asn1.Type.SEQUENCE,
true,
[asn1.create(asn1.Class.CONTEXT_SPECIFIC, 7, false, ipv4ToBytes(permitted))],
)
});
const permittedAndExcluded: Array<asn1.Asn1> = []
if (input.permitted !== undefined) {
permittedAndExcluded.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, ipsToSequence(input.permitted)))
}
if (input.excluded !== undefined) {
permittedAndExcluded.push(asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, ipsToSequence(input.excluded)))
}
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, permittedAndExcluded)
}
const cert = forge.pki.createCertificate();
cert.setExtensions([
{
id: '2.5.29.30', // https://oidref.com/2.5.29.30, https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10
critical: true,
value: generateNameConstraints({
permitted: [`192.168.5.245.255.255.255.255`),
}),
name: 'nameConstraints',
},
]);
A PR will follow.
Thanks, using your code I manage to add DNS name restrictions to a root CA.
Instead of asn1.create(asn1.Class.CONTEXT_SPECIFIC, 7, false, ipv4ToBytes(permitted)), I simply did: asn1.create(asn1.Class.CONTEXT_SPECIFIC, 2, false, util.encodeUtf8("example.com"))