'use strict';

const common = require('../common');

if (!common.hasCrypto)
  common.skip('missing crypto');

const assert = require('assert');
const { hasOpenSSL } = require('../common/crypto');
const { subtle } = globalThis.crypto;

// This is only a partial test. The WebCrypto Web Platform Tests
// will provide much greater coverage.

// Test Encrypt/Decrypt RSA-OAEP w/ SHA-2
{
  const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));

  async function test() {
    const ec = new TextEncoder();
    const { publicKey, privateKey } = await subtle.generateKey({
      name: 'RSA-OAEP',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA-384',
    }, true, ['encrypt', 'decrypt']);

    const ciphertext = await subtle.encrypt({
      name: 'RSA-OAEP',
      label: ec.encode('a label')
    }, publicKey, buf);

    const plaintext = await subtle.decrypt({
      name: 'RSA-OAEP',
      label: ec.encode('a label')
    }, privateKey, ciphertext);

    assert.strictEqual(
      Buffer.from(plaintext).toString('hex'),
      Buffer.from(buf).toString('hex'));

    await assert.rejects(() => subtle.encrypt({
      name: 'RSA-OAEP',
    }, privateKey, buf), {
      name: 'InvalidAccessError',
      message: 'The requested operation is not valid for the provided key'
    });

    await assert.rejects(() => subtle.decrypt({
      name: 'RSA-OAEP',
    }, publicKey, ciphertext), {
      name: 'InvalidAccessError',
      message: 'The requested operation is not valid for the provided key'
    });
  }

  test().then(common.mustCall());
}

// Test Encrypt/Decrypt RSA-OAEP w/ SHA-3
if (!process.features.openssl_is_boringssl) {
  const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));

  async function test() {
    const ec = new TextEncoder();
    const { publicKey, privateKey } = await subtle.generateKey({
      name: 'RSA-OAEP',
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: 'SHA3-384',
    }, true, ['encrypt', 'decrypt']);

    const ciphertext = await subtle.encrypt({
      name: 'RSA-OAEP',
      label: ec.encode('a label')
    }, publicKey, buf);

    const plaintext = await subtle.decrypt({
      name: 'RSA-OAEP',
      label: ec.encode('a label')
    }, privateKey, ciphertext);

    assert.strictEqual(
      Buffer.from(plaintext).toString('hex'),
      Buffer.from(buf).toString('hex'));

    await assert.rejects(() => subtle.encrypt({
      name: 'RSA-OAEP',
    }, privateKey, buf), {
      name: 'InvalidAccessError',
      message: 'The requested operation is not valid for the provided key'
    });

    await assert.rejects(() => subtle.decrypt({
      name: 'RSA-OAEP',
    }, publicKey, ciphertext), {
      name: 'InvalidAccessError',
      message: 'The requested operation is not valid for the provided key'
    });
  }

  test().then(common.mustCall());
}

// Test Encrypt/Decrypt AES-CTR
{
  const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));
  const counter = globalThis.crypto.getRandomValues(new Uint8Array(16));

  async function test() {
    const key = await subtle.generateKey({
      name: 'AES-CTR',
      length: 256
    }, true, ['encrypt', 'decrypt']);

    const ciphertext = await subtle.encrypt(
      { name: 'AES-CTR', counter, length: 64 }, key, buf,
    );

    const plaintext = await subtle.decrypt(
      { name: 'AES-CTR', counter, length: 64 }, key, ciphertext,
    );

    assert.strictEqual(
      Buffer.from(plaintext).toString('hex'),
      Buffer.from(buf).toString('hex'));
  }

  test().then(common.mustCall());
}

// Test Encrypt/Decrypt AES-CBC
{
  const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(16));

  async function test() {
    const key = await subtle.generateKey({
      name: 'AES-CBC',
      length: 256
    }, true, ['encrypt', 'decrypt']);

    const ciphertext = await subtle.encrypt(
      { name: 'AES-CBC', iv }, key, buf,
    );

    const plaintext = await subtle.decrypt(
      { name: 'AES-CBC', iv }, key, ciphertext,
    );

    assert.strictEqual(
      Buffer.from(plaintext).toString('hex'),
      Buffer.from(buf).toString('hex'));
  }

  test().then(common.mustCall());
}

// Test Encrypt/Decrypt AES-GCM
{
  const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));

  async function test() {
    const key = await subtle.generateKey({
      name: 'AES-GCM',
      length: 256
    }, true, ['encrypt', 'decrypt']);

    const ciphertext = await subtle.encrypt(
      { name: 'AES-GCM', iv }, key, buf,
    );

    const plaintext = await subtle.decrypt(
      { name: 'AES-GCM', iv }, key, ciphertext,
    );

    assert.strictEqual(
      Buffer.from(plaintext).toString('hex'),
      Buffer.from(buf).toString('hex'));
  }

  test().then(common.mustCall());
}

// Test Encrypt/Decrypt AES-OCB
if (hasOpenSSL(3)) {
  const buf = globalThis.crypto.getRandomValues(new Uint8Array(50));
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12));

  async function test() {
    const key = await subtle.generateKey({
      name: 'AES-OCB',
      length: 256
    }, true, ['encrypt', 'decrypt']);

    const ciphertext = await subtle.encrypt(
      { name: 'AES-OCB', iv }, key, buf,
    );

    const plaintext = await subtle.decrypt(
      { name: 'AES-OCB', iv }, key, ciphertext,
    );

    assert.strictEqual(
      Buffer.from(plaintext).toString('hex'),
      Buffer.from(buf).toString('hex'));
  }

  test().then(common.mustCall());
} else {
  common.printSkipMessage('Skipping unsupported AES-OCB test cases');
}
