function x509Error(msg, cert) { throw new Error('SASL channel binding: ' + msg + ' when parsing public certificate ' + cert.toString('base64')) } function readASN1Length(data, index) { let length = data[index++] if (length < 0x80) return { length, index } const lengthBytes = length & 0x7f if (lengthBytes > 4) x509Error('bad length', data) length = 0 for (let i = 0; i < lengthBytes; i++) { length = (length << 8) | data[index++] } return { length, index } } function readASN1OID(data, index) { if (data[index++] !== 0x6) x509Error('non-OID data', data) // 6 = OID const { length: OIDLength, index: indexAfterOIDLength } = readASN1Length(data, index) index = indexAfterOIDLength lastIndex = index + OIDLength const byte1 = data[index++] let oid = ((byte1 / 40) >> 0) + '.' + (byte1 % 40) while (index < lastIndex) { // loop over numbers in OID let value = 0 while (index < lastIndex) { // loop over bytes in number const nextByte = data[index++] value = (value << 7) | (nextByte & 0x7f) if (nextByte < 0x80) break } oid += '.' + value } return { oid, index } } function expectASN1Seq(data, index) { if (data[index++] !== 0x30) x509Error('non-sequence data', data) // 30 = Sequence return readASN1Length(data, index) } function signatureAlgorithmHashFromCertificate(data, index) { // read this thread: https://www.postgresql.org/message-id/17760-b6c61e752ec07060%40postgresql.org if (index === undefined) index = 0 index = expectASN1Seq(data, index).index const { length: certInfoLength, index: indexAfterCertInfoLength } = expectASN1Seq(data, index) index = indexAfterCertInfoLength + certInfoLength // skip over certificate info index = expectASN1Seq(data, index).index // skip over signature length field const { oid, index: indexAfterOID } = readASN1OID(data, index) switch (oid) { // RSA case '1.2.840.113549.1.1.4': return 'MD5' case '1.2.840.113549.1.1.5': return 'SHA-1' case '1.2.840.113549.1.1.11': return 'SHA-256' case '1.2.840.113549.1.1.12': return 'SHA-384' case '1.2.840.113549.1.1.13': return 'SHA-512' case '1.2.840.113549.1.1.14': return 'SHA-224' case '1.2.840.113549.1.1.15': return 'SHA512-224' case '1.2.840.113549.1.1.16': return 'SHA512-256' // ECDSA case '1.2.840.10045.4.1': return 'SHA-1' case '1.2.840.10045.4.3.1': return 'SHA-224' case '1.2.840.10045.4.3.2': return 'SHA-256' case '1.2.840.10045.4.3.3': return 'SHA-384' case '1.2.840.10045.4.3.4': return 'SHA-512' // RSASSA-PSS: hash is indicated separately case '1.2.840.113549.1.1.10': index = indexAfterOID index = expectASN1Seq(data, index).index if (data[index++] !== 0xa0) x509Error('non-tag data', data) // a0 = constructed tag 0 index = readASN1Length(data, index).index // skip over tag length field index = expectASN1Seq(data, index).index // skip over sequence length field const { oid: hashOID } = readASN1OID(data, index) switch (hashOID) { // standalone hash OIDs case '1.2.840.113549.2.5': return 'MD5' case '1.3.14.3.2.26': return 'SHA-1' case '2.16.840.1.101.3.4.2.1': return 'SHA-256' case '2.16.840.1.101.3.4.2.2': return 'SHA-384' case '2.16.840.1.101.3.4.2.3': return 'SHA-512' } x509Error('unknown hash OID ' + hashOID, data) // Ed25519 -- see https: return//github.com/openssl/openssl/issues/15477 case '1.3.101.110': case '1.3.101.112': // ph return 'SHA-512' // Ed448 -- still not in pg 17.2 (if supported, digest would be SHAKE256 x 64 bytes) case '1.3.101.111': case '1.3.101.113': // ph x509Error('Ed448 certificate channel binding is not currently supported by Postgres') } x509Error('unknown OID ' + oid, data) } module.exports = { signatureAlgorithmHashFromCertificate }