@reclaimprotocol/tls
Version:
TLS 1.2/1.3 for any JavaScript Environment
145 lines (144 loc) • 5.81 kB
JavaScript
import { cbc as aesCbc, gcm as aesGcm } from '@noble/ciphers/aes';
import { chacha20poly1305 } from '@noble/ciphers/chacha';
import { x25519 } from '@noble/curves/ed25519';
import { p256, p384 } from '@noble/curves/nist';
import { extract } from '@noble/hashes/hkdf';
import { hmac } from '@noble/hashes/hmac';
import { sha1 } from '@noble/hashes/legacy';
import { sha256, sha384 } from '@noble/hashes/sha2';
import { OriginatorPublicKey } from '@peculiar/asn1-cms';
import { AsnParser } from '@peculiar/asn1-schema';
import { mgf1, PKCS1_KEM, PKCS1_SHA256, PKCS1_SHA384, PKCS1_SHA512, PSS } from 'micro-rsa-dsa-dh/rsa.js';
import { asciiToUint8Array, concatenateUint8Arrays } from "../utils/generics.js";
import { bufToUint8Array, parseRsaPublicKeyFromAsn1 } from "./common.js";
import { randomBytes } from "./insecure-rand.js";
const CURVE_MAP = {
'P-256': p256,
'P-384': p384,
'X25519': x25519
};
const AUTH_CIPHER_MAP = {
'AES-128-GCM': aesGcm,
'AES-256-GCM': aesGcm,
'CHACHA20-POLY1305': chacha20poly1305,
};
const HASH_MAP = {
'SHA-1': sha1,
'SHA-256': sha256,
'SHA-384': sha384
};
const AUTH_TAG_BYTE_LENGTH = 16;
export const pureJsCrypto = {
importKey(_, raw) {
return raw;
},
exportKey(key) {
return key;
},
async generateKeyPair(alg) {
const curve = CURVE_MAP[alg];
const secretKey = curve.utils.randomSecretKey();
if (alg === 'P-256' || alg === 'P-384') {
// @ts-expect-error: need uncompressed public key for TLS
const pubKey = curve.getPublicKey(secretKey, false);
return { privKey: secretKey, pubKey };
}
return { privKey: secretKey, pubKey: curve.getPublicKey(secretKey) };
},
calculateSharedSecret(alg, privateKey, publicKey) {
const curve = CURVE_MAP[alg];
if (!curve) {
throw new Error(`Unsupported algorithm: ${alg}`);
}
const secret = curve.getSharedSecret(privateKey, publicKey);
if (alg === 'P-256' || alg === 'P-384') {
// from noble curves, the secret is packed with 1 y-coordinate byte
// so we need to remove the first byte
return secret.slice(1); // remove the first byte
}
return secret;
},
randomBytes: randomBytes,
asymmetricEncrypt(cipherSuite, { publicKey, data }) {
if (cipherSuite !== 'RSA-PCKS1_5') {
throw new Error(`Unsupported cipher suite ${cipherSuite}`);
}
return PKCS1_KEM.encrypt(parseRsaPublicKeyFromAsn1(publicKey), data);
},
encrypt(cipherSuite, { key, iv, data }) {
if (cipherSuite !== 'AES-128-CBC') {
throw new Error(`Unsupported cipher suite: ${cipherSuite}`);
}
const cipher = aesCbc(key, iv, { disablePadding: true });
return cipher.encrypt(data);
},
decrypt(cipherSuite, { key, iv, data }) {
if (cipherSuite !== 'AES-128-CBC') {
throw new Error(`Unsupported cipher suite: ${cipherSuite}`);
}
const cipher = aesCbc(key, iv, { disablePadding: true });
const decrypted = cipher.decrypt(data);
return decrypted;
},
authenticatedEncrypt(cipherSuite, { key, iv, data, aead }) {
const cipher = AUTH_CIPHER_MAP[cipherSuite](key, iv, aead);
const ciphertext = cipher.encrypt(data);
return {
ciphertext: ciphertext.slice(0, -AUTH_TAG_BYTE_LENGTH),
authTag: ciphertext.slice(-AUTH_TAG_BYTE_LENGTH),
};
},
authenticatedDecrypt(cipherSuite, { key, iv, data, aead, authTag }) {
const cipher = AUTH_CIPHER_MAP[cipherSuite](key, iv, aead);
const decrypted = cipher.decrypt(concatenateUint8Arrays([data, authTag]));
return { plaintext: decrypted };
},
verify(alg, { data, signature, publicKey }) {
if (alg === 'ECDSA-SECP384R1-SHA384'
|| alg === 'ECDSA-SECP384R1-SHA256'
|| alg === 'ECDSA-SECP256R1-SHA384'
|| alg === 'ECDSA-SECP256R1-SHA256') {
const parsedPubKey = parseAsn1PublicKey(publicKey);
const curv = alg.includes('P-384') ? p384 : p256;
return curv
.verify(signature, data, parsedPubKey, { prehash: true, format: 'der' });
}
if (alg === 'RSA-PSS-SHA256') {
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
const pss = PSS(sha256, mgf1(sha256), 32);
return pss.verify(rsaPubKey, data, signature);
}
if (alg === 'RSA-PKCS1-SHA256') {
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
return PKCS1_SHA256.verify(rsaPubKey, data, signature);
}
if (alg === 'RSA-PKCS1-SHA384') {
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
return PKCS1_SHA384.verify(rsaPubKey, data, signature);
}
if (alg === 'RSA-PKCS1-SHA512') {
const rsaPubKey = parseRsaPublicKeyFromAsn1(publicKey);
return PKCS1_SHA512.verify(rsaPubKey, data, signature);
}
throw new Error(`Unsupported signature algorithm: ${alg}`);
},
hash(alg, data) {
const hasher = HASH_MAP[alg].create();
hasher.update(data);
return hasher.digest();
},
hmac(alg, key, data) {
return hmac(HASH_MAP[alg], key, data);
},
extract(alg, hashLength, ikm, salt) {
salt = typeof salt === 'string' ? asciiToUint8Array(salt) : salt;
if (!salt.length) {
salt = new Uint8Array(hashLength).fill(0);
}
return extract(HASH_MAP[alg], ikm, salt);
}
};
function parseAsn1PublicKey(pubKey) {
const parsed = AsnParser.parse(pubKey, OriginatorPublicKey);
return bufToUint8Array(parsed.publicKey);
}