OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-booking / node_modules / bare-crypto / lib / web / algorithm / hmac.js

192 lines · javascript ref:
const crypto = require('../../..')
const errors = require('../../errors')
const CryptoKey = require('../crypto-key')

// https://w3c.github.io/webcrypto/#hmac

// https://w3c.github.io/webcrypto/#hmac-operations-sign
exports.sign = function sign(algorithm, key, data) {
  const digest = crypto.createHmac(key.algorithm.hash.name, key._handle).update(data).digest()

  return digest.buffer.slice(0, digest.byteLength)
}

// https://w3c.github.io/webcrypto/#hmac-operations-verify
exports.verify = function verify(algorithm, key, signature, data) {
  const digest = crypto.createHmac(key.algorithm.hash.name, key._handle).update(data).digest()

  if (ArrayBuffer.isView(signature)) {
    signature = Buffer.coerce(signature)
  } else {
    signature = Buffer.from(signature)
  }

  return signature.equals(digest)
}

// https://w3c.github.io/webcrypto/#hmac-operations-generate-key
exports.generateKey = function generateKey(algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(`Usage '${usage}' cannot be used for the HMAC generateKey() operation`)
    }
  }

  const { length = exports.getKeyLength(algorithm) } = algorithm.length

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  const key = crypto.createHmac(hash.name, crypto.randomBytes(length)).digest()

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'HMAC',
      length,
      hash: {
        name: hash.name.toUpperCase()
      }
    },
    usages,
    key
  )
}

// https://w3c.github.io/webcrypto/#hmac-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  for (const usage of usages) {
    if (usage !== 'sign' && usage !== 'verify') {
      throw new SyntaxError(`Invalid usage ${usage}`)
    }
  }

  let hash = algorithm.hash

  if (typeof hash === 'string') hash = { name: hash }

  let data

  switch (format) {
    case 'raw':
      data = keyData
      break
    case 'jwk':
      const jwk = keyData

      if (jwk.kty !== 'oct') {
        throw errors.INVALID_DATA('JWK key must be an octet sequence')
      }

      data = Buffer.from(jwk.k, 'base64url')

      switch (hash.name.toLowerCase()) {
        case 'sha-1':
          if (jwk.alg === 'HS1') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-256':
          if (jwk.alg === 'HS256') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-384':
          if (jwk.alg === 'HS384') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
        case 'sha-512':
          if (jwk.alg === 'HS512') break
          else throw errors.INVALID_DATA('Invalid JWK key algorithm')
      }

      if (usages.length && 'use' in jwk && jwk.use !== 'sign') {
        throw errors.INVALID_DATA('JWK cannot be used for signing')
      }

      if ('ext' in jwk && jwk.ext !== extractable && extractable) {
        throw errors.INVALID_DATA('JWK is not extractable')
      }
      break
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC importKey() operation`
      )
  }

  const length = data.byteLength * 8

  if (length === 0) {
    throw errors.INVALID_DATA('Key cannot be empty')
  }

  return new CryptoKey(
    'secret',
    extractable,
    {
      name: 'HMAC',
      length,
      hash: {
        name: hash.name.toUpperCase()
      }
    },
    usages,
    data
  )
}

// https://w3c.github.io/webcrypto/#hmac-operations-export-key
exports.exportKey = function exportKey(format, key) {
  const data = key._handle

  switch (format) {
    case 'raw':
      return data.buffer.slice(0, data.byteLength)
    case 'jwk': {
      const jwk = {
        kty: 'oct',
        k: data.toString('base64url'),
        alg: null,
        key_ops: key.usages,
        ext: key.extractable
      }

      switch (key.algorithm.hash.name) {
        case 'SHA-1':
          jwk.alg = 'HS1'
          break
        case 'SHA-256':
          jwk.alg = 'HS256'
          break
        case 'SHA-384':
          jwk.alg = 'HS384'
          break
        case 'SHA-512':
          jwk.alg = 'HS512'
          break
      }

      return jwk
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC exportKey() operation`
      )
  }
}

// https://w3c.github.io/webcrypto/#hmac-operations-get-key-length
exports.getKeyLength = function getKeyLength(algorithm) {
  const { length, hash } = algorithm

  if (length === undefined) {
    if (hash === 'SHA-1' || hash === 'SHA-256') return 512
    if (hash === 'SHA-512') return 1024

    throw errors.OPERATION_ERROR(`Invalid hash '${hash}'`)
  }

  if (length === 0) {
    throw errors.OPERATION_ERROR(`Invalid length ${length}`)
  }

  return length
}