auth/index.js

/**
 * Authentication functions
 *
 * @namespace auth
 */

const crypto = require('crypto')
let subtle
/* istanbul ignore next */
if (typeof window !== 'undefined') {
  subtle = window.crypto.subtle
} else {
  subtle = crypto.webcrypto.subtle
}

/**
 * Verify ISO 9798-2 2-Pass Unilateral Authentication
 *
 * @example
 * // setup multi-factor derived key
 * const key = await mfkdf.setup.key([ await mfkdf.setup.factors.password('password') ])

 * // challenger: create random challenge
 * const challenge = crypto.randomBytes(32)
 * const identity = Buffer.from('Challenger')

 * // responder: generate response
 * const response = await key.ISO97982PassUnilateralAuthSymmetric(challenge, identity)

 * // verifier: verify response
 * const authKey = await key.ISO9798SymmetricKey()
 * const valid = await mfkdf.auth.VerifyISO97982PassUnilateralAuthSymmetric(challenge, identity, response, authKey) // -> true
 *
 * @param {Buffer} challenge - The nonce value provided by the challenger
 * @param {Buffer} identity - The identity of the challenger
 * @param {Buffer} response - The response of the responder
 * @param {Buffer} key - The key used to authenticate
 * @returns {boolean} Whether the response is valid
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.17.0
 * @memberOf auth
 * @async
 * @deprecated
 */
async function VerifyISO97982PassUnilateralAuthSymmetric (
  challenge,
  identity,
  response,
  key
) {
  const plaintext = Buffer.concat([challenge, identity])

  const iv = response.subarray(0, 16)
  const ct = response.subarray(16)
  const decipher = crypto.createDecipheriv('AES-256-CBC', key, iv)

  let decrypted
  try {
    decrypted = Buffer.concat([decipher.update(ct), decipher.final()])
  } catch (e) {
    return false
  }

  return plaintext.toString('hex') === decrypted.toString('hex')
}
module.exports.VerifyISO97982PassUnilateralAuthSymmetric =
  VerifyISO97982PassUnilateralAuthSymmetric

/**
 * Verify ISO 9798-2 Public Key 2-Pass Unilateral Authentication
 *
 * @example
 * // setup multi-factor derived key
 * const key = await mfkdf.setup.key([ await mfkdf.setup.factors.password('password') ])
 *
 * // challenger: create random challenge
 * const challenge = crypto.randomBytes(32)
 * const identity = Buffer.from('Challenger')
 *
 * // responder: generate response
 * const response = await key.ISO97982PassUnilateralAuthAsymmetric(challenge, identity)
 *
 * // verifier: verify response
 * const authKey = await key.ISO9798AsymmetricKey()
 * const valid = await mfkdf.auth.VerifyISO97982PassUnilateralAuthAsymmetric(challenge, identity, response, authKey) // -> true
 *
 * @param {Buffer} challenge - The nonce value provided by the challenger
 * @param {Buffer} identity - The identity of the challenger
 * @param {Buffer} response - The response of the responder
 * @param {Buffer} key - The key used to authenticate
 * @returns {boolean} Whether the response is valid
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.17.0
 * @memberOf auth
 * @async
 * @deprecated
 */
async function VerifyISO97982PassUnilateralAuthAsymmetric (
  challenge,
  identity,
  response,
  key
) {
  const plaintext = Buffer.concat([challenge, identity])

  const cryptoKey = await subtle.importKey(
    'spki',
    key,
    { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
    false,
    ['verify']
  )
  return await subtle.verify(
    { name: 'RSASSA-PKCS1-v1_5' },
    cryptoKey,
    response,
    plaintext
  )
}
module.exports.VerifyISO97982PassUnilateralAuthAsymmetric =
  VerifyISO97982PassUnilateralAuthAsymmetric

/**
 * Verify ISO 9798-2 2-Pass Unilateral Authentication over CCF
 *
 * @example
 * // setup multi-factor derived key
 * const key = await mfkdf.setup.key([ await mfkdf.setup.factors.password('password') ])
 *
 * // challenger: create random challenge
 * const challenge = crypto.randomBytes(32)
 * const identity = Buffer.from('Challenger')
 *
 * // responder: generate response
 * const response = await key.ISO97982PassUnilateralAuthCCF(challenge, identity)
 *
 * // verifier: verify response
 * const authKey = await key.ISO9798CCFKey()
 * const valid = await mfkdf.auth.VerifyISO97982PassUnilateralAuthCCF(challenge, identity, response, authKey) // -> true
 *
 * @param {Buffer} challenge - The nonce value provided by the challenger
 * @param {Buffer} identity - The identity of the challenger
 * @param {Buffer} response - The response of the responder
 * @param {Buffer} key - The key used to authenticate
 * @returns {boolean} Whether the response is valid
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.17.0
 * @memberOf auth
 * @async
 * @deprecated
 */
async function VerifyISO97982PassUnilateralAuthCCF (
  challenge,
  identity,
  response,
  key
) {
  const ct = Buffer.concat([challenge, identity, key])
  const hash = crypto.createHash('sha256').update(ct).digest()
  return hash.toString('hex') === response.toString('hex')
}
module.exports.VerifyISO97982PassUnilateralAuthCCF =
  VerifyISO97982PassUnilateralAuthCCF

/**
 * Verify ISO 9798-2 1-Pass Unilateral Authentication
 *
 * @example
 * // setup multi-factor derived key
 * const key = await mfkdf.setup.key([ await mfkdf.setup.factors.password('password') ])
 * const identity = Buffer.from('Challenger')
 *
 * // responder: generate response
 * const response = await key.ISO97981PassUnilateralAuthSymmetric(identity)
 *
 * // verifier: verify response
 * const authKey = await key.ISO9798SymmetricKey()
 * const valid = await mfkdf.auth.VerifyISO97981PassUnilateralAuthSymmetric(identity, response, authKey) // -> true
 *
 * @param {Buffer} identity - The identity of the challenger
 * @param {Buffer} response - The response of the responder
 * @param {Buffer} key - The key used to authenticate
 * @param {number} [window=5] - The maximum time difference in seconds
 * @returns {boolean} Whether the response is valid
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.17.0
 * @memberOf auth
 * @async
 * @deprecated
 */
async function VerifyISO97981PassUnilateralAuthSymmetric (
  identity,
  response,
  key,
  window = 5
) {
  const challenge = response.subarray(0, 4)
  const value = response.subarray(4)

  const actual = Math.floor(Date.now() / 1000)
  const observed = challenge.readUInt32BE(0)
  if (Math.abs(actual - observed) > window) return false

  return await VerifyISO97982PassUnilateralAuthSymmetric(
    challenge,
    identity,
    value,
    key
  )
}
module.exports.VerifyISO97981PassUnilateralAuthSymmetric =
  VerifyISO97981PassUnilateralAuthSymmetric

/**
 * Verify ISO 9798-2 Public Key 1-Pass Unilateral Authentication
 *
 * @example
 * // setup multi-factor derived key
 * const key = await mfkdf.setup.key([ await mfkdf.setup.factors.password('password') ])
 * const identity = Buffer.from('Challenger')
 *
 * // responder: generate response
 * const response = await key.ISO97981PassUnilateralAuthAsymmetric(identity)
 *
 * // verifier: verify response
 * const authKey = await key.ISO9798AsymmetricKey()
 * const valid = await mfkdf.auth.VerifyISO97981PassUnilateralAuthAsymmetric(identity, response, authKey) // -> true
 *
 * @param {Buffer} identity - The identity of the challenger
 * @param {Buffer} response - The response of the responder
 * @param {Buffer} key - The key used to authenticate
 * @param {number} [window=5] - The maximum time difference in seconds
 * @returns {boolean} Whether the response is valid
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.17.0
 * @memberOf auth
 * @async
 * @deprecated
 */
async function VerifyISO97981PassUnilateralAuthAsymmetric (
  identity,
  response,
  key,
  window = 5
) {
  const challenge = response.subarray(0, 4)
  const value = response.subarray(4)

  const actual = Math.floor(Date.now() / 1000)
  const observed = challenge.readUInt32BE(0)
  if (Math.abs(actual - observed) > window) return false

  return await VerifyISO97982PassUnilateralAuthAsymmetric(
    challenge,
    identity,
    value,
    key
  )
}
module.exports.VerifyISO97981PassUnilateralAuthAsymmetric =
  VerifyISO97981PassUnilateralAuthAsymmetric

/**
 * Verify ISO 9798-2 1-Pass Unilateral Authentication over CCF
 *
 * @example
 * // setup multi-factor derived key
 * const key = await mfkdf.setup.key([ await mfkdf.setup.factors.password('password') ])
 * const identity = Buffer.from('Challenger')
 *
 * // responder: generate response
 * const response = await key.ISO97981PassUnilateralAuthCCF(identity)
 *
 * // verifier: verify response
 * const authKey = await key.ISO9798CCFKey()
 * const valid = await mfkdf.auth.VerifyISO97981PassUnilateralAuthCCF(identity, response, authKey) // -> true
 *
 * @param {Buffer} identity - The identity of the challenger
 * @param {Buffer} response - The response of the responder
 * @param {Buffer} key - The key used to authenticate
 * @param {number} [window=5] - The maximum time difference in seconds
 * @returns {boolean} Whether the response is valid
 * @author Vivek Nair (https://nair.me) <[email protected]>
 * @since 0.17.0
 * @memberOf auth
 * @async
 * @deprecated
 */
async function VerifyISO97981PassUnilateralAuthCCF (
  identity,
  response,
  key,
  window = 5
) {
  const challenge = response.subarray(0, 4)
  const value = response.subarray(4)

  const actual = Math.floor(Date.now() / 1000)
  const observed = challenge.readUInt32BE(0)
  if (Math.abs(actual - observed) > window) return false

  return await VerifyISO97982PassUnilateralAuthCCF(
    challenge,
    identity,
    value,
    key
  )
}
module.exports.VerifyISO97981PassUnilateralAuthCCF =
  VerifyISO97981PassUnilateralAuthCCF