// Copyright 2018-2026 the Deno authors. MIT license.

import { assert, assertEquals } from "@std/assert";
import {
  createSign,
  createVerify,
  generateKeyPairSync,
  sign,
  verify,
} from "node:crypto";
import { Buffer } from "node:buffer";
import fixtures from "../testdata/crypto_digest_fixtures.json" with {
  type: "json",
};

const rsaPrivatePem = Buffer.from(
  await Deno.readFile(
    new URL("../testdata/rsa_private.pem", import.meta.url),
  ),
);
const rsaPrivatePkcs1Pem = Buffer.from(
  await Deno.readFile(
    new URL("../testdata/rsa_private_pkcs1.pem", import.meta.url),
  ),
);
const rsaPublicPem = Buffer.from(
  await Deno.readFile(
    new URL("../testdata/rsa_public.pem", import.meta.url),
  ),
);

const table = [
  {
    algorithms: ["sha224", "RSA-SHA224"],
    signature:
      "7ad162b288bd7f4ba9b8a31295ad4136d143a5fd11eb99a72379dc9b53e3e8b5c1b7c9dd8a3864a1f626d921e550c48056982bd8fe7e75333885311b5515de1ecbbfcc6a1dd930f422dff87bfceb7eb38882ac6b4fd9dea9efd462776775976e81b1d677f8db41f5ac8686abfa9838069125be939c59e404aa50550872d84befb8b5f6ce2dd051c62a8ba268f876b6f17a27af43b79938222e4ab8b90c4f5540d0f8b02508ef3e68279d685746956b924f00c92438b7981a3cfcb1e2a97305402d381ea62aeaa803f8707961bc3e10a258352e210772e9846ca4024e3dc0a956a50d6db1c03d2943826cc98c6f36d7bafacf1c94b6c438c7664c300a3be172b1",
  },
  {
    algorithms: ["sha256", "RSA-SHA256"],
    signature:
      "080313284d7398e1e0e27f6e44f198ceecedddc801e81af63a867d9245ad744e29018099c9ac3c27061c33cabfe27af1db38f44bac09cdcd2c4ab3b00a2a3020f68368f2239db5f911a2dbb7ea2dee322ca7d26d0c88d197482ca4aa1c29ac87b9e6c20075dc974ae71d2d76d2a5b2a15bd541033519465c3aea815cc73b0f1c3ffeedcfb93d6788416623789f86786870d23e86b982ab0df157d7a596097bd3cca3e752f3f47eff4b83754296868b52bc8ff741492dc8a401fe6dc035569e45d1fa1a71c8988d3aadce68fb1bf5c3e756c586af20c8e75c037436ff4c8389e6ce9d943ef7e2566977b84577272181fcec403077cc29e7db1166fff900b36a1d",
  },
  {
    algorithms: ["sha384", "RSA-SHA384"],
    signature:
      "2f77a5b7ac0168efd652c30ecb082075f3de30629e9c1f51b7e7e671f24b5c3a2606bb72159a217438220fc7aaba887d4b817e3f43fe0cc8f840747368df8cd65ec760c21a3f9296d01caedc80a335030e31d31ac451277fc4bcc1679c168b2c3185dfee21286514113c080af5238a61a677b03777344f476f25053108588aa6bdc02a6138c6b59a20de4d11e3d668482f17e748e75747f83c0512206283acfc64ed0ad963dddc9ec24589cfd459ee806b8e0e67b93cea16651e967762a5deef890f438ffb9db39247469289db06e2ed7fe262aa1df4ab9607e5b5219a17ddc9694283a61bf8643f58fd702f2c5d3b2d53dc7f36bb5e96461174d376950d6d19",
  },
  {
    algorithms: ["sha512", "RSA-SHA512"],
    signature:
      "072e20a433f255ab2f7e5e9ce69255d5c6d7c15a36af75c8389b9672c41abc6a9532fbd057d9d64270bb2483d3c9923f8f419fba4b59b838dcda82a1322009d245c06e2802a74febaea9cebc0b7f46f8761331c5f52ffb650245b5aefefcc604f209b44f6560fe45370cb239d236622e5f72fbb45377f08a0c733e16a8f15830897679ad4349d2e2e5e50a99796820302f4f47881ed444aede56a6d3330b71acaefc4218ae2e4a3bdfbb0c9432ffc5e5bac8c168278b2205d68a5d6905ccbb91282d519c11eccca52d42c86787de492b2a89679dce98cd14c37b0c183af8427e7a1ec86b1ed3f9b5bebf83f1ef81eb18748e69c716a0f263a8598fe627158647",
  },
];

const data = Buffer.from("some data to sign");

Deno.test({
  name:
    "crypto.Sign|sign - RSA PEM with SHA224, SHA256, SHA384, SHA512 digests",
  fn() {
    for (const testCase of table) {
      for (const algorithm of testCase.algorithms) {
        assertEquals(
          createSign(algorithm)
            .update(data)
            .sign(rsaPrivatePem, "hex"),
          testCase.signature,
        );
        assertEquals(
          sign(algorithm, data, rsaPrivatePem),
          Buffer.from(testCase.signature, "hex"),
        );
      }
    }
  },
});

Deno.test({
  name:
    "crypto.Verify|verify - RSA PEM with SHA224, SHA256, SHA384, SHA512 digests",
  fn() {
    for (const testCase of table) {
      for (const algorithm of testCase.algorithms) {
        assert(
          createVerify(algorithm).update(data).verify(
            rsaPublicPem,
            testCase.signature,
            "hex",
          ),
        );
        assert(
          verify(
            algorithm,
            data,
            rsaPublicPem,
            Buffer.from(testCase.signature, "hex"),
          ),
        );
      }
    }
  },
});

Deno.test({
  name: "crypto.createPrivateKey|sign - RSA PEM",
  fn() {
    for (const testCase of table) {
      for (const algorithm of testCase.algorithms) {
        assertEquals(
          createSign(algorithm).update(data).sign(rsaPrivatePem, "hex"),
          testCase.signature,
        );
        assertEquals(
          sign(algorithm, data, rsaPrivatePem),
          Buffer.from(testCase.signature, "hex"),
        );
      }
    }
  },
});

Deno.test({
  name: "crypto.createPrivateKey|sign - RSA PKCS1 PEM",
  fn() {
    for (const testCase of table) {
      for (const algorithm of testCase.algorithms) {
        assertEquals(
          createSign(algorithm).update(data).sign(rsaPrivatePkcs1Pem, "hex"),
          testCase.signature,
        );
        assertEquals(
          sign(algorithm, data, rsaPrivatePkcs1Pem),
          Buffer.from(testCase.signature, "hex"),
        );
      }
    }
  },
});

Deno.test({
  name: "crypto.createSign|sign - EC PRIVATE KEY",
  fn() {
    const pem = `-----BEGIN EC PRIVATE KEY-----
MDECAQEEIIThPSZ00CNW1UD5Ju9mhplv6SSs3T5objYjlx11gHW9oAoGCCqGSM49
AwEH
-----END EC PRIVATE KEY-----`;
    createSign("SHA256").update("test").sign(pem, "base64");
  },
});

Deno.test("crypto.createSign|sign - compare with node", async (t) => {
  const DATA = "Hello, world!";
  const privateKey = Deno.readTextFileSync(
    new URL(import.meta.resolve("../testdata/rsa_private.pem")),
  );
  for (const { digest, signature } of fixtures) {
    await t.step({
      name: digest,
      // TODO(lucacasonato): our md4 implementation does not have an OID, so it can't sign/verify
      ignore: digest.toLowerCase().includes("md4"),
      fn: () => {
        let actual: string | null;
        try {
          const s = createSign(digest);
          s.update(DATA);
          actual = s.sign(privateKey).toString("hex");
        } catch {
          actual = null;
        }
        assertEquals(actual, signature);
      },
    });
  }
});

Deno.test("crypto.createVerify|verify - compare with node", async (t) => {
  const DATA = "Hello, world!";
  const publicKey = Deno.readTextFileSync(
    new URL(import.meta.resolve("../testdata/rsa_public.pem")),
  );
  for (const { digest, signature } of fixtures) {
    await t.step({
      name: digest,
      // TODO(lucacasonato): our md4 implementation does not have an OID, so it can't sign/verify
      ignore: signature === null || digest.toLowerCase().includes("md4"),
      fn: () => {
        const s = createVerify(digest);
        s.update(DATA);
        s.verify(publicKey, signature!);
      },
    });
  }
});

Deno.test("crypto sign|verify dsaEncoding", () => {
  const { privateKey, publicKey } = generateKeyPairSync("ec", {
    namedCurve: "P-256",
  });

  const sign = createSign("SHA256");
  sign.write("some data to sign");
  sign.end();

  // @ts-ignore FIXME: types dont allow this
  privateKey.dsaEncoding = "ieee-p1363";
  const signature = sign.sign(privateKey, "hex");

  const verify = createVerify("SHA256");
  verify.write("some data to sign");
  verify.end();

  // @ts-ignore FIXME: types dont allow this
  publicKey.dsaEncoding = "ieee-p1363";
  assert(verify.verify(publicKey, signature, "hex"));
});

// Regression test for https://github.com/denoland/deno/issues/33291
// RSA-PSS key with non-standard PEM line width and explicit PSS parameters
Deno.test("crypto.verify - RSA-PSS key with null algorithm infers digest from key params", () => {
  const data = Buffer.from([
    0,
    2,
    21,
    48,
    212,
    249,
    4,
    188,
    181,
    150,
    232,
    200,
    126,
    175,
    24,
    20,
    29,
    97,
    3,
    194,
    77,
    180,
    245,
    147,
    214,
    205,
    145,
    176,
    225,
    201,
    20,
    230,
    224,
    176,
    28,
    169,
    180,
    174,
    157,
    147,
    51,
    161,
    23,
    133,
    155,
    19,
    18,
    168,
    182,
    203,
    81,
    8,
    135,
    8,
    87,
    18,
    118,
    12,
    92,
    23,
    251,
    154,
    229,
    229,
    40,
    151,
    243,
    129,
    231,
    13,
    16,
    38,
    78,
    38,
    253,
    234,
    72,
    118,
    216,
    237,
    182,
    196,
    51,
    201,
    123,
    29,
    187,
    186,
    15,
    238,
    255,
    237,
    240,
    202,
    103,
    222,
    228,
    240,
  ]);

  // This PEM has a 65-char base64 line (non-standard but accepted by OpenSSL/Node)
  const pubkey = `-----BEGIN PUBLIC KEY-----
MIIBUjA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAqEaMBgGCSqGSIb3DQEBC
DALBglghkgBZQMEAgKiAwIBMAOCAQ8AMIIBCgKCAQEAo3iVbsT8MIiTUmehBFL2e
r8SwXqKkPCZMi1jw4zQ2Dzwrl8IyQpQS4NSbn0bdYHEkP+SU2V7GsAnnhXdIzWaH
QsvHE44vndDIxnmaTg2R5BUZaDdi5aX6xpYP39KC5Z6XlyehCSIFLSQUIyfgCxII
JhV+aHKK5zNBAgdAVjEWao687vtbqu6rng6VbKChW1I2aIIjBCCdkQsw1uxyVcNp
KWeqHhMCF0TIC90DiBTX+r1C3ASly906BTEqeonSNguClzz7oQukbZMncnWpxSnp
f8/Dr7qOL0AVMkOYCYHk7XR5WX+2zp2LqJwtcO87Dek5QOKcAwpOFTTuI2ZpN+3n
QIDAQAB
-----END PUBLIC KEY-----`;

  const sig = Buffer.from([
    150,
    107,
    108,
    113,
    140,
    93,
    191,
    104,
    47,
    48,
    99,
    122,
    16,
    242,
    141,
    19,
    171,
    12,
    7,
    107,
    110,
    212,
    229,
    112,
    194,
    19,
    70,
    165,
    26,
    99,
    213,
    137,
    185,
    85,
    95,
    15,
    180,
    172,
    178,
    198,
    6,
    67,
    31,
    184,
    16,
    93,
    149,
    41,
    200,
    164,
    75,
    48,
    9,
    95,
    24,
    25,
    140,
    108,
    156,
    118,
    66,
    138,
    226,
    154,
    83,
    37,
    89,
    199,
    225,
    126,
    121,
    197,
    2,
    242,
    3,
    114,
    120,
    107,
    118,
    120,
    147,
    221,
    157,
    43,
    80,
    205,
    67,
    138,
    201,
    39,
    241,
    13,
    155,
    82,
    191,
    26,
    8,
    213,
    64,
    105,
    199,
    69,
    242,
    20,
    56,
    108,
    27,
    218,
    121,
    1,
    37,
    200,
    60,
    251,
    140,
    102,
    53,
    215,
    87,
    202,
    223,
    26,
    136,
    168,
    99,
    182,
    1,
    78,
    128,
    217,
    24,
    3,
    22,
    147,
    19,
    151,
    29,
    17,
    46,
    170,
    172,
    97,
    173,
    25,
    3,
    31,
    59,
    95,
    237,
    155,
    72,
    102,
    184,
    114,
    212,
    106,
    212,
    58,
    211,
    164,
    148,
    63,
    108,
    130,
    191,
    240,
    209,
    6,
    17,
    36,
    176,
    117,
    179,
    228,
    101,
    218,
    93,
    6,
    226,
    196,
    158,
    212,
    202,
    171,
    126,
    168,
    246,
    199,
    131,
    210,
    114,
    140,
    61,
    126,
    0,
    239,
    95,
    73,
    234,
    93,
    26,
    52,
    157,
    19,
    106,
    95,
    184,
    186,
    126,
    127,
    160,
    185,
    168,
    49,
    15,
    190,
    158,
    110,
    238,
    156,
    211,
    5,
    146,
    123,
    147,
    241,
    227,
    4,
    188,
    4,
    148,
    34,
    121,
    251,
    235,
    95,
    163,
    169,
    114,
    47,
    210,
    178,
    230,
    148,
    63,
    233,
    103,
    44,
    147,
    84,
    249,
    208,
    248,
    0,
    251,
    193,
  ]);

  // algorithm=null should infer SHA-384 from the RSA-PSS key parameters
  const isVerified = verify(null, data, { key: pubkey }, sig);
  assertEquals(isVerified, true);
});

// Companion to the above test: verify sign(null, ...) also infers digest from RSA-PSS key params
Deno.test("crypto.sign - RSA-PSS key with null algorithm infers digest from key params", () => {
  const { privateKey, publicKey } = generateKeyPairSync("rsa-pss", {
    modulusLength: 2048,
    hashAlgorithm: "sha384",
  });

  const data = Buffer.from("test data for RSA-PSS sign with null algorithm");

  // algorithm=null should infer SHA-384 from the RSA-PSS key parameters
  const signature = sign(null, data, privateKey);
  assert(signature.length > 0);

  // Verify the signature using the same null-algorithm path
  const isVerified = verify(null, data, publicKey, signature);
  assertEquals(isVerified, true);
});
