review feedback

This commit is contained in:
Max Schmitt 2024-08-16 15:35:31 +02:00
parent 23aca5e891
commit 1afc25aa70
2 changed files with 30 additions and 44 deletions

View file

@ -31,7 +31,7 @@ function loadDummyServerCertsIfNeeded() {
if (dummyServerTlsOptions) if (dummyServerTlsOptions)
return; return;
// TODO: do we want to have it unique per browser context, launch or global? // TODO: do we want to have it unique per browser context, launch or global?
const { cert, key } = generateSelfSignedCertificate('localhost'); const { cert, key } = generateSelfSignedCertificate();
dummyServerTlsOptions = { key, cert }; dummyServerTlsOptions = { key, cert };
} }

View file

@ -14,6 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import assert from 'assert';
import crypto from 'crypto'; import crypto from 'crypto';
export function createGuid(): string { export function createGuid(): string {
@ -26,46 +27,28 @@ export function calculateSha1(buffer: Buffer | string): string {
return hash.digest('hex'); return hash.digest('hex');
} }
const encodeBase128 = (value: number) => { // Variable-length quantity encoding aka. base-128 encoding
const bytes = new Uint8Array(calculateBase128BytesNeeded(value)); function encodeBase128 (value: number): Buffer {
const lastPos = bytes.byteLength - 1; const bytes = [];
let pos = lastPos;
do { do {
let byte = value & 0x7f; // Take the last 7 bits let byte = value & 0x7f;
value >>>= 7; // Shift right, unsigned value >>>= 7;
if (pos !== lastPos) { if (bytes.length > 0) byte |= 0x80;
byte |= 0x80; // Set the continuation bit on all but the first byte bytes.push(byte);
}
bytes[pos--] = byte; // Insert the byte at the start of the array
} while (value > 0); } while (value > 0);
return bytes; return Buffer.from(bytes.reverse());
};
const calculateBase128BytesNeeded = (num: number) => {
// Start at 6 and not 0 to account for overflow and to ensure that the
// division below always gives a value equal to or greater than 1.
// For example, consider the following 'real' bits needed:
// 0: 6 (initial value) + 1 (real) => 7 / 7 = 1
// 7: 6 (initial value) + 7 (real) => 13 / 7 = 1
// 8: 6 (initial value) + 8 (real) => 14 / 7 = 2
let bitsNeeded = 6;
do {
bitsNeeded++;
num >>>= 1;
} while (num > 0);
return (bitsNeeded / 7) >>> 0;
}; };
// ASN1/DER Speficiation: https://www.itu.int/rec/T-REC-X.680-X.693-202102-I/en
class DER { class DER {
static encodeSequence(data: Buffer[]): Buffer { static encodeSequence(data: Buffer[]): Buffer {
return this._encode(0x30, Buffer.concat(data)); return this._encode(0x30, Buffer.concat(data));
} }
static encodeInteger(data: number): Buffer { static encodeInteger(data: number): Buffer {
assert(data >= 0 && data <= 0xff);
return this._encode(0x02, Buffer.from([data])); return this._encode(0x02, Buffer.from([data]));
} }
static encodeObject(oid: string): Buffer { static encodeObjectIdentifier(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])];
@ -80,9 +63,10 @@ class DER {
return Buffer.from([0x05, 0x00]); return Buffer.from([0x05, 0x00]);
} }
static encodeSet(data: Buffer[]): Buffer { static encodeSet(data: Buffer[]): Buffer {
// We expect the data to be already sorted.
return this._encode(0x31, Buffer.concat(data)); return this._encode(0x31, Buffer.concat(data));
} }
static encodeForContext(tag: number, data: Buffer): Buffer { static encodeImplicitContextDependent(tag: number, data: Buffer): Buffer {
return this._encode(0xa0 + tag, data); return this._encode(0xa0 + tag, data);
} }
static encodePrintableString(data: string): Buffer { static encodePrintableString(data: string): Buffer {
@ -130,48 +114,50 @@ class DER {
} }
} }
export function generateSelfSignedCertificate(commonName: string) { // X.509 Specification: https://datatracker.ietf.org/doc/html/rfc2459#section-4.1
export function generateSelfSignedCertificate() {
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' });
// List of fields / structure: https://datatracker.ietf.org/doc/html/rfc2459#section-4.1
const tbsCertificate = DER.encodeSequence([ const tbsCertificate = DER.encodeSequence([
DER.encodeForContext(0, DER.encodeInteger(1)), // version DER.encodeImplicitContextDependent(0, DER.encodeInteger(1)), // version
DER.encodeInteger(1), // serialNumber DER.encodeInteger(1), // serialNumber
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('1.2.840.113549.1.1.11'), DER.encodeObjectIdentifier('1.2.840.113549.1.1.11'), // sha256WithRSAEncryption PKCS #1
DER.encodeNull() DER.encodeNull()
]), // signature ]), // signature
DER.encodeSequence([ DER.encodeSequence([
DER.encodeSet([ DER.encodeSet([
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('2.5.4.3'), DER.encodeObjectIdentifier('2.5.4.3'), // commonName X.520 DN component
DER.encodePrintableString(commonName) DER.encodePrintableString('localhost')
]), ]),
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('2.5.4.10'), DER.encodeObjectIdentifier('2.5.4.10'), // organizationName X.520 DN component
DER.encodePrintableString('Client Certificate Demo') DER.encodePrintableString('Client Certificate Demo')
]) ])
]) ])
]), // issuer ]), // issuer
DER.encodeSequence([ DER.encodeSequence([
DER.encodeDate(new Date()), DER.encodeDate(new Date()), // notBefore
DER.encodeDate(new Date()), DER.encodeDate(new Date()), // notAfter
]), // validity ]), // validity
DER.encodeSequence([ DER.encodeSequence([
DER.encodeSet([ DER.encodeSet([
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('2.5.4.3'), DER.encodeObjectIdentifier('2.5.4.3'), // commonName X.520 DN component
DER.encodePrintableString(commonName) DER.encodePrintableString('localhost')
]), ]),
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('2.5.4.10'), DER.encodeObjectIdentifier('2.5.4.10'), // organizationName X.520 DN component
DER.encodePrintableString('Client Certificate Demo') DER.encodePrintableString('Client Certificate Demo')
]) ])
]) ])
]), // subject ]), // subject
DER.encodeSequence([ DER.encodeSequence([
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('1.2.840.113549.1.1.1'), DER.encodeObjectIdentifier('1.2.840.113549.1.1.1'), // rsaEncryption PKCS #1
DER.encodeNull() DER.encodeNull()
]), ]),
DER.encodeBitString(publicKeyDer) DER.encodeBitString(publicKeyDer)
@ -183,7 +169,7 @@ export function generateSelfSignedCertificate(commonName: string) {
const certificate = DER.encodeSequence([ const certificate = DER.encodeSequence([
tbsCertificate, tbsCertificate,
DER.encodeSequence([ DER.encodeSequence([
DER.encodeObject('1.2.840.113549.1.1.11'), DER.encodeObjectIdentifier('1.2.840.113549.1.1.11'), // sha256WithRSAEncryption PKCS #1
DER.encodeNull() DER.encodeNull()
]), ]),
DER.encodeBitString(signature) DER.encodeBitString(signature)