OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / bare-crypto / lib / web / algorithm / ed25519.js

264 lines · javascript ref:
const crypto = require('../../..')
const binding = require('../../../binding')
const errors = require('../../errors')
const { Ed25519PublicKey, Ed25519PrivateKey } = require('../../key')
const CryptoKey = require('../crypto-key')

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

// https://w3c.github.io/webcrypto/#ed25519-operations-sign
exports.sign = function sign(algorithm, key, data) {
  if (key.type !== 'private') {
    throw errors.INVALID_ACCESS('Must pass private key for Ed25519 signing')
  }

  const signature = crypto.sign(null, data, key._handle)

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

// https://w3c.github.io/webcrypto/#ed25519-operations-verify
exports.verify = function verify(algorithm, key, signature, data) {
  if (key.type !== 'public') {
    throw errors.INVALID_ACCESS('Must pass public key for Ed25519 verification')
  }

  return crypto.verify(null, data, key._handle, signature)
}

// https://w3c.github.io/webcrypto/#ed25519-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 Ed25519 generateKey() operation`
      )
    }
  }

  const keys = crypto.generateKeyPair('ed25519')

  algorithm = { name: 'Ed25519' }

  return {
    publicKey: new CryptoKey('public', true, algorithm, ['verify'], keys.publicKey),
    privateKey: new CryptoKey('private', extractable, algorithm, ['sign'], keys.privateKey)
  }
}

// https://w3c.github.io/webcrypto/#ed25519-operations-import-key
exports.importKey = function importKey(format, keyData, algorithm, extractable, usages) {
  switch (format) {
    case 'spki': {
      for (const usage of usages) {
        if (usage !== 'verify') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      const publicKey = new Ed25519PublicKey()

      binding.ed25519FromSPKI(publicKey, keyData.buffer, keyData.byteOffset, keyData.byteLength)

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        publicKey
      )
    }
    case 'pkcs8': {
      for (const usage of usages) {
        if (usage !== 'sign') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      const privateKey = new Ed25519PrivateKey()

      binding.ed25519FromPKCS8(privateKey, keyData.buffer, keyData.byteOffset, keyData.byteLength)

      return new CryptoKey(
        'private',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        privateKey
      )
    }
    case 'raw':
      for (const usage of usages) {
        if (usage !== 'verify') {
          throw new SyntaxError(
            `Usage '${usage}' cannot be used for the Ed25519 importKey() operation`
          )
        }
      }

      if (keyData.byteLength * 8 !== 256) {
        throw errors.INVALID_DATA('Key must be 256 bits')
      }

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(keyData.buffer)
      )
    case 'jwk': {
      const jwk = keyData

      if ('d' in jwk) {
        if (usages.some((usage) => usage !== 'sign')) {
          throw new SyntaxError('JWK must be valid for signing')
        }
      } else {
        if (usages.some((usage) => usage !== 'verify')) {
          throw new SyntaxError('JWK must be valid for verification')
        }
      }

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

      if (jwk.crv !== 'Ed25519') {
        throw errors.INVALID_DATA('JWK must use the Ed25519 curve')
      }

      if ('alg' in jwk && jwk.alg !== 'Ed25519' && jwk.alg !== 'EdDSA') {
        throw errors.INVALID_DATA('JWK must use the Ed25519 curve')
      }

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

      if ('ext' in jwk && jwk.ext !== extractable && extractable) {
        throw errors.INVALID_DATA('JWK is not extractable')
      }

      if ('d' in jwk) {
        const key = Buffer.concat([
          Buffer.from(jwk.d, 'base64url'),
          Buffer.from(jwk.x, 'base64url')
        ])

        if (key.byteLength * 8 !== 512) {
          throw errors.INVALID_DATA('Key must be 512 bits')
        }

        const handle = new Ed25519PrivateKey(key.buffer)

        key.fill(0)

        return new CryptoKey(
          'private',
          extractable,
          {
            name: 'Ed25519'
          },
          usages,
          handle
        )
      }

      const key = Buffer.from(jwk.x, 'base64url')

      if (key.byteLength * 8 !== 256) {
        throw errors.INVALID_DATA('Key must be 256 bits')
      }

      return new CryptoKey(
        'public',
        extractable,
        {
          name: 'Ed25519'
        },
        usages,
        new Ed25519PublicKey(key.buffer)
      )
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the Ed25519 importKey() operation`
      )
  }
}

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

  switch (format) {
    case 'spki':
      if (key.type !== 'public') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return binding.ed25519ToSPKI(data)
    case 'pkcs8':
      if (key.type !== 'private') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return binding.ed25519ToPKCS8(data)
    case 'raw': {
      if (key.type !== 'public') {
        throw errors.INVALID_ACCESS(
          `Key of type '${key.type}' cannot be used for the Ed25519 exportKey() operation`
        )
      }

      return data.export().buffer
    }
    case 'jwk': {
      const buffer = data.export()

      if (key.type === 'private') {
        const d = buffer.subarray(0, 32).toString('base64url')
        const x = buffer.subarray(32).toString('base64url')

        return {
          kty: 'OKP',
          alg: 'Ed25519',
          crv: 'Ed25519',
          x,
          d,
          key_ops: key.usages,
          ext: key.extractable
        }
      }

      return {
        kty: 'OKP',
        alg: 'Ed25519',
        crv: 'Ed25519',
        x: buffer.toString('base64url'),
        key_ops: key.usages,
        ext: key.extractable
      }
    }
    default:
      throw errors.NOT_SUPPORTED(
        `Format '${format}' cannot be used for the HMAC exportKey() operation`
      )
  }
}