Skip to content

Commit

Permalink
add option contrainToDomains to generateCACertificate
Browse files Browse the repository at this point in the history
  • Loading branch information
lipsumar committed Jul 5, 2024
1 parent 5778dd0 commit 4d79cdc
Showing 1 changed file with 81 additions and 6 deletions.
87 changes: 81 additions & 6 deletions src/util/tls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as fs from 'fs/promises';
import { v4 as uuid } from "uuid";
import * as forge from 'node-forge';

const { pki, md, util: { encode64 } } = forge;
const { asn1, pki, md, util } = forge;

export type CAOptions = (CertDataOptions | CertPathOptions);

Expand Down Expand Up @@ -63,7 +63,8 @@ export async function generateCACertificate(options: {
commonName?: string,
organizationName?: string,
countryName?: string,
bits?: number
bits?: number,
contrainToDomains?: string[]
} = {}) {
options = _.defaults({}, options, {
commonName: 'Mockttp Testing CA - DO NOT TRUST - TESTING ONLY',
Expand Down Expand Up @@ -98,11 +99,21 @@ export async function generateCACertificate(options: {
{ name: 'organizationName', value: options.organizationName }
]);

cert.setExtensions([
const extensions: any[] = [
{ name: 'basicConstraints', cA: true, critical: true },
{ name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, cRLSign: true, critical: true },
{ name: 'subjectKeyIdentifier' }
]);
{ name: 'subjectKeyIdentifier' },
];
if(options.contrainToDomains && options.contrainToDomains.length > 0) {
extensions.push({
critical: true,
name: 'nameConstraints',
value: generateNameConstraints({
permitted: options.contrainToDomains,
}),
})
}
cert.setExtensions(extensions);

// Self-issued too
cert.setIssuer(cert.subject.attributes);
Expand All @@ -116,9 +127,73 @@ export async function generateCACertificate(options: {
};
}


type GenerateNameConstraintsInput = {
/**
* Array of excluded domains
*/
excluded?: string[];

/**
* Array of permitted domains
*/
permitted?: string[];
};

/**
* Generate name constraints in conformance with
* [RFC 5280 § 4.2.1.10](https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10)
*/
function generateNameConstraints(
input: GenerateNameConstraintsInput
): forge.asn1.Asn1 {
const ipsToSequence = (ips: string[]) =>
ips.map((domain) => {
return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
asn1.create(
asn1.Class.CONTEXT_SPECIFIC,
2,
false,
util.encodeUtf8(domain)
),
]);
});

const permittedAndExcluded: forge.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
);
}

export function generateSPKIFingerprint(certPem: PEM) {
let cert = pki.certificateFromPem(certPem.toString('utf8'));
return encode64(
return util.encode64(
pki.getPublicKeyFingerprint(cert.publicKey, {
type: 'SubjectPublicKeyInfo',
md: md.sha256.create(),
Expand Down

0 comments on commit 4d79cdc

Please sign in to comment.