All files / src/crypto/token-signer token-verifier.ts

93.75% Statements 30/32
66.66% Branches 4/6
100% Functions 7/7
93.75% Lines 30/32

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 8841x 41x 41x 41x 41x     41x         5x 5x                 6x 5x 1x 1x               5x     5x   5x 5x   5x   5x                         5x 5x       1x 1x   1x 1x 1x   1x               1x       1x     1x 1x      
import { verify } from '@noble/secp256k1';
import { base64ToBytes, bytesToHex, utf8ToBytes } from 'micro-stacks/common';
import base64url from './base64url';
import { joseToDerES256 } from './ecdsa-sig-formatter';
import { hashSha256 } from 'micro-stacks/crypto-sha';
import type { SignedToken } from './types';
 
export class TokenVerifier {
  tokenType: string;
  rawPublicKey: string;
 
  constructor(signingAlgorithm: string, rawPublicKey: string) {
    this.tokenType = 'JWT';
    this.rawPublicKey = rawPublicKey;
  }
 
  verify(
    token: string | SignedToken,
    // @param {Boolean} strict - whether a signature s should be no more than 1/2 prime order.
    // true makes signatures compatible with libsecp256k1, false makes signatures compatible with openssl
    strict = false
  ): boolean {
    if (typeof token === 'string') {
      return this.verifyCompact(token, strict);
    } else if (typeof token === 'object') {
      return this.verifyExpanded(token, strict);
    } else E{
      return false;
    }
  }
 
  verifyCompact(token: string, strict?: boolean): boolean {
    // decompose the token into parts
    const tokenParts = token.split('.');
 
    // calculate the signing input hash
    const signingInput = tokenParts[0] + '.' + tokenParts[1];
 
    const performVerify = (signingInputHash: Uint8Array) => {
      const signature = tokenParts[2];
      // extract the signature as a DER array
      const formatted: string = joseToDerES256(signature);
      // verify the signed hash
      return verify(
        bytesToHex(base64ToBytes(formatted)),
        bytesToHex(signingInputHash),
        this.rawPublicKey,
        {
          // TODO: this should be true default
          // is not compat with legacy tokens.
          // change reflected here https://github.com/paulmillr/noble-secp256k1/releases/tag/1.4.0
          strict,
        }
      );
    };
 
    const signingInputHash = hashSha256(utf8ToBytes(signingInput));
    return performVerify(signingInputHash);
  }
 
  verifyExpanded(token: SignedToken, strict?: boolean): boolean {
    const signingInput = [token['header'].join('.'), base64url.encode(token['payload'])].join('.');
    let verified = true;
 
    const performVerify = (signingInputHash: Uint8Array) => {
      token['signature'].map((signature: string) => {
        const formatted: string = joseToDerES256(signature);
 
        const signatureVerified = verify(
          bytesToHex(base64ToBytes(formatted)),
          bytesToHex(signingInputHash),
          this.rawPublicKey,
          {
            strict,
          }
        );
        Iif (!signatureVerified) {
          verified = false;
        }
      });
      return verified;
    };
 
    const signingInputHash = hashSha256(utf8ToBytes(signingInput));
    return performVerify(signingInputHash);
  }
}