export function getToken()

in src/governance/ccf-app/js/src/endpoints/token.ts [19:153]


export function getToken(
  request: ccfapp.Request<GetTokenRequest>
): ccfapp.Response<GetTokenResponse> | ccfapp.Response<ErrorResponse> {
  const contractId = request.params.contractId;
  const body = request.body.json();
  if (!body.attestation) {
    return {
      statusCode: 400,
      body: new ErrorResponse(
        "AttestationMissing",
        "Attestation payload must be supplied."
      )
    };
  }

  if (!body.encrypt) {
    return {
      statusCode: 400,
      body: new ErrorResponse(
        "EncryptionMissing",
        "Encrypt payload must be supplied."
      )
    };
  }

  // First validate attestation report.
  let snpAttestationResult: SnpAttestationResult;
  try {
    snpAttestationResult = verifySnpAttestation(contractId, body.attestation);
  } catch (e) {
    return {
      statusCode: 400,
      body: new ErrorResponse("VerifySnpAttestationFailed", e.message)
    };
  }

  //  Then validate the report data value.
  try {
    verifyReportData(snpAttestationResult, body.encrypt.publicKey);
  } catch (e) {
    return {
      statusCode: 400,
      body: new ErrorResponse("ReportDataMismatch", e.message)
    };
  }

  // Attestation report and report data values are verified.
  // Now generate the token and wrap it with the encryption key before returning it.
  const parsedQuery = parseRequestQuery(request);
  let params: GetTokenParams;
  try {
    params = extractParams(parsedQuery);
  } catch (e) {
    return {
      statusCode: 400,
      body: new ErrorResponse("InvalidInput", e.message)
    };
  }

  const { nbf, exp, iat, jti, sub, tid, aud } = params;
  const signingKey = getSigningKey();
  if (!signingKey) {
    return {
      statusCode: 405,
      body: new ErrorResponse(
        "SigningKeyNotAvailable",
        "Propose enable_oidc_issuer and generate signing key before attempting to fetch it."
      )
    };
  }

  const iss = getTenantIdIssuerUrl(tid) ?? getGovIssuerUrl();
  if (!iss) {
    return {
      statusCode: 405,
      body: new ErrorResponse(
        "IssuerUrlNotSet",
        `Issuer url has not been configured for tenant ${tid}. Propose set_oidc_issuer_url or set the issuer at the tenant level.`
      )
    };
  }

  // https://www.scottbrady91.com/jose/jwts-which-signing-algorithm-should-i-use
  // https://learn.microsoft.com/en-us/entra/identity-platform/certificate-credentials#assertion-format
  const algHeader: string = "PS256";
  const header = {
    alg: algHeader,
    typ: "JWT",
    kid: signingKey.kid
  };
  const claims = {
    aud: aud,
    exp: exp,
    iss: iss,
    jti: jti,
    nbf: nbf,
    sub: sub,
    iat: iat
  };

  // https://jwt.io/introduction
  const headerBase64 = Base64.encodeURL(JSON.stringify(header));
  const claimsBase64 = Base64.encodeURL(JSON.stringify(claims));
  const toSign = headerBase64 + "." + claimsBase64;
  const algorithmName: AlgorithmName = "RSA-PSS";
  const algorithm: SigningAlgorithm = {
    name: algorithmName,
    hash: "SHA-256",
    saltLength: 32
  };
  const signature: ArrayBuffer = ccf.crypto.sign(
    algorithm,
    signingKey.privateKey,
    ccf.strToBuf(toSign)
  );
  const urlSafe: boolean = true;
  const signatureBase64 = Base64.fromUint8Array(
    new Uint8Array(signature),
    urlSafe
  );
  const token = headerBase64 + "." + claimsBase64 + "." + signatureBase64;

  // Wrap the token before returning it.
  const wrapAlgo = {
    name: "RSA-OAEP-AES-KWP",
    aesKeySize: 256
  } as RsaOaepAesKwpParams;
  const wrapped: ArrayBuffer = ccf.crypto.wrapKey(
    ccf.strToBuf(token),
    ccf.strToBuf(Base64.decode(body.encrypt.publicKey)),
    wrapAlgo
  );
  const wrappedBase64 = Base64.fromUint8Array(new Uint8Array(wrapped));
  return { body: { value: wrappedBase64 } };
}