chore: generate self-signed certificates for socks proxy
This commit is contained in:
parent
a1d32d997c
commit
a37e127042
|
|
@ -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"
|
||||
```
|
||||
|
|
@ -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-----
|
||||
|
|
@ -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-----
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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' }),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue