review feedback

This commit is contained in:
Max Schmitt 2024-08-16 13:52:37 +02:00
parent a37e127042
commit 23aca5e891

View file

@ -58,14 +58,14 @@ const calculateBase128BytesNeeded = (num: number) => {
return (bitsNeeded / 7) >>> 0; return (bitsNeeded / 7) >>> 0;
}; };
class ASN1 { class DER {
static toSequence(data: Buffer[]): Buffer { static encodeSequence(data: Buffer[]): Buffer {
return this._encode(0x30, Buffer.concat(data)); return this._encode(0x30, Buffer.concat(data));
} }
static toInteger(data: number): Buffer { static encodeInteger(data: number): Buffer {
return this._encode(0x02, Buffer.from([data])); return this._encode(0x02, Buffer.from([data]));
} }
static toObject(oid: string): Buffer { static encodeObject(oid: string): Buffer {
const parts = oid.split('.').map((v) => Number(v)); const parts = oid.split('.').map((v) => Number(v));
// Encode the second part, which could be large, using base-128 encoding if necessary // Encode the second part, which could be large, using base-128 encoding if necessary
const output = [encodeBase128(40 * parts[0] + parts[1])]; const output = [encodeBase128(40 * parts[0] + parts[1])];
@ -76,34 +76,41 @@ class ASN1 {
return this._encode(0x06, Buffer.concat(output)); return this._encode(0x06, Buffer.concat(output));
} }
static toNull(): Buffer { static encodeNull(): Buffer {
return Buffer.from([0x05, 0x00]); return Buffer.from([0x05, 0x00]);
} }
static toSet(data: Buffer[]): Buffer { static encodeSet(data: Buffer[]): Buffer {
return this._encode(0x31, Buffer.concat(data)); return this._encode(0x31, Buffer.concat(data));
} }
static toContextSpecific(tag: number, data: Buffer): Buffer { static encodeForContext(tag: number, data: Buffer): Buffer {
return this._encode(0xa0 + tag, data); return this._encode(0xa0 + tag, data);
} }
static toPrintableString(data: string): Buffer { static encodePrintableString(data: string): Buffer {
return this._encode(0x13, Buffer.from(data)); return this._encode(0x13, Buffer.from(data));
} }
static toBitString(data: Buffer): Buffer { static encodeBitString(data: Buffer): Buffer {
// The first byte of the content is the number of unused bits at the end // The first byte of the content is the number of unused bits at the end
const unusedBits = 0; // Assuming all bits are used const unusedBits = 0; // Assuming all bits are used
const content = Buffer.concat([Buffer.from([unusedBits]), data]); const content = Buffer.concat([Buffer.from([unusedBits]), data]);
return this._encode(0x03, content); return this._encode(0x03, content);
} }
static toUtcTime(date: Date): Buffer { static encodeDate(date: Date): Buffer {
const year = date.getUTCFullYear();
const isGeneralizedTime = year >= 2050;
const parts = [ const parts = [
date.getUTCFullYear().toString().slice(-2), isGeneralizedTime ? year.toString() : year.toString().slice(-2),
(date.getUTCMonth() + 1).toString().padStart(2, '0'), (date.getUTCMonth() + 1).toString().padStart(2, '0'),
date.getUTCDate().toString().padStart(2, '0'), date.getUTCDate().toString().padStart(2, '0'),
date.getUTCHours().toString().padStart(2, '0'), date.getUTCHours().toString().padStart(2, '0'),
date.getUTCMinutes().toString().padStart(2, '0'), date.getUTCMinutes().toString().padStart(2, '0'),
date.getUTCSeconds().toString().padStart(2, '0') date.getUTCSeconds().toString().padStart(2, '0')
]; ];
return this._encode(0x17, Buffer.from(parts.join('') + 'Z'));
const encodedDate = parts.join('') + 'Z';
const tag = isGeneralizedTime ? 0x18 : 0x17; // 0x18 for GeneralizedTime, 0x17 for UTCTime
return this._encode(tag, Buffer.from(encodedDate));
} }
private static _encode(tag: number, data: Buffer): Buffer { private static _encode(tag: number, data: Buffer): Buffer {
const lengthBytes = this._encodeLength(data.length); const lengthBytes = this._encodeLength(data.length);
@ -127,63 +134,64 @@ export function generateSelfSignedCertificate(commonName: string) {
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 }); const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
const publicKeyDer = publicKey.export({ type: 'pkcs1', format: 'der' }); const publicKeyDer = publicKey.export({ type: 'pkcs1', format: 'der' });
const tbsCertificate = ASN1.toSequence([ const tbsCertificate = DER.encodeSequence([
ASN1.toContextSpecific(0, ASN1.toInteger(1)), // version DER.encodeForContext(0, DER.encodeInteger(1)), // version
ASN1.toInteger(1), // serialNumber DER.encodeInteger(1), // serialNumber
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('1.2.840.113549.1.1.11'), DER.encodeObject('1.2.840.113549.1.1.11'),
ASN1.toNull() DER.encodeNull()
]), // signature ]), // signature
ASN1.toSequence([ DER.encodeSequence([
ASN1.toSet([ DER.encodeSet([
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('2.5.4.3'), DER.encodeObject('2.5.4.3'),
ASN1.toPrintableString(commonName) DER.encodePrintableString(commonName)
]), ]),
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('2.5.4.10'), DER.encodeObject('2.5.4.10'),
ASN1.toPrintableString('Client Certificate Demo') DER.encodePrintableString('Client Certificate Demo')
]) ])
]) ])
]), // issuer ]), // issuer
ASN1.toSequence([ DER.encodeSequence([
ASN1.toUtcTime(new Date()), DER.encodeDate(new Date()),
ASN1.toUtcTime(new Date()), DER.encodeDate(new Date()),
]), // validity ]), // validity
ASN1.toSequence([ DER.encodeSequence([
ASN1.toSet([ DER.encodeSet([
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('2.5.4.3'), DER.encodeObject('2.5.4.3'),
ASN1.toPrintableString(commonName) DER.encodePrintableString(commonName)
]), ]),
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('2.5.4.10'), DER.encodeObject('2.5.4.10'),
ASN1.toPrintableString('Client Certificate Demo') DER.encodePrintableString('Client Certificate Demo')
]) ])
]) ])
]), // subject ]), // subject
ASN1.toSequence([ DER.encodeSequence([
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('1.2.840.113549.1.1.1'), DER.encodeObject('1.2.840.113549.1.1.1'),
ASN1.toNull() DER.encodeNull()
]), ]),
ASN1.toBitString(publicKeyDer) DER.encodeBitString(publicKeyDer)
]), // SubjectPublicKeyInfo ]), // SubjectPublicKeyInfo
]); ]);
const signature = crypto.sign('sha256', tbsCertificate, privateKey); const signature = crypto.sign('sha256', tbsCertificate, privateKey);
const certificate = ASN1.toSequence([ const certificate = DER.encodeSequence([
tbsCertificate, tbsCertificate,
ASN1.toSequence([ DER.encodeSequence([
ASN1.toObject('1.2.840.113549.1.1.11'), DER.encodeObject('1.2.840.113549.1.1.11'),
ASN1.toNull() DER.encodeNull()
]), ]),
ASN1.toBitString(signature) DER.encodeBitString(signature)
]); ]);
const certPem = [ const certPem = [
'-----BEGIN CERTIFICATE-----', '-----BEGIN CERTIFICATE-----',
// Split the base64 string into lines of 64 characters
certificate.toString('base64').match(/.{1,64}/g)!.join('\n'), certificate.toString('base64').match(/.{1,64}/g)!.join('\n'),
'-----END CERTIFICATE-----' '-----END CERTIFICATE-----'
].join('\n'); ].join('\n');