Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | 9x 9x 9x 9x 9x 9x 9x 9x 9x 4x 9x 4x 4x 2x 2x 9x 9x 3x 9x 9x 2x 7x 7x 10x 9x 9x 6x 6x 1x 3x 9x 9x 9x 9x 9x 9x 7x 4x 3x | import { createDecipheriv } from 'crypto'
import { inflateRaw } from 'zlib'
import { promisify } from 'util'
import { EventsGetResponse } from './types'
import { UnsealAggregateError, UnsealError } from './errors/unsealError'
import { Buffer } from 'buffer'
 
const asyncInflateRaw = promisify(inflateRaw)
 
export enum DecryptionAlgorithm {
  Aes256Gcm = 'aes-256-gcm',
}
 
export interface DecryptionKey {
  key: Buffer
  algorithm: DecryptionAlgorithm | `${DecryptionAlgorithm}`
}
 
const SEALED_HEADER = Buffer.from([0x9e, 0x85, 0xdc, 0xed])
 
function isEventResponse(data: unknown): data is EventsGetResponse {
  return Boolean(data && typeof data === 'object' && 'products' in data)
}
 
/**
 * @private
 * */
export function parseEventsResponse(unsealed: string): EventsGetResponse {
  const json = JSON.parse(unsealed)
 
  if (!isEventResponse(json)) {
    throw new Error('Sealed data is not valid events response')
  }
 
  return json
}
 
/**
 * Decrypts the sealed response with the provided keys.
 * The SDK will try to decrypt the result with each key until it succeeds.
 * To learn more about sealed results visit: https://dev.fingerprint.com/docs/sealed-client-results
 * @throws UnsealAggregateError
 * @throws Error
 */
export async function unsealEventsResponse(
  sealedData: Buffer,
  decryptionKeys: DecryptionKey[]
): Promise<EventsGetResponse> {
  const unsealed = await unseal(sealedData, decryptionKeys)
 
  return parseEventsResponse(unsealed)
}
 
/**
 * @private
 * */
export async function unseal(sealedData: Buffer, decryptionKeys: DecryptionKey[]) {
  if (sealedData.subarray(0, SEALED_HEADER.length).toString('hex') !== SEALED_HEADER.toString('hex')) {
    throw new Error('Invalid sealed data header')
  }
 
  const errors = new UnsealAggregateError([])
 
  for (const decryptionKey of decryptionKeys) {
    switch (decryptionKey.algorithm) {
      case DecryptionAlgorithm.Aes256Gcm:
        try {
          return await unsealAes256Gcm(sealedData, decryptionKey.key)
        } catch (e) {
          errors.addError(new UnsealError(decryptionKey, e as Error))
          continue
        }
 
      default:
        throw new Error(`Unsupported decryption algorithm: ${decryptionKey.algorithm}`)
    }
  }
 
  throw errors
}
 
async function unsealAes256Gcm(sealedData: Buffer, decryptionKey: Buffer) {
  const nonceLength = 12
  const nonce = sealedData.subarray(SEALED_HEADER.length, SEALED_HEADER.length + nonceLength)
 
  const authTagLength = 16
  const authTag = sealedData.subarray(-authTagLength)
 
  const ciphertext = sealedData.subarray(SEALED_HEADER.length + nonceLength, -authTagLength)
 
  const decipher = createDecipheriv('aes-256-gcm', decryptionKey, nonce).setAuthTag(authTag)
  const compressed = Buffer.concat([decipher.update(ciphertext), decipher.final()])
 
  const payload = await asyncInflateRaw(compressed)
 
  return payload.toString()
}
  |