# rebuild:
# rm -f src/liballinone.a && touch CryptX.xs && make && perl -Mblib t/wycheproof.t

use strict;
use warnings;

use Test::More;

plan skip_all => "No JSON::* module installed" unless eval { require JSON::PP } || eval { require JSON::XS } || eval { require Cpanel::JSON::XS };
plan tests => 1298;

use CryptX;
use Crypt::Misc 'read_rawfile';
use Crypt::Digest 'digest_data';

if (1) {
  use Crypt::AuthEnc::GCM qw(gcm_encrypt_authenticate gcm_decrypt_verify);

  my $tests = CryptX::_decode_json read_rawfile 't/wycheproof/aes_gcm_test.json';
  for my $g (@{$tests->{testGroups}}) {
    my $type    = $g->{type};
    for my $t (@{$g->{tests}}) {
      my $tcId    = $t->{tcId};           # 1
      my $comment = $t->{comment};        # ""
      my $result  = $t->{result};         # "valid"
      my $aad     = pack "H*", $t->{aad}; # "6578616d706c65"
      my $ct      = pack "H*", $t->{ct};  # "5d349ead175ef6b1def6fd"
      my $iv      = pack "H*", $t->{iv};  # "752abad3e0afb5f434dc4310"
      my $key     = pack "H*", $t->{key}; # "ee8e1ed9ff2540ae8f2ba9f50bc2f27c"
      my $msg     = pack "H*", $t->{msg}; # "48656c6c6f20776f726c64"
      my $tag     = pack "H*", $t->{tag}; # "4fbcdeb7e4793f4a1d7e4faa70100af1"
      # do the test
      my ($ct2, $tag2) = eval { gcm_encrypt_authenticate('AES', $key, $iv, $aad, $msg) };
      my $pt2 = eval { gcm_decrypt_verify('AES', $key, $iv, $aad, $ct, $tag) };
      my $testname = "type=$type tcId=$tcId comment='$comment' expected-result=$result";
      if ($result eq 'valid') {
        is(unpack("H*", $ct2),  $t->{ct},  "$testname CT-v");
        is(unpack("H*", $tag2), $t->{tag}, "$testname TAG-v");
        is(unpack("H*", $pt2),  $t->{msg}, "$testname PT-v");
      }
      elsif ($result eq 'invalid') {
        #isnt(unpack("H*", $ct2),  $t->{ct},  "$testname CT-i");
        #isnt(unpack("H*", $tag2), $t->{tag}, "$testname TAG-i");
        is($pt2, undef, "$testname PT-i");
      }
      else {
        ok(0, "UNEXPECTED result=$result");
      }
    }
  }
}

if (1) {
  use Crypt::PK::RSA;

  my $tests = CryptX::_decode_json read_rawfile 't/wycheproof/rsa_signature_test.json';
  for my $g (@{$tests->{testGroups}}) {
    my $type   = $g->{type};
    my $keyDer = pack "H*", $g->{keyDer};
    my $keyPem = $g->{keyPem};
    my $sha    = $g->{sha};
    $sha =~ s/-//g; # SHA-1 >> SHA1
    ok(Crypt::PK::RSA->new( \$keyDer ), "Crypt::PK::RSA->new + DER type: $type/$sha");
    ok(Crypt::PK::RSA->new( \$keyPem ), "Crypt::PK::RSA->new + PEM type: $type/$sha");
    for my $t (@{$g->{tests}}) {
      my $tcId    = $t->{tcId};
      my $comment = $t->{comment};
      my $result  = $t->{result};
      my $message = pack "H*", $t->{message};
      my $sig     = pack "H*", $t->{sig};
      # do the test
      my $testname = "type=$type/$sha tcId=$tcId comment='$comment' expected-result=$result";
      my $pk = Crypt::PK::RSA->new( \$keyPem );
      my $valid = $pk->verify_message($sig, $message, $sha,"v1.5");
      if ($result eq 'valid' || $result eq 'acceptable') {
        ok($valid, $testname);
      }
      elsif ($result eq 'invalid') {
        ok(!$valid, $testname);
      }
      else {
        ok(0, "UNEXPECTED result=$result");
      }
    }
  }
}

if (1) {
  use Crypt::PK::DSA;

  my $tests = CryptX::_decode_json read_rawfile 't/wycheproof/dsa_test.json';
  for my $g (@{$tests->{testGroups}}) {
    my $type   = $g->{type};   # "DSAVer"
    my $keyDer = pack "H*", $g->{keyDer};
    my $keyPem = $g->{keyPem};
    my $sha    = $g->{sha};    # "SHA-1"
    $sha =~ s/-//g; # SHA-1 >> SHA1
    ok(Crypt::PK::DSA->new( \$keyDer ), "Crypt::PK::DSA->new + DER type=$type/$sha");
    ok(Crypt::PK::DSA->new( \$keyPem ), "Crypt::PK::DSA->new + PEM type=$type/$sha");
    for my $t (@{$g->{tests}}) {
      my $tcId    = $t->{tcId};
      my $comment = $t->{comment};
      my $result  = $t->{result};
      my $message = pack "H*", $t->{message};
      my $sig     = pack "H*", $t->{sig};
      # skip unsupported tests:
      next if $tcId==12 && $result eq 'acceptable' && $comment eq "Legacy:ASN encoding of s misses leading 0";
      next if $tcId==13 && $result eq 'acceptable' && $comment eq "BER:long form encoding of length";
      next if $tcId==14 && $result eq 'acceptable' && $comment eq "BER:long form encoding of length";
      next if $tcId==15 && $result eq 'acceptable' && $comment eq "BER:long form encoding of length";
      next if $tcId==16 && $result eq 'acceptable' && $comment eq "BER:length contains leading 0";
      next if $tcId==17 && $result eq 'acceptable' && $comment eq "BER:length contains leading 0";
      next if $tcId==18 && $result eq 'acceptable' && $comment eq "BER:length contains leading 0";
      next if $tcId==19 && $result eq 'acceptable' && $comment eq "BER:indefinite length";
      next if $tcId==20 && $result eq 'acceptable' && $comment eq "BER:prepending 0's to integer";
      next if $tcId==21 && $result eq 'acceptable' && $comment eq "BER:prepending 0's to integer";
      # do the test
      my $testname = "type=$type/$sha tcId=$tcId comment='$comment' expected-result=$result";
      my $pk = Crypt::PK::DSA->new( \$keyPem );
      my $hash = digest_data($sha, $message);
      my $valid_h = $pk->verify_hash($sig, $hash);
      my $valid = $pk->verify_message($sig, $message, $sha);
      if ($result eq 'valid' || $result eq 'acceptable') {
        ok($valid, $testname);
      }
      elsif ($result eq 'invalid') {
        ok(!$valid, $testname);
      }
      else {
        ok(0, "UNEXPECTED result=$result");
      }
    }
  }
}

if (1) {
  use Crypt::PK::ECC;

  my $tests = CryptX::_decode_json read_rawfile 't/wycheproof/ecdsa_test.json';
  for my $g (@{$tests->{testGroups}}) {
    my $type   = $g->{type};
    my $keyDer = pack "H*", $g->{keyDer};
    my $keyPem = $g->{keyPem};
    my $sha    = $g->{sha};
    $sha =~ s/-//g; # SHA-1 >> SHA1
    ok(Crypt::PK::ECC->new( \$keyDer ), "Crypt::PK::ECC->new + DER type=$type/$sha");
    ok(Crypt::PK::ECC->new( \$keyPem ), "Crypt::PK::ECC->new + PEM type=$type/$sha");
    for my $t (@{$g->{tests}}) {
      my $tcId    = $t->{tcId};
      my $comment = $t->{comment};
      my $result  = $t->{result};
      my $message = pack "H*", $t->{message};
      my $sig     = pack "H*", $t->{sig};
      # skip unsupported tests:
      next if $tcId==9  && $result eq 'acceptable' && $comment eq "BER:long form encoding of length";
      next if $tcId==10 && $result eq 'acceptable' && $comment eq "BER:long form encoding of length";
      next if $tcId==12 && $result eq 'acceptable' && $comment eq "BER:length contains leading 0";
      next if $tcId==13 && $result eq 'acceptable' && $comment eq "BER:length contains leading 0";
      next if $tcId==14 && $result eq 'acceptable' && $comment eq "BER:indefinite length";
      next if $tcId==15 && $result eq 'acceptable' && $comment eq "BER:prepending 0's to integer";
      next if $tcId==16 && $result eq 'acceptable' && $comment eq "BER:prepending 0's to integer";
      # do the test
      my $testname = "type=$type/$sha tcId=$tcId comment='$comment' expected-result=$result";
      my $pk = Crypt::PK::ECC->new( \$keyPem );
      my $valid = $pk->verify_message($sig, $message, $sha);
      if ($result eq 'valid' || $result eq 'acceptable') {
        ok($valid, "$testname verify_message=$valid");
      }
      elsif ($result eq 'invalid') {
        ok(!$valid, "$testname verify_message=$valid");
      }
      else {
        ok(0, "UNEXPECTED result=$result");
      }
    }
  }
}

if (1) {
  use Crypt::PK::ECC;

  my $tests = CryptX::_decode_json read_rawfile 't/wycheproof/ecdsa_webcrypto_test.json';
  for my $g (@{$tests->{testGroups}}) {
    my $type   = $g->{type};
    my $keyDer = pack "H*", $g->{keyDer};
    my $keyPem = $g->{keyPem};
    my $sha    = $g->{sha};
    my $jwk    = $g->{jwk};
    $sha =~ s/-//g; # SHA-1 >> SHA1
    ok(Crypt::PK::ECC->new( \$keyDer ), "Crypt::PK::ECC->new + DER type=$type/$sha");
    ok(Crypt::PK::ECC->new( \$keyPem ), "Crypt::PK::ECC->new + PEM type=$type/$sha");
    ok(Crypt::PK::ECC->new( $jwk ),     "Crypt::PK::ECC->new + JWK type=$type/$sha");
    for my $t (@{$g->{tests}}) {
      my $tcId    = $t->{tcId};
      my $comment = $t->{comment};
      my $result  = $t->{result};
      my $message = pack "H*", $t->{message};
      my $sig     = pack "H*", $t->{sig};
      # do the test
      my $testname = "type=$type/$sha tcId=$tcId comment='$comment' expected-result=$result";
      my $pk = Crypt::PK::ECC->new( \$keyPem );
      my $valid = $pk->verify_message_rfc7518($sig, $message, $sha);
      if ($result eq 'valid' || $result eq 'acceptable') {
        ok($valid, "$testname verify_message=$valid");
      }
      elsif ($result eq 'invalid') {
        ok(!$valid, "$testname verify_message=$valid");
      }
      else {
        ok(0, "UNEXPECTED result=$result");
      }
    }
  }
}

if (1) {
  use Crypt::PK::ECC;

  my $tests = CryptX::_decode_json read_rawfile 't/wycheproof/ecdh_webcrypto_test.json';
  for my $g (@{$tests->{testGroups}}) {
    my $type   = $g->{type};
    for my $t (@{$g->{tests}}) {
      my $tcId    = $t->{tcId};
      my $comment = $t->{comment};
      my $name    = $t->{name};
      my $result  = $t->{result};
      my $shared  = pack "H*", $t->{shared};
      # do the test
      my $testname = "type=$type/$name tcId=$tcId comment='$comment' expected-result=$result";
      my $pub = Crypt::PK::ECC->new( $t->{public} );
      my $pri = Crypt::PK::ECC->new( $t->{private} );
      my $shared_hex = unpack "H*", $pri->shared_secret($pub);
      if ($result eq 'valid' || $result eq 'acceptable') {
        is($shared_hex, $t->{shared}, $testname);
      }
      elsif ($result eq 'invalid') {
        isnt($shared_hex, $t->{shared}, $testname);
      }
      else {
        ok(0, "UNEXPECTED result=$result");
      }
    }
  }
}
