| 
 | 1 | +function x509Error(msg, cert) {  | 
 | 2 | +  throw new Error('SASL channel binding: ' + msg + ' when parsing public certificate ' + cert.toString('base64'))  | 
 | 3 | +}  | 
 | 4 | + | 
 | 5 | +function readASN1Length(data, index) {  | 
 | 6 | +  let length = data[index++]  | 
 | 7 | +  if (length < 0x80) return { length, index }  | 
 | 8 | + | 
 | 9 | +  const lengthBytes = length & 0x7f  | 
 | 10 | +  if (lengthBytes > 4) x509Error('bad length', data)  | 
 | 11 | + | 
 | 12 | +  length = 0  | 
 | 13 | +  for (let i = 0; i < lengthBytes; i++) {  | 
 | 14 | +    length = (length << 8) | data[index++]  | 
 | 15 | +  }  | 
 | 16 | + | 
 | 17 | +  return { length, index }  | 
 | 18 | +}  | 
 | 19 | + | 
 | 20 | +function readASN1OID(data, index) {  | 
 | 21 | +  if (data[index++] !== 0x6) x509Error('non-OID data', data) // 6 = OID  | 
 | 22 | + | 
 | 23 | +  const { length: OIDLength, index: indexAfterOIDLength } = readASN1Length(data, index)  | 
 | 24 | +  index = indexAfterOIDLength  | 
 | 25 | +  lastIndex = index + OIDLength  | 
 | 26 | + | 
 | 27 | +  const byte1 = data[index++]  | 
 | 28 | +  let oid = ((byte1 / 40) >> 0) + '.' + (byte1 % 40)  | 
 | 29 | + | 
 | 30 | +  while (index < lastIndex) {  | 
 | 31 | +    // loop over numbers in OID  | 
 | 32 | +    let value = 0  | 
 | 33 | +    while (index < lastIndex) {  | 
 | 34 | +      // loop over bytes in number  | 
 | 35 | +      const nextByte = data[index++]  | 
 | 36 | +      value = (value << 7) | (nextByte & 0x7f)  | 
 | 37 | +      if (nextByte < 0x80) break  | 
 | 38 | +    }  | 
 | 39 | +    oid += '.' + value  | 
 | 40 | +  }  | 
 | 41 | + | 
 | 42 | +  return { oid, index }  | 
 | 43 | +}  | 
 | 44 | + | 
 | 45 | +function expectASN1Seq(data, index) {  | 
 | 46 | +  if (data[index++] !== 0x30) x509Error('non-sequence data', data) // 30 = Sequence  | 
 | 47 | +  return readASN1Length(data, index)  | 
 | 48 | +}  | 
 | 49 | + | 
 | 50 | +function signatureAlgorithmHashFromCertificate(data, index) {  | 
 | 51 | +  // read this thread: https://www.postgresql.org/message-id/17760-b6c61e752ec07060%40postgresql.org  | 
 | 52 | +  if (index === undefined) index = 0  | 
 | 53 | +  index = expectASN1Seq(data, index).index  | 
 | 54 | +  const { length: certInfoLength, index: indexAfterCertInfoLength } = expectASN1Seq(data, index)  | 
 | 55 | +  index = indexAfterCertInfoLength + certInfoLength // skip over certificate info  | 
 | 56 | +  index = expectASN1Seq(data, index).index // skip over signature length field  | 
 | 57 | +  const { oid, index: indexAfterOID } = readASN1OID(data, index)  | 
 | 58 | +  switch (oid) {  | 
 | 59 | +    // RSA  | 
 | 60 | +    case '1.2.840.113549.1.1.4':  | 
 | 61 | +      return 'MD5'  | 
 | 62 | +    case '1.2.840.113549.1.1.5':  | 
 | 63 | +      return 'SHA-1'  | 
 | 64 | +    case '1.2.840.113549.1.1.11':  | 
 | 65 | +      return 'SHA-256'  | 
 | 66 | +    case '1.2.840.113549.1.1.12':  | 
 | 67 | +      return 'SHA-384'  | 
 | 68 | +    case '1.2.840.113549.1.1.13':  | 
 | 69 | +      return 'SHA-512'  | 
 | 70 | +    case '1.2.840.113549.1.1.14':  | 
 | 71 | +      return 'SHA-224'  | 
 | 72 | +    case '1.2.840.113549.1.1.15':  | 
 | 73 | +      return 'SHA512-224'  | 
 | 74 | +    case '1.2.840.113549.1.1.16':  | 
 | 75 | +      return 'SHA512-256'  | 
 | 76 | +    // ECDSA  | 
 | 77 | +    case '1.2.840.10045.4.1':  | 
 | 78 | +      return 'SHA-1'  | 
 | 79 | +    case '1.2.840.10045.4.3.1':  | 
 | 80 | +      return 'SHA-224'  | 
 | 81 | +    case '1.2.840.10045.4.3.2':  | 
 | 82 | +      return 'SHA-256'  | 
 | 83 | +    case '1.2.840.10045.4.3.3':  | 
 | 84 | +      return 'SHA-384'  | 
 | 85 | +    case '1.2.840.10045.4.3.4':  | 
 | 86 | +      return 'SHA-512'  | 
 | 87 | +    // RSASSA-PSS: hash is indicated separately  | 
 | 88 | +    case '1.2.840.113549.1.1.10':  | 
 | 89 | +      index = indexAfterOID  | 
 | 90 | +      index = expectASN1Seq(data, index).index  | 
 | 91 | +      if (data[index++] !== 0xa0) x509Error('non-tag data', data) // a0 = constructed tag 0  | 
 | 92 | +      index = readASN1Length(data, index).index // skip over tag length field  | 
 | 93 | +      index = expectASN1Seq(data, index).index // skip over sequence length field  | 
 | 94 | +      const { oid: hashOID } = readASN1OID(data, index)  | 
 | 95 | +      switch (hashOID) {  | 
 | 96 | +        // standalone hash OIDs  | 
 | 97 | +        case '1.2.840.113549.2.5':  | 
 | 98 | +          return 'MD5'  | 
 | 99 | +        case '1.3.14.3.2.26':  | 
 | 100 | +          return 'SHA-1'  | 
 | 101 | +        case '2.16.840.1.101.3.4.2.1':  | 
 | 102 | +          return 'SHA-256'  | 
 | 103 | +        case '2.16.840.1.101.3.4.2.2':  | 
 | 104 | +          return 'SHA-384'  | 
 | 105 | +        case '2.16.840.1.101.3.4.2.3':  | 
 | 106 | +          return 'SHA-512'  | 
 | 107 | +      }  | 
 | 108 | +      x509Error('unknown hash OID ' + hashOID, data)  | 
 | 109 | +    // Ed25519 -- see https: return//github.com/openssl/openssl/issues/15477  | 
 | 110 | +    case '1.3.101.110':  | 
 | 111 | +    case '1.3.101.112': // ph  | 
 | 112 | +      return 'SHA-512'  | 
 | 113 | +    // Ed448 -- still not in pg 17.2 (if supported, digest would be SHAKE256 x 64 bytes)  | 
 | 114 | +    case '1.3.101.111':  | 
 | 115 | +    case '1.3.101.113': // ph  | 
 | 116 | +      x509Error('Ed448 certificate channel binding is not currently supported by Postgres')  | 
 | 117 | +  }  | 
 | 118 | +  x509Error('unknown OID ' + oid, data)  | 
 | 119 | +}  | 
 | 120 | + | 
 | 121 | +module.exports = { signatureAlgorithmHashFromCertificate }  | 
0 commit comments