chore: generate self-signed certificates for socks proxy

This commit is contained in:
Max Schmitt 2024-08-16 13:37:32 +02:00
parent a1d32d997c
commit a37e127042
5 changed files with 172 additions and 65 deletions

View file

@ -1,11 +0,0 @@
# Certfificates for Socks Proxy
These certificates are used when client certificates are used with
Playwright. Playwright then creates a Socks proxy, which sits between
the browser and the actual target server. The Socks proxy uses this certificiate
to talk to the browser and establishes its own secure TLS connection to the server.
The certificates are generated via:
```bash
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -keyout key.pem -out cert.pem -subj "/CN=localhost"
```

View file

@ -1,19 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUTcrzEueVL/OuLHr4LBIPWeS4UL0wDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI0MDcwNDA4NDAzNFoXDTM0MDcw
MjA4NDAzNFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEApof+SZVN4UGma4xJDVHhMSpmEJoCdMPr+HFadJJK/brF
BNOhA1C5wNk8oD/XYo7enAHQH/EsBnq4MMxv79rXTGnIdXMF+43GdMDh5kh81FQy
Esw8Vt4eif9eZkjUxI2GHhR2ovJewmQa7E+SeUB2RzJTqz8QPLhd74JFfgaci+S2
8L37ScVjcw55T1PcNflzB4vwsQHBT3yND0MLDhm+8MLzmTl4Mw5PgIOaBl5Jh8Tr
wQF4eeeB3FPJoMQhTP8aGBjW1mo+NmSSRAPIAZyhmCAnDeC33yRjAaiHjaL5Pr9f
wt5zoF5+U1xWhGXWzGOE6p/VTj62F9a2fOXNHclYJQIDAQABo1MwUTAdBgNVHQ4E
FgQU9BoVzGtb5x70KqGO/89N1hyqi5kwHwYDVR0jBBgwFoAU9BoVzGtb5x70KqGO
/89N1hyqi5kwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYcbI
wvcfx2p8z0RNN3EA+epKX1SagZyJX4ORIO8kln1sDU+ceHde3n3xnp1dg6HG2qh1
a7CZub/fNUaP9R8+6iiV0wPT7Ybkb2NIJcH1yq+/bfSS5OC5DO0yv9SUADdBoDwa
zOuBAqdcYW1BHYcbAzsQnniRcejHu06ioaS6SwwJ8150rQnLT4Lh9LAl40W6v4nZ
NdTGQETTrbjcgH1ER4IhWTKtVyPOxGF9A/OOawMEdfS8BhUO7YRS4QNFFaQMrJAb
MDhDtjSyDogLr8P43xjjWvQWG9a7zTF0kKEsdJ0cEG5HATpg8bPHmrouxbs2HGeH
kJXzMykrsYyXsInN3w==
-----END CERTIFICATE-----

View file

@ -1,28 +0,0 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCmh/5JlU3hQaZr
jEkNUeExKmYQmgJ0w+v4cVp0kkr9usUE06EDULnA2TygP9dijt6cAdAf8SwGergw
zG/v2tdMach1cwX7jcZ0wOHmSHzUVDISzDxW3h6J/15mSNTEjYYeFHai8l7CZBrs
T5J5QHZHMlOrPxA8uF3vgkV+BpyL5LbwvftJxWNzDnlPU9w1+XMHi/CxAcFPfI0P
QwsOGb7wwvOZOXgzDk+Ag5oGXkmHxOvBAXh554HcU8mgxCFM/xoYGNbWaj42ZJJE
A8gBnKGYICcN4LffJGMBqIeNovk+v1/C3nOgXn5TXFaEZdbMY4Tqn9VOPrYX1rZ8
5c0dyVglAgMBAAECggEAB6zX4vNPKhUZAvbtvP/rlZUDLDu05kXLX+F1jk7ZxvTv
NKg+UQVM8l7wxN/8YM3944nP2lEGuuu4BoO9mvvmlV6Avy0EdxITNflX0AHCQxT4
U9Z253gIR0ruQl+T8tUk+8jsqNjr1iC//ukx8oWujdx7b7aR3IKQzcOeyU6rs2TN
lyrVVsEaFVi9+wCw0xyiCmPlobrn+egdigw7Zhp2BRinC6W9eMxuPS2hlhQUhBm/
eiD96YWp0RAv/L5qO93reoXIAzrrLdcUgPEnnq1zN7y2xihU2+B2sTph1m/A26+J
yPcXd7vQrXlRXQU6PaCa+0oJULlpiAzy3HPbnr4BkQKBgQDdmekTX8dQqiEZPX1C
017QRFbx0/x/TDFDSeJbDeauMzzCaGqCO2WVmYmTvFtby2G4/6BYowVtJVHm4uJl
XsYk8dWIQGLPIj1Cw7ZieJvb2EVRxgnY2oMaOTOazHzPHFzZV718zwEeZrryT82J
881E8wgM8V3DjkS4ye3TbwvimQKBgQDAYa/IdnpAg5z1TREi9Tt8fnoGpmSscAak
USgeXVsvoNzXXkE94MiiCOOrX1r68TWYDAzq6MKGDewkWOfLwXWR6D5C2LyE1q9P
1pxstgs/nC3ZUTz0yEH47ahSmhywhGlvXXOQEXUSLiVTOdeMCubMqwQW80F1868n
aBHcj5/lbQKBgQDIojjsWaNT3TTqbUmj30vQtI8jlBLgDlPr4FEYr5VT0wAH5BHK
p4xpzgFJyRfOHG312TuMBM087LUinfjsXsp3WJ1EJ0dO0mk0sY3HyfsTKNRaHTt9
Ixnf/DpExS+bNMq73Tyqa6FPrSNFkAtAA4SuEHwRe9aw33ZI+EpjS/8uwQKBgQCi
9NwqSLlLVnColEw0uVdXH+cLJPzX19i4bQo3lkp8MJ2ATJWk7XflUPRQoGf3ckQ8
c9CpVtoXJUnmi+xkeo21Nu0uQFqHhzZewWIk75rdmdR4ZUjl649+ZQkUVviASNjq
fVU7Lp5k9POm6LL9K+rOaPoA2rKTUAQItC2VD4+YjQKBgB6kgvgN6Mz/u0RE3kkV
2GOoP5sso71Hxwh7o6JEzUMhR+e/T/LLcBwEjLYcf1FYRySHsXLn2Ar/Uw1J7pAZ
ud54/at+7mTDliaT8Ar7S9vcso7ZfmuDX9qB9+c77idPskVBPo2tjJbwvFcB6sww
5Elcfmj6tEP4YLJ6Kv3qTPhT
-----END PRIVATE KEY-----

View file

@ -15,14 +15,12 @@
*/
import net from 'net';
import path from 'path';
import http2 from 'http2';
import type https from 'https';
import fs from 'fs';
import tls from 'tls';
import stream from 'stream';
import { createSocket, createTLSSocket } from '../utils/happy-eyeballs';
import { escapeHTML, ManualPromise, rewriteErrorMessage } from '../utils';
import { escapeHTML, generateSelfSignedCertificate, ManualPromise, rewriteErrorMessage } from '../utils';
import type { SocksSocketClosedPayload, SocksSocketDataPayload, SocksSocketRequestedPayload } from '../common/socksProxy';
import { SocksProxy } from '../common/socksProxy';
import type * as channels from '@protocol/channels';
@ -32,10 +30,9 @@ let dummyServerTlsOptions: tls.TlsOptions | undefined = undefined;
function loadDummyServerCertsIfNeeded() {
if (dummyServerTlsOptions)
return;
dummyServerTlsOptions = {
key: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/key.pem')),
cert: fs.readFileSync(path.join(__dirname, '../../bin/socks-certs/cert.pem')),
};
// TODO: do we want to have it unique per browser context, launch or global?
const { cert, key } = generateSelfSignedCertificate('localhost');
dummyServerTlsOptions = { key, cert };
}
class ALPNCache {

View file

@ -25,3 +25,171 @@ export function calculateSha1(buffer: Buffer | string): string {
hash.update(buffer);
return hash.digest('hex');
}
const encodeBase128 = (value: number) => {
const bytes = new Uint8Array(calculateBase128BytesNeeded(value));
const lastPos = bytes.byteLength - 1;
let pos = lastPos;
do {
let byte = value & 0x7f; // Take the last 7 bits
value >>>= 7; // Shift right, unsigned
if (pos !== lastPos) {
byte |= 0x80; // Set the continuation bit on all but the first byte
}
bytes[pos--] = byte; // Insert the byte at the start of the array
} while (value > 0);
return bytes;
};
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;
};
class ASN1 {
static toSequence(data: Buffer[]): Buffer {
return this._encode(0x30, Buffer.concat(data));
}
static toInteger(data: number): Buffer {
return this._encode(0x02, Buffer.from([data]));
}
static toObject(oid: string): Buffer {
const parts = oid.split('.').map((v) => Number(v));
// Encode the second part, which could be large, using base-128 encoding if necessary
const output = [encodeBase128(40 * parts[0] + parts[1])];
for (let i = 2; i < parts.length; i++) {
output.push(encodeBase128(parts[i]));
}
return this._encode(0x06, Buffer.concat(output));
}
static toNull(): Buffer {
return Buffer.from([0x05, 0x00]);
}
static toSet(data: Buffer[]): Buffer {
return this._encode(0x31, Buffer.concat(data));
}
static toContextSpecific(tag: number, data: Buffer): Buffer {
return this._encode(0xa0 + tag, data);
}
static toPrintableString(data: string): Buffer {
return this._encode(0x13, Buffer.from(data));
}
static toBitString(data: Buffer): Buffer {
// The first byte of the content is the number of unused bits at the end
const unusedBits = 0; // Assuming all bits are used
const content = Buffer.concat([Buffer.from([unusedBits]), data]);
return this._encode(0x03, content);
}
static toUtcTime(date: Date): Buffer {
const parts = [
date.getUTCFullYear().toString().slice(-2),
(date.getUTCMonth() + 1).toString().padStart(2, '0'),
date.getUTCDate().toString().padStart(2, '0'),
date.getUTCHours().toString().padStart(2, '0'),
date.getUTCMinutes().toString().padStart(2, '0'),
date.getUTCSeconds().toString().padStart(2, '0')
];
return this._encode(0x17, Buffer.from(parts.join('') + 'Z'));
}
private static _encode(tag: number, data: Buffer): Buffer {
const lengthBytes = this._encodeLength(data.length);
return Buffer.concat([Buffer.from([tag]), lengthBytes, data]);
}
private static _encodeLength(length: number): Buffer {
if (length < 128) {
return Buffer.from([length]);
} else {
const lengthBytes = [];
while (length > 0) {
lengthBytes.unshift(length & 0xFF);
length >>= 8;
}
return Buffer.from([0x80 | lengthBytes.length, ...lengthBytes]);
}
}
}
export function generateSelfSignedCertificate(commonName: string) {
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
const publicKeyDer = publicKey.export({ type: 'pkcs1', format: 'der' });
const tbsCertificate = ASN1.toSequence([
ASN1.toContextSpecific(0, ASN1.toInteger(1)), // version
ASN1.toInteger(1), // serialNumber
ASN1.toSequence([
ASN1.toObject('1.2.840.113549.1.1.11'),
ASN1.toNull()
]), // signature
ASN1.toSequence([
ASN1.toSet([
ASN1.toSequence([
ASN1.toObject('2.5.4.3'),
ASN1.toPrintableString(commonName)
]),
ASN1.toSequence([
ASN1.toObject('2.5.4.10'),
ASN1.toPrintableString('Client Certificate Demo')
])
])
]), // issuer
ASN1.toSequence([
ASN1.toUtcTime(new Date()),
ASN1.toUtcTime(new Date()),
]), // validity
ASN1.toSequence([
ASN1.toSet([
ASN1.toSequence([
ASN1.toObject('2.5.4.3'),
ASN1.toPrintableString(commonName)
]),
ASN1.toSequence([
ASN1.toObject('2.5.4.10'),
ASN1.toPrintableString('Client Certificate Demo')
])
])
]), // subject
ASN1.toSequence([
ASN1.toSequence([
ASN1.toObject('1.2.840.113549.1.1.1'),
ASN1.toNull()
]),
ASN1.toBitString(publicKeyDer)
]), // SubjectPublicKeyInfo
]);
const signature = crypto.sign('sha256', tbsCertificate, privateKey);
const certificate = ASN1.toSequence([
tbsCertificate,
ASN1.toSequence([
ASN1.toObject('1.2.840.113549.1.1.11'),
ASN1.toNull()
]),
ASN1.toBitString(signature)
]);
const certPem = [
'-----BEGIN CERTIFICATE-----',
certificate.toString('base64').match(/.{1,64}/g)!.join('\n'),
'-----END CERTIFICATE-----'
].join('\n');
return {
cert: certPem,
key: privateKey.export({ type: 'pkcs1', format: 'pem' }),
};
}