// Loaded from https://deno.land/x/djwt@v1.9/_signature.ts


import type { Algorithm } from "./_algorithm.ts";
import {
  base64url,
  convertHexToUint8Array,
  HmacSha256,
  HmacSha512,
  RSA,
} from "./_depts.ts";

function assertNever(alg: never, message: string): never {
  throw new RangeError(message);
}

export function convertHexToBase64url(input: string): string {
  return base64url.encode(convertHexToUint8Array(input));
}

/**
 * Do a constant time string comparison. Always compare the complete strings
 * against each other to get a constant time. This method does not short-cut
 * if the two string's length differs.
 * CREDIT: https://github.com/Bruce17/safe-compare
 */
function safeCompare(a: string, b: string) {
  const strA = String(a);
  const lenA = strA.length;
  let strB = String(b);
  let result = 0;

  if (lenA !== strB.length) {
    strB = strA;
    result = 1;
  }

  for (var i = 0; i < lenA; i++) {
    result |= (strA.charCodeAt(i) ^ strB.charCodeAt(i));
  }

  return result === 0;
}

async function encrypt(
  algorithm: Algorithm,
  key: string,
  message: string,
): Promise<string> {
  switch (algorithm) {
    case "none":
      return "";
    case "HS256":
      return new HmacSha256(key).update(message).toString();
    case "HS512":
      return new HmacSha512(key).update(message).toString();
    case "RS256":
      return (
        await new RSA(RSA.parseKey(key)).sign(message, { hash: "sha256" })
      ).hex();
    default:
      assertNever(
        algorithm,
        "no matching crypto algorithm in the header: " + algorithm,
      );
  }
}

export async function create(
  algorithm: Algorithm,
  key: string,
  input: string,
): Promise<string> {
  return convertHexToBase64url(await encrypt(algorithm, key, input));
}

export async function verify({
  signature,
  key,
  algorithm,
  signingInput,
}: {
  signature: string;
  key: string;
  algorithm: Algorithm;
  signingInput: string;
}): Promise<boolean> {
  switch (algorithm) {
    case "none":
    case "HS256":
    case "HS512": {
      return safeCompare(
        signature,
        (await encrypt(algorithm, key, signingInput)),
      );
    }
    case "RS256": {
      return await new RSA(RSA.parseKey(key)).verify(
        convertHexToUint8Array(signature),
        signingInput,
        { hash: "sha256" },
      );
    }
    default:
      assertNever(
        algorithm,
        "no matching crypto algorithm in the header: " + algorithm,
      );
  }
}