summaryrefslogtreecommitdiffhomepage
path: root/packages/server/src/helpers/verifySignature.ts
blob: 97bef50bcf8fc60a19d74afb094559112f5b0d78 (plain)
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
99
100
101
102
import crypto from 'crypto';
import { verify as ed25519Verify } from '@noble/ed25519';

import { COSEKEYS, COSEKTY, COSEPublicKey } from './convertCOSEtoPKCS';
import { convertCertBufferToPEM } from './convertCertBufferToPEM';
import { convertPublicKeyToPEM } from './convertPublicKeyToPEM';
import * as cbor from './cbor';

type VerifySignatureOptsLeafCert = {
  signature: Uint8Array;
  signatureBase: Uint8Array;
  leafCert: Uint8Array;
  hashAlgorithm?: string;
};

type VerifySignatureOptsCredentialPublicKey = {
  signature: Uint8Array;
  signatureBase: Uint8Array;
  credentialPublicKey: Uint8Array;
  hashAlgorithm?: string;
};

/**
 * Verify an authenticator's signature
 *
 * @param signature attStmt.sig
 * @param signatureBase Bytes that were signed over
 * @param publicKey Authenticator's public key as a PEM certificate
 * @param algo Which algorithm to use to verify the signature (default: `'sha256'`)
 */
export async function verifySignature(
  opts: VerifySignatureOptsLeafCert | VerifySignatureOptsCredentialPublicKey,
): Promise<boolean> {
  const { signature, signatureBase, hashAlgorithm = 'sha256' } = opts;
  const _isLeafcertOpts = isLeafCertOpts(opts);
  const _isCredPubKeyOpts = isCredPubKeyOpts(opts);

  if (!_isLeafcertOpts && !_isCredPubKeyOpts) {
    throw new Error('Must declare either "leafCert" or "credentialPublicKey"');
  }

  if (_isLeafcertOpts && _isCredPubKeyOpts) {
    throw new Error('Must not declare both "leafCert" and "credentialPublicKey"');
  }

  let publicKeyPEM = '';

  if (_isCredPubKeyOpts) {
    const { credentialPublicKey } = opts;

    // Decode CBOR to COSE
    let struct;
    try {
      struct = cbor.decodeFirst<COSEPublicKey>(credentialPublicKey);
    } catch (err) {
      const _err = err as Error;
      throw new Error(`Error decoding public key while converting to PEM: ${_err.message}`);
    }

    const kty = struct.get(COSEKEYS.kty);

    if (!kty) {
      throw new Error('Public key was missing kty');
    }

    // Check key type
    if (kty === COSEKTY.OKP) {
      // Verify Ed25519 slightly differently
      const x = struct.get(COSEKEYS.x);

      if (!x) {
        throw new Error('Public key was missing x (OKP)');
      }

      return ed25519Verify(signature, signatureBase, (x as Uint8Array));
    } else {
      // Convert pubKey to PEM for ECC and RSA
      publicKeyPEM = convertPublicKeyToPEM(credentialPublicKey);
    }
  }

  if (_isLeafcertOpts) {
    const { leafCert } = opts;
    publicKeyPEM = convertCertBufferToPEM(leafCert);
  }

  return crypto.createVerify(hashAlgorithm).update(signatureBase).verify(publicKeyPEM, signature);
}

function isLeafCertOpts(
  opts: VerifySignatureOptsLeafCert | VerifySignatureOptsCredentialPublicKey,
): opts is VerifySignatureOptsLeafCert {
  return Object.keys(opts as VerifySignatureOptsLeafCert).indexOf('leafCert') >= 0;
}

function isCredPubKeyOpts(
  opts: VerifySignatureOptsLeafCert | VerifySignatureOptsCredentialPublicKey,
): opts is VerifySignatureOptsCredentialPublicKey {
  return (
    Object.keys(opts as VerifySignatureOptsCredentialPublicKey).indexOf('credentialPublicKey') >= 0
  );
}