diff --git a/tests/core/crypto/wycheproof/aead.odin b/tests/core/crypto/wycheproof/aead.odin new file mode 100644 index 000000000..6c2b61d72 --- /dev/null +++ b/tests/core/crypto/wycheproof/aead.odin @@ -0,0 +1,514 @@ +package test_wycheproof + +import "core:encoding/hex" +import "core:log" +import "core:mem" +import "core:os" +import "core:slice" +import "core:testing" + +import chacha_simd128 "core:crypto/_chacha20/simd128" +import chacha_simd256 "core:crypto/_chacha20/simd256" +import "core:crypto/aegis" +import "core:crypto/aes" +import "core:crypto/chacha20" +import "core:crypto/chacha20poly1305" + +import "../common" + +supported_aegis_impls :: proc() -> [dynamic]aes.Implementation { + impls := make([dynamic]aes.Implementation, 0, 2, context.temp_allocator) + append(&impls, aes.Implementation.Portable) + if aegis.is_hardware_accelerated() { + append(&impls, aes.Implementation.Hardware) + } + + return impls +} + +@(test) +test_aead_aegis :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + files := []string { + "aegis128L_test.json", + "aegis256_test.json", + } + + log.debug("aead/aegis: starting") + + for f in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Aead_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + for impl in supported_aegis_impls() { + testing.expectf(t, test_aead_aegis_impl(&test_vectors, impl), "impl {} failed", impl) + } + } +} + +test_aead_aegis_impl :: proc( + test_vectors: ^Test_Vectors(Aead_Test_Group), + impl: aes.Implementation, +) -> bool { + log.debug("aead/aegis/%v: starting", impl) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "aead/aegis/%v/%d: %s: %+v", + impl, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("aead/aegis/%v/%d: %+v", + impl, + test_vector.tc_id, + test_vector.flags, + ) + } + + key := common.hexbytes_decode(test_vector.key) + iv := common.hexbytes_decode(test_vector.iv) + aad := common.hexbytes_decode(test_vector.aad) + msg := common.hexbytes_decode(test_vector.msg) + ct := common.hexbytes_decode(test_vector.ct) + tag := common.hexbytes_decode(test_vector.tag) + + if len(iv) == 0 { + log.infof( + "aead/aegis/%v/%d: skipped, invalid IVs panic", + impl, + test_vector.tc_id, + ) + num_skipped += 1 + continue + } + + ctx: aegis.Context + aegis.init(&ctx, key, impl) + + if result_is_valid(test_vector.result) { + ct_ := make([]byte, len(ct)) + tag_ := make([]byte, len(tag)) + aegis.seal(&ctx, ct_, tag_, iv, aad, msg) + + ok := common.hexbytes_compare(test_vector.ct, ct_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(ct_)) + log.errorf( + "aead/aegis/%v/%d: ciphertext: expected %s actual %s", + impl, + test_vector.tc_id, + test_vector.ct, + x, + ) + num_failed += 1 + continue + } + + ok = common.hexbytes_compare(test_vector.tag, tag_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(tag_)) + log.errorf( + "aead/aegis/%v/%d: tag: expected %s actual %s", + impl, + test_vector.tc_id, + test_vector.tag, + x, + ) + num_failed += 1 + continue + } + } + + msg_ := make([]byte, len(msg)) + ok := aegis.open(&ctx, msg_, iv, aad, ct, tag) + if !result_check(test_vector.result, ok) { + log.errorf("aead/aegis/%v/%d: decrypt failed", impl, test_vector.tc_id) + num_failed += 1 + continue + } + + if ok && !common.hexbytes_compare(test_vector.msg, msg_) { + x := transmute(string)(hex.encode(msg_)) + log.errorf( + "aead/aegis/%v/%d: decrypt msg: expected %s actual %s", + impl, + test_vector.tc_id, + test_vector.msg, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "aead/aegis: ran %d, passed %d, failed %d, skipped %d", + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +supported_aes_impls :: proc() -> [dynamic]aes.Implementation { + impls := make([dynamic]aes.Implementation, 0, 2) + append(&impls, aes.Implementation.Portable) + if aes.is_hardware_accelerated() { + append(&impls, aes.Implementation.Hardware) + } + + return impls +} + +@(test) +test_aead_aes_gcm :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + fn, _ := os.join_path([]string{BASE_PATH, "aes_gcm_test.json"}, context.allocator) + + log.debug("aead/aes-gcm: starting") + + test_vectors: Test_Vectors(Aead_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", fn) { + return + } + + for impl in supported_aes_impls() { + testing.expectf(t, test_aead_aes_gcm_impl(&test_vectors, impl), "impl {} failed", impl) + } +} + +test_aead_aes_gcm_impl :: proc( + test_vectors: ^Test_Vectors(Aead_Test_Group), + impl: aes.Implementation, +) -> bool { + log.debug("aead/aes-gcm/%v: starting", impl) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "aead/aes-gcm/%v/%d: %s: %+v", + impl, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("aead/aes-gcm/%v/%d: %+v", + impl, + test_vector.tc_id, + test_vector.flags, + ) + } + + key := common.hexbytes_decode(test_vector.key) + iv := common.hexbytes_decode(test_vector.iv) + aad := common.hexbytes_decode(test_vector.aad) + msg := common.hexbytes_decode(test_vector.msg) + ct := common.hexbytes_decode(test_vector.ct) + tag := common.hexbytes_decode(test_vector.tag) + + if len(iv) == 0 { + log.infof( + "aead/aes-gcm/%v/%d: skipped, invalid IVs panic", + impl, + test_vector.tc_id, + ) + num_skipped += 1 + continue + } + + ctx: aes.Context_GCM + aes.init_gcm(&ctx, key, impl) + + if result_is_valid(test_vector.result) { + ct_ := make([]byte, len(ct)) + tag_ := make([]byte, len(tag)) + aes.seal_gcm(&ctx, ct_, tag_, iv, aad, msg) + + ok := common.hexbytes_compare(test_vector.ct, ct_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(ct_)) + log.errorf( + "aead/aes-gcm/%v/%d: ciphertext: expected %s actual %s", + impl, + test_vector.tc_id, + test_vector.ct, + x, + ) + num_failed += 1 + continue + } + + ok = common.hexbytes_compare(test_vector.tag, tag_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(tag_)) + log.errorf( + "aead/aes-gcm/%v/%d: tag: expected %s actual %s", + impl, + test_vector.tc_id, + test_vector.tag, + x, + ) + num_failed += 1 + continue + } + } + + msg_ := make([]byte, len(msg)) + ok := aes.open_gcm(&ctx, msg_, iv, aad, ct, tag) + if !result_check(test_vector.result, ok) { + log.errorf("aead/aes-gcm/%v/%d: decrypt failed", impl, test_vector.tc_id) + num_failed += 1 + continue + } + + if ok && !common.hexbytes_compare(test_vector.msg, msg_) { + x := transmute(string)(hex.encode(msg_)) + log.errorf( + "aead/aes-gcm/%v/%d: decrypt msg: expected %s actual %s", + impl, + test_vector.tc_id, + test_vector.msg, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "aead/aes-gcm: ran %d, passed %d, failed %d, skipped %d", + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +supported_chacha_impls :: proc() -> [dynamic]chacha20.Implementation { + impls := make([dynamic]chacha20.Implementation, 0, 3) + append(&impls, chacha20.Implementation.Portable) + if chacha_simd128.is_performant() { + append(&impls, chacha20.Implementation.Simd128) + } + if chacha_simd256.is_performant() { + append(&impls, chacha20.Implementation.Simd256) + } + + return impls +} + +@(test) +test_aead_chacha20_poly1305 :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + files := []string { + "chacha20_poly1305_test.json", + "xchacha20_poly1305_test.json", + } + + log.debug("aead/(x)chacha20poly1305: starting") + + for f, i in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Aead_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + for impl in supported_chacha_impls() { + testing.expectf(t, test_aead_chacha20_poly1305_impl(&test_vectors, i == 1, impl), "impl {} failed", impl) + } + } +} + +test_aead_chacha20_poly1305_impl :: proc( + test_vectors: ^Test_Vectors(Aead_Test_Group), + is_xchacha: bool, + impl: chacha20.Implementation, +) -> bool { + FLAG_INVALID_NONCE_SIZE :: "InvalidNonceSize" + + alg_str := is_xchacha ? "xchacha20poly1305" : "chacha20poly1305" + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "aead/%s/%v/%d: %s: %+v", + alg_str, + impl, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("aead/%s/%v/%d: %+v", + alg_str, + impl, + test_vector.tc_id, + test_vector.flags, + ) + } + + key := common.hexbytes_decode(test_vector.key) + iv := common.hexbytes_decode(test_vector.iv) + aad := common.hexbytes_decode(test_vector.aad) + msg := common.hexbytes_decode(test_vector.msg) + ct := common.hexbytes_decode(test_vector.ct) + tag := common.hexbytes_decode(test_vector.tag) + + if slice.contains(test_vector.flags, FLAG_INVALID_NONCE_SIZE) { + log.infof( + "aead/%s/%v/%d: skipped, invalid nonces panic", + alg_str, + impl, + test_vector.tc_id, + ) + num_skipped += 1 + continue + } + + ctx: chacha20poly1305.Context + switch is_xchacha { + case true: + chacha20poly1305.init_xchacha(&ctx, key, impl) + case false: + chacha20poly1305.init(&ctx, key, impl) + } + + if result_is_valid(test_vector.result) { + ct_ := make([]byte, len(ct)) + tag_ := make([]byte, len(tag)) + chacha20poly1305.seal(&ctx, ct_, tag_, iv, aad, msg) + + ok := common.hexbytes_compare(test_vector.ct, ct_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(ct_)) + log.errorf( + "aead/%s/%v/%d: ciphertext: expected %s actual %s", + alg_str, + impl, + test_vector.tc_id, + test_vector.ct, + x, + ) + num_failed += 1 + continue + } + + ok = common.hexbytes_compare(test_vector.tag, tag_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(tag_)) + log.errorf( + "aead/%s/%v/%d: tag: expected %s actual %s", + alg_str, + impl, + test_vector.tc_id, + test_vector.tag, + x, + ) + num_failed += 1 + continue + } + } + + msg_ := make([]byte, len(msg)) + ok := chacha20poly1305.open(&ctx, msg_, iv, aad, ct, tag) + if !result_check(test_vector.result, ok) { + log.errorf("aead/%s/%v/%d: decrypt failed", + alg_str, + impl, + test_vector.tc_id, + ) + num_failed += 1 + continue + } + + if ok && !common.hexbytes_compare(test_vector.msg, msg_) { + x := transmute(string)(hex.encode(msg_)) + log.errorf( + "aead/%s/%v/%d: decrypt msg: expected %s actual %s", + alg_str, + impl, + test_vector.tc_id, + test_vector.msg, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "aead/%s/%v: ran %d, passed %d, failed %d, skipped %d", + alg_str, + impl, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} diff --git a/tests/core/crypto/wycheproof/ecc.odin b/tests/core/crypto/wycheproof/ecc.odin new file mode 100644 index 000000000..53b63cc08 --- /dev/null +++ b/tests/core/crypto/wycheproof/ecc.odin @@ -0,0 +1,427 @@ +package test_wycheproof + +import "core:encoding/hex" +import "core:log" +import "core:mem" +import "core:os" +import "core:slice" +import "core:strings" +import "core:testing" + +import "core:crypto/hash" +import "core:crypto/ecdh" +import "core:crypto/ecdsa" +import "core:crypto/ed25519" + +import "../common" + +@(test) +test_eddsa_ed25519 :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + fn, _ := os.join_path([]string{BASE_PATH, "ed25519_test.json"}, context.allocator) + + log.debug("eddsa/ed25519: starting") + + test_vectors: Test_Vectors(Eddsa_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", fn) { + return + } + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group, i in test_vectors.test_groups { + mem.free_all() // Probably don't need this, but be safe. + pk_bytes := common.hexbytes_decode(test_group.public_key.pk) + + pk: ed25519.Public_Key + pk_ok := ed25519.public_key_set_bytes(&pk, pk_bytes) + if !testing.expectf(t, pk_ok, "eddsa/ed25519/%d: invalid public key: %s", i, test_group.public_key.pk) { + num_failed += len(test_group.tests) + continue + } + + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "eddsa/ed25519/%d: %s: %+v", + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("eddsa/ed25519/%d: %+v", test_vector.tc_id, test_vector.flags) + } + + msg := common.hexbytes_decode(test_vector.msg) + sig := common.hexbytes_decode(test_vector.sig) + + verify_ok := ed25519.verify(&pk, msg, sig) + if !testing.expectf( + t, + result_check(test_vector.result, verify_ok), + "eddsa/ed25519/%d: verify failed: expected %s actual %v", + test_vector.tc_id, + test_vector.result, + verify_ok, + ) { + num_failed += 1 + continue + } + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "eddsa/ed25519: ran %d, passed %d, failed %d, skipped %d", + num_ran, + num_passed, + num_failed, + num_skipped, + ) +} + +@(test) +test_ecdsa :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("ecdsa: starting") + + files := []string { + "ecdsa_secp256r1_sha256_test.json", + "ecdsa_secp256r1_sha512_test.json", + "ecdsa_secp384r1_sha384_test.json", + } + + for f in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Ecdsa_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_ecdsa_impl(t, &test_vectors), "ecdsa failed") + } +} + +test_ecdsa_impl :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Ecdsa_Test_Group)) -> bool { + curve_str := test_vectors.test_groups[0].public_key.curve + hash_str := test_vectors.test_groups[0].sha + + curve_alg: ecdsa.Curve + switch curve_str { + case "secp256r1": + curve_alg = .SECP256R1 + case "secp384r1": + curve_alg = .SECP384R1 + case: + log.errorf("ecdsa: unsupported curve: %s", curve_str) + } + + hash_alg: hash.Algorithm + switch hash_str { + case "SHA-256": + hash_alg = .SHA256 + case "SHA-384": + hash_alg = .SHA384 + case "SHA-512": + hash_alg = .SHA512 + case: + log.errorf("ecdsa: unsupported hash: %s", hash_str) + } + + log.debugf("ecdsa/%s/%s: starting", curve_str, hash_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group, i in test_vectors.test_groups { + pk_bytes := common.hexbytes_decode(test_group.public_key.uncompressed) + + pk: ecdsa.Public_Key + pk_ok := ecdsa.public_key_set_bytes(&pk, curve_alg, pk_bytes) + if !testing.expectf(t, pk_ok, "ecdsa/%s/%s/%d: invalid public key: %s", curve_str, hash_str, i, test_group.public_key.uncompressed) { + num_failed += len(test_group.tests) + continue + } + + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "ecda/%s/%s/%d: %s: %+v", + curve_str, + hash_str, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("ecdsa/%s/%s/%d: %+v", curve_str, hash_str, test_vector.tc_id, test_vector.flags) + } + + msg := common.hexbytes_decode(test_vector.msg) + sig := common.hexbytes_decode(test_vector.sig) + + verify_ok := ecdsa.verify_asn1(&pk, hash_alg, msg, sig) + if !testing.expectf( + t, + result_check(test_vector.result, verify_ok), + "ecdsa/%s/%s/%d: verify failed: expected %s actual %v", + curve_str, + hash_str, + test_vector.tc_id, + test_vector.result, + verify_ok, + ) { + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "ecdsa/%s/%s: ran %d, passed %d, failed %d, skipped %d", + curve_str, + hash_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +@(test) +test_ecdh :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + PREFIX_TEST_ECDH :: "ecdh_" + SUFFIX_TEST_ECPOINT :: "_ecpoint" + + files := []string { + "ecdh_secp256r1_ecpoint_test.json", + "ecdh_secp384r1_ecpoint_test.json", + "x25519_test.json", + "x448_test.json", + } + + log.debug("ecdh: starting") + + for f in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Ecdh_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + alg_str := strings.trim_suffix(f, SUFFIX_TEST_JSON) + alg_str = strings.trim_suffix(alg_str, SUFFIX_TEST_ECPOINT) + alg_str = strings.trim_prefix(alg_str, PREFIX_TEST_ECDH) + testing.expectf(t, test_ecdh_impl(&test_vectors, alg_str), "alg {} failed", alg_str) + } +} + +test_ecdh_impl :: proc( + test_vectors: ^Test_Vectors(Ecdh_Test_Group), + alg_str: string, +) -> bool { + ALG_P256 :: "secp256r1" + ALG_P384 :: "secp384r1" + ALG_X25519 :: "x25519" + ALG_X448 :: "x448" + + // XDH exceptions + FLAG_PUBLIC_KEY_TOO_LONG :: "PublicKeyTooLong" + FLAG_ZERO_SHARED_SECRET :: "ZeroSharedSecret" + + // ECDH exceptions + FLAG_COMPRESSED_POINT :: "CompressedPoint" + FLAG_INVALID_CURVE :: "InvalidCurveAttack" + FLAG_INVALID_ENCODING :: "InvalidEncoding" + + log.debugf("ecdh/%s: starting", alg_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf("ecdh/%s/%d: %s: %+v", alg_str, test_vector.tc_id, comment, test_vector.flags) + } else { + log.debugf("ecdh/%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) + } + + raw_pub := common.hexbytes_decode(test_vector.public) + raw_priv := common.hexbytes_decode(test_vector.private) + + curve: ecdh.Curve + priv_key: ecdh.Private_Key + pub_key: ecdh.Public_Key + + is_nist, is_xdh: bool + switch alg_str { + case ALG_P256: + curve = .SECP256R1 + // Ugh, ASN.1 :( + l := len(raw_priv) + if l == 33 { + if raw_priv[0] == 0 { + raw_priv = raw_priv[1:] + } + } else if l < 32 { + // left-pad.odin + tmp := make([]byte, 32) + copy(tmp[32-l:], raw_priv) + raw_priv = tmp + } + is_nist = true + case ALG_P384: + curve = .SECP384R1 + // Ugh, ASN.1 :( + l := len(raw_priv) + if l == 49 { + if raw_priv[0] == 0 { + raw_priv = raw_priv[1:] + } + } else if l < 48 { + // left-pad.odin + tmp := make([]byte, 48) + copy(tmp[48-l:], raw_priv) + raw_priv = tmp + } + is_nist = true + case ALG_X25519: + curve = .X25519 + is_xdh = true + case ALG_X448: + curve = .X448 + is_xdh = true + case: + log.errorf("ecdh: unsupported algorithm: %s", alg_str) + return false + } + + if ok := ecdh.private_key_set_bytes(&priv_key, curve, raw_priv); !ok { + log.errorf( + "ecdh/%s/%d: failed to deserialize private_key: %s %d %x", + alg_str, + test_vector.tc_id, + test_vector.private, + len(raw_priv), + raw_priv, + ) + num_failed += 1 + continue + } + + if ok := ecdh.public_key_set_bytes(&pub_key, curve, raw_pub); !ok { + if is_nist { + if slice.contains(test_vector.flags, FLAG_COMPRESSED_POINT) { + num_passed += 1 + continue + } + if slice.contains(test_vector.flags, FLAG_INVALID_CURVE) { + num_passed += 1 + continue + } + if slice.contains(test_vector.flags, FLAG_INVALID_ENCODING) { + num_passed += 1 + continue + } + } + if slice.contains(test_vector.flags, FLAG_PUBLIC_KEY_TOO_LONG) { + num_passed += 1 + continue + } + + log.errorf( + "ecdh/%s/%d: failed to deserialize public_key: %s", + alg_str, + test_vector.tc_id, + test_vector.public, + ) + num_failed += 1 + continue + } + + shared := make([]byte, ecdh.SHARED_SECRET_SIZES[curve]) + + ok := ecdh.ecdh(&priv_key, &pub_key, shared) + if !ok { + if is_xdh && slice.contains(test_vector.flags, FLAG_ZERO_SHARED_SECRET) { + num_passed += 1 + continue + } + // unused: x := transmute(string)(hex.encode(shared)) + log.errorf( + "ecdh/%s/%d: ecdh failed", + alg_str, + test_vector.tc_id, + ) + num_failed += 1 + continue + } + + ok = common.hexbytes_compare(test_vector.shared, shared) + // "acceptable" results are fine from here because we have + // checked for the all-zero shared secret XDH case already. + if !result_check(test_vector.result, ok, false) { + x := transmute(string)(hex.encode(shared)) + log.errorf( + "ecdh/%s/%d: shared: expected %s actual %s", + alg_str, + test_vector.tc_id, + test_vector.shared, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "ecdh/%s: ran %d, passed %d, failed %d, skipped %d", + alg_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} diff --git a/tests/core/crypto/wycheproof/kdf.odin b/tests/core/crypto/wycheproof/kdf.odin new file mode 100644 index 000000000..5b5ea1f2d --- /dev/null +++ b/tests/core/crypto/wycheproof/kdf.odin @@ -0,0 +1,238 @@ +package test_wycheproof + +import "core:encoding/hex" +import "core:log" +import "core:mem" +import "core:os" +import "core:slice" +import "core:strings" +import "core:testing" + +import "core:crypto/hkdf" +import "core:crypto/pbkdf2" + +import "../common" + +@(test) +test_hkdf :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("hkdf: starting") + + files := []string { + "hkdf_sha1_test.json", + "hkdf_sha256_test.json", + "hkdf_sha384_test.json", + "hkdf_sha512_test.json", + } + + for f in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Hkdf_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_hkdf_impl(&test_vectors), "hkdf failed") + } +} + +test_hkdf_impl :: proc(test_vectors: ^Test_Vectors(Hkdf_Test_Group)) -> bool { + PREFIX_HKDF :: "HKDF-" + FLAG_SIZE_TOO_LARGE :: "SizeTooLarge" + + alg_str := strings.trim_prefix(test_vectors.algorithm, PREFIX_HKDF) + alg, ok := hash_name_to_algorithm(alg_str) + if !ok { + return false + } + alg_str = strings.to_lower(alg_str) + + log.debugf("hkdf/%s: starting", alg_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "hkdf/%s/%d: %s: %+v", + alg_str, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("hkdf/%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) + } + + ikm := common.hexbytes_decode(test_vector.ikm) + salt := common.hexbytes_decode(test_vector.salt) + info := common.hexbytes_decode(test_vector.info) + + if slice.contains(test_vector.flags, FLAG_SIZE_TOO_LARGE) { + log.infof( + "hkdf/%s/%d: skipped, oversized outputs panic", + alg_str, + test_vector.tc_id, + ) + num_skipped += 1 + continue + } + + okm_ := make([]byte, test_vector.size) + hkdf.extract_and_expand(alg, salt, ikm, info, okm_) + + ok = common.hexbytes_compare(test_vector.okm, okm_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(okm_)) + log.errorf( + "hkdf/%s/%d: shared: expected %s actual %s", + alg_str, + test_vector.tc_id, + test_vector.okm, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "hkdf/%s: ran %d, passed %d, failed %d, skipped %d", + alg_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} + +@(test) +test_pbkdf2 :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("pbkdf2: starting") + + files := []string { + "pbkdf2_hmacsha1_test.json", + "pbkdf2_hmacsha224_test.json", + "pbkdf2_hmacsha256_test.json", + "pbkdf2_hmacsha384_test.json", + "pbkdf2_hmacsha512_test.json", + } + + for f in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Pbkdf_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_pbkdf2_impl(&test_vectors), "pbkdf2 failed") + } +} + +test_pbkdf2_impl :: proc( + test_vectors: ^Test_Vectors(Pbkdf_Test_Group), +) -> bool { + PREFIX_PBKDF_HMAC :: "PBKDF2-HMAC" + FLAG_LARGE_ITERATION_COUNT :: "LargeIterationCount" + + alg_str := strings.trim_prefix(test_vectors.algorithm, PREFIX_PBKDF_HMAC) + alg, ok := hash_name_to_algorithm(alg_str) + if !ok { + return false + } + alg_str = strings.to_lower(alg_str) + + log.debugf("pbkdf2/hmac-%s: starting", alg_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "pbkdf2/hmac-%s/%d: %s: %+v", + alg_str, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("pbkdf2/hmac-%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) + } + + if slice.contains(test_vector.flags, FLAG_LARGE_ITERATION_COUNT) { + log.infof( + "pbkdf2/hmac-%s/%d: skipped, takes fucking forever", + alg_str, + test_vector.tc_id, + ) + num_skipped += 1 + continue + } + + password := common.hexbytes_decode(test_vector.password) + salt := common.hexbytes_decode(test_vector.salt) + + dk_ := make([]byte, test_vector.dk_len) + pbkdf2.derive(alg, password, salt, test_vector.iteration_count, dk_) + + ok = common.hexbytes_compare(test_vector.dk, dk_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(dk_)) + log.errorf( + "pbkdf2/hmac-%s/%d: shared: expected %s actual %s", + alg_str, + test_vector.tc_id, + test_vector.dk, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "pbkdf2/%s: ran %d, passed %d, failed %d, skipped %d", + alg_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} diff --git a/tests/core/crypto/wycheproof/mac.odin b/tests/core/crypto/wycheproof/mac.odin new file mode 100644 index 000000000..35dcc1fde --- /dev/null +++ b/tests/core/crypto/wycheproof/mac.odin @@ -0,0 +1,156 @@ +package test_wycheproof + +import "core:encoding/hex" + +import "core:log" +import "core:mem" +import "core:os" +import "core:testing" + +import "core:crypto/hmac" +import "core:crypto/kmac" +import "core:crypto/siphash" + +import "../common" + +@(test) +test_mac :: proc(t: ^testing.T) { + arena: mem.Arena + arena_backing := make([]byte, ARENA_SIZE) + defer delete(arena_backing) + mem.arena_init(&arena, arena_backing) + context.allocator = mem.arena_allocator(&arena) + + log.debug("mac: starting") + + files := []string { + "hmac_sha1_test.json", + "hmac_sha224_test.json", + "hmac_sha256_test.json", + "hmac_sha3_224_test.json", + "hmac_sha3_256_test.json", + "hmac_sha3_384_test.json", + "hmac_sha3_512_test.json", + "hmac_sha384_test.json", + // "hmac_sha512_224_test.json", + "hmac_sha512_256_test.json", + "hmac_sha512_test.json", + "hmac_sm3_test.json", + "kmac128_no_customization_test.json", + "kmac256_no_customization_test.json", + "siphash_1_3_test.json", + "siphash_2_4_test.json", + "siphash_4_8_test.json", + } + + for f in files { + mem.free_all() // Probably don't need this, but be safe. + + fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) + + test_vectors: Test_Vectors(Mac_Test_Group) + if !testing.expectf(t, load(&test_vectors, fn), "Unable to load {}", f) { + continue + } + + testing.expectf(t, test_mac_impl(&test_vectors), "hkdf failed") + } +} + +test_mac_impl :: proc(test_vectors: ^Test_Vectors(Mac_Test_Group)) -> bool { + PREFIX_HMAC :: "HMAC" + PREFIX_KMAC :: "KMAC" + + mac_alg, hmac_alg, alg_str, ok := mac_algorithm(test_vectors.algorithm) + if !ok { + log.errorf("mac: unsupported algorith: %s", test_vectors.algorithm) + return false + } + + log.debugf("%s: starting", alg_str) + + num_ran, num_passed, num_failed, num_skipped: int + for &test_group in test_vectors.test_groups { + for &test_vector in test_group.tests { + num_ran += 1 + + if comment := test_vector.comment; comment != "" { + log.debugf( + "%s/%d: %s: %+v", + alg_str, + test_vector.tc_id, + comment, + test_vector.flags, + ) + } else { + log.debugf("%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) + } + + key := common.hexbytes_decode(test_vector.key) + msg := common.hexbytes_decode(test_vector.msg) + + tag_ := make([]byte, len(test_vector.tag) / 2) + + #partial switch mac_alg { + case .HMAC: + ctx: hmac.Context + hmac.init(&ctx, hmac_alg, key) + hmac.update(&ctx, msg) + if l := hmac.tag_size(&ctx); l == len(tag_) { + hmac.final(&ctx, tag_) + } else { + // Our hmac package does not support truncation. + tmp := make([]byte, l) + hmac.final(&ctx, tmp) + copy(tag_, tmp) + } + case .KMAC128, .KMAC256: + ctx: kmac.Context + #partial switch mac_alg { + case .KMAC128: + kmac.init_128(&ctx, key, nil) + case .KMAC256: + kmac.init_256(&ctx, key, nil) + } + kmac.update(&ctx, msg) + kmac.final(&ctx, tag_) + case .SIPHASH_1_3: + siphash.sum_1_3(msg, key, tag_) + case .SIPHASH_2_4: + siphash.sum_2_4(msg, key, tag_) + case .SIPHASH_4_8: + siphash.sum_4_8(msg, key, tag_) + } + + ok = common.hexbytes_compare(test_vector.tag, tag_) + if !result_check(test_vector.result, ok) { + x := transmute(string)(hex.encode(tag_)) + log.errorf( + "%s/%d: tag: expected %s actual %s", + alg_str, + test_vector.tc_id, + test_vector.tag, + x, + ) + num_failed += 1 + continue + } + + num_passed += 1 + } + } + + assert(num_ran == test_vectors.number_of_tests) + assert(num_passed + num_failed + num_skipped == num_ran) + + log.infof( + "%s: ran %d, passed %d, failed %d, skipped %d", + alg_str, + num_ran, + num_passed, + num_failed, + num_skipped, + ) + + return num_failed == 0 +} diff --git a/tests/core/crypto/wycheproof/main.odin b/tests/core/crypto/wycheproof/main.odin index dc8ab9237..dfdc78267 100644 --- a/tests/core/crypto/wycheproof/main.odin +++ b/tests/core/crypto/wycheproof/main.odin @@ -1,32 +1,8 @@ package test_wycheproof -import "core:encoding/hex" import "core:log" -import "core:mem" -import "core:os" -import "core:slice" -import "core:strings" import "core:testing" -import chacha_simd128 "core:crypto/_chacha20/simd128" -import chacha_simd256 "core:crypto/_chacha20/simd256" -import "core:crypto/aegis" -import "core:crypto/aes" -import "core:crypto/chacha20" -import "core:crypto/chacha20poly1305" -import "core:crypto/ecdh" -import "core:crypto/ecdsa" -import "core:crypto/ed25519" -import "core:crypto/hash" -import "core:crypto/hkdf" -import "core:crypto/hmac" -import "core:crypto/kmac" -import "core:crypto/pbkdf2" -import "core:crypto/siphash" -import "core:crypto/deoxysii" - -import "../common" - // Covered: // - crypto/aegis // - aegis128L_test.json @@ -96,1307 +72,3 @@ SUFFIX_TEST_JSON :: "_test.json" print_test_vector_path :: proc(t: ^testing.T) { log.infof("wycheproof path: %s", BASE_PATH) } - -supported_aegis_impls :: proc() -> [dynamic]aes.Implementation { - impls := make([dynamic]aes.Implementation, 0, 2, context.temp_allocator) - append(&impls, aes.Implementation.Portable) - if aegis.is_hardware_accelerated() { - append(&impls, aes.Implementation.Hardware) - } - - return impls -} - -@(test) -test_aead_aegis :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - files := []string { - "aegis128L_test.json", - "aegis256_test.json", - } - - log.debug("aead/aegis: starting") - - for f in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Aead_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - for impl in supported_aegis_impls() { - testing.expectf(t, test_aead_aegis_impl(&test_vectors, impl), "impl {} failed", impl) - } - } -} - -test_aead_aegis_impl :: proc( - test_vectors: ^Test_Vectors(Aead_Test_Group), - impl: aes.Implementation, -) -> bool { - log.debug("aead/aegis/%v: starting", impl) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "aead/aegis/%v/%d: %s: %+v", - impl, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("aead/aegis/%v/%d: %+v", - impl, - test_vector.tc_id, - test_vector.flags, - ) - } - - key := common.hexbytes_decode(test_vector.key) - iv := common.hexbytes_decode(test_vector.iv) - aad := common.hexbytes_decode(test_vector.aad) - msg := common.hexbytes_decode(test_vector.msg) - ct := common.hexbytes_decode(test_vector.ct) - tag := common.hexbytes_decode(test_vector.tag) - - if len(iv) == 0 { - log.infof( - "aead/aegis/%v/%d: skipped, invalid IVs panic", - impl, - test_vector.tc_id, - ) - num_skipped += 1 - continue - } - - ctx: aegis.Context - aegis.init(&ctx, key, impl) - - if result_is_valid(test_vector.result) { - ct_ := make([]byte, len(ct)) - tag_ := make([]byte, len(tag)) - aegis.seal(&ctx, ct_, tag_, iv, aad, msg) - - ok := common.hexbytes_compare(test_vector.ct, ct_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(ct_)) - log.errorf( - "aead/aegis/%v/%d: ciphertext: expected %s actual %s", - impl, - test_vector.tc_id, - test_vector.ct, - x, - ) - num_failed += 1 - continue - } - - ok = common.hexbytes_compare(test_vector.tag, tag_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(tag_)) - log.errorf( - "aead/aegis/%v/%d: tag: expected %s actual %s", - impl, - test_vector.tc_id, - test_vector.tag, - x, - ) - num_failed += 1 - continue - } - } - - msg_ := make([]byte, len(msg)) - ok := aegis.open(&ctx, msg_, iv, aad, ct, tag) - if !result_check(test_vector.result, ok) { - log.errorf("aead/aegis/%v/%d: decrypt failed", impl, test_vector.tc_id) - num_failed += 1 - continue - } - - if ok && !common.hexbytes_compare(test_vector.msg, msg_) { - x := transmute(string)(hex.encode(msg_)) - log.errorf( - "aead/aegis/%v/%d: decrypt msg: expected %s actual %s", - impl, - test_vector.tc_id, - test_vector.msg, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "aead/aegis: ran %d, passed %d, failed %d, skipped %d", - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -supported_aes_impls :: proc() -> [dynamic]aes.Implementation { - impls := make([dynamic]aes.Implementation, 0, 2) - append(&impls, aes.Implementation.Portable) - if aes.is_hardware_accelerated() { - append(&impls, aes.Implementation.Hardware) - } - - return impls -} - -@(test) -test_aead_aes_gcm :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - fn, _ := os.join_path([]string{BASE_PATH, "aes_gcm_test.json"}, context.allocator) - - log.debug("aead/aes-gcm: starting") - - test_vectors: Test_Vectors(Aead_Test_Group) - assert(load(&test_vectors, fn)) - - for impl in supported_aes_impls() { - testing.expectf(t, test_aead_aes_gcm_impl(&test_vectors, impl), "impl {} failed", impl) - } -} - -test_aead_aes_gcm_impl :: proc( - test_vectors: ^Test_Vectors(Aead_Test_Group), - impl: aes.Implementation, -) -> bool { - log.debug("aead/aes-gcm/%v: starting", impl) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "aead/aes-gcm/%v/%d: %s: %+v", - impl, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("aead/aes-gcm/%v/%d: %+v", - impl, - test_vector.tc_id, - test_vector.flags, - ) - } - - key := common.hexbytes_decode(test_vector.key) - iv := common.hexbytes_decode(test_vector.iv) - aad := common.hexbytes_decode(test_vector.aad) - msg := common.hexbytes_decode(test_vector.msg) - ct := common.hexbytes_decode(test_vector.ct) - tag := common.hexbytes_decode(test_vector.tag) - - if len(iv) == 0 { - log.infof( - "aead/aes-gcm/%v/%d: skipped, invalid IVs panic", - impl, - test_vector.tc_id, - ) - num_skipped += 1 - continue - } - - ctx: aes.Context_GCM - aes.init_gcm(&ctx, key, impl) - - if result_is_valid(test_vector.result) { - ct_ := make([]byte, len(ct)) - tag_ := make([]byte, len(tag)) - aes.seal_gcm(&ctx, ct_, tag_, iv, aad, msg) - - ok := common.hexbytes_compare(test_vector.ct, ct_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(ct_)) - log.errorf( - "aead/aes-gcm/%v/%d: ciphertext: expected %s actual %s", - impl, - test_vector.tc_id, - test_vector.ct, - x, - ) - num_failed += 1 - continue - } - - ok = common.hexbytes_compare(test_vector.tag, tag_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(tag_)) - log.errorf( - "aead/aes-gcm/%v/%d: tag: expected %s actual %s", - impl, - test_vector.tc_id, - test_vector.tag, - x, - ) - num_failed += 1 - continue - } - } - - msg_ := make([]byte, len(msg)) - ok := aes.open_gcm(&ctx, msg_, iv, aad, ct, tag) - if !result_check(test_vector.result, ok) { - log.errorf("aead/aes-gcm/%v/%d: decrypt failed", impl, test_vector.tc_id) - num_failed += 1 - continue - } - - if ok && !common.hexbytes_compare(test_vector.msg, msg_) { - x := transmute(string)(hex.encode(msg_)) - log.errorf( - "aead/aes-gcm/%v/%d: decrypt msg: expected %s actual %s", - impl, - test_vector.tc_id, - test_vector.msg, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "aead/aes-gcm: ran %d, passed %d, failed %d, skipped %d", - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -supported_chacha_impls :: proc() -> [dynamic]chacha20.Implementation { - impls := make([dynamic]chacha20.Implementation, 0, 3) - append(&impls, chacha20.Implementation.Portable) - if chacha_simd128.is_performant() { - append(&impls, chacha20.Implementation.Simd128) - } - if chacha_simd256.is_performant() { - append(&impls, chacha20.Implementation.Simd256) - } - - return impls -} - -@(test) -test_aead_chacha20_poly1305 :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - files := []string { - "chacha20_poly1305_test.json", - "xchacha20_poly1305_test.json", - } - - log.debug("aead/(x)chacha20poly1305: starting") - - for f, i in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Aead_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - for impl in supported_chacha_impls() { - testing.expectf(t, test_aead_chacha20_poly1305_impl(&test_vectors, i == 1, impl), "impl {} failed", impl) - } - } -} - -test_aead_chacha20_poly1305_impl :: proc( - test_vectors: ^Test_Vectors(Aead_Test_Group), - is_xchacha: bool, - impl: chacha20.Implementation, -) -> bool { - FLAG_INVALID_NONCE_SIZE :: "InvalidNonceSize" - - alg_str := is_xchacha ? "xchacha20poly1305" : "chacha20poly1305" - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "aead/%s/%v/%d: %s: %+v", - alg_str, - impl, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("aead/%s/%v/%d: %+v", - alg_str, - impl, - test_vector.tc_id, - test_vector.flags, - ) - } - - key := common.hexbytes_decode(test_vector.key) - iv := common.hexbytes_decode(test_vector.iv) - aad := common.hexbytes_decode(test_vector.aad) - msg := common.hexbytes_decode(test_vector.msg) - ct := common.hexbytes_decode(test_vector.ct) - tag := common.hexbytes_decode(test_vector.tag) - - if slice.contains(test_vector.flags, FLAG_INVALID_NONCE_SIZE) { - log.infof( - "aead/%s/%v/%d: skipped, invalid nonces panic", - alg_str, - impl, - test_vector.tc_id, - ) - num_skipped += 1 - continue - } - - ctx: chacha20poly1305.Context - switch is_xchacha { - case true: - chacha20poly1305.init_xchacha(&ctx, key, impl) - case false: - chacha20poly1305.init(&ctx, key, impl) - } - - if result_is_valid(test_vector.result) { - ct_ := make([]byte, len(ct)) - tag_ := make([]byte, len(tag)) - chacha20poly1305.seal(&ctx, ct_, tag_, iv, aad, msg) - - ok := common.hexbytes_compare(test_vector.ct, ct_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(ct_)) - log.errorf( - "aead/%s/%v/%d: ciphertext: expected %s actual %s", - alg_str, - impl, - test_vector.tc_id, - test_vector.ct, - x, - ) - num_failed += 1 - continue - } - - ok = common.hexbytes_compare(test_vector.tag, tag_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(tag_)) - log.errorf( - "aead/%s/%v/%d: tag: expected %s actual %s", - alg_str, - impl, - test_vector.tc_id, - test_vector.tag, - x, - ) - num_failed += 1 - continue - } - } - - msg_ := make([]byte, len(msg)) - ok := chacha20poly1305.open(&ctx, msg_, iv, aad, ct, tag) - if !result_check(test_vector.result, ok) { - log.errorf("aead/%s/%v/%d: decrypt failed", - alg_str, - impl, - test_vector.tc_id, - ) - num_failed += 1 - continue - } - - if ok && !common.hexbytes_compare(test_vector.msg, msg_) { - x := transmute(string)(hex.encode(msg_)) - log.errorf( - "aead/%s/%v/%d: decrypt msg: expected %s actual %s", - alg_str, - impl, - test_vector.tc_id, - test_vector.msg, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "aead/%s/%v: ran %d, passed %d, failed %d, skipped %d", - alg_str, - impl, - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -@(test) -test_aead_deoxysii :: proc(t: ^testing.T) { - ctx: deoxysii.Context - - key: [deoxysii.KEY_SIZE]byte - iv: [deoxysii.IV_SIZE]byte - tag: [deoxysii.TAG_SIZE]byte - buf: [4096]byte - - deoxysii.init(&ctx, key[:]) - deoxysii.seal(&ctx, buf[:], tag[:], iv[:], nil, buf[:]) - assert(deoxysii.open(&ctx, buf[:], iv[:], nil, buf[:], tag[:])) -} - -@(test) -test_eddsa_ed25519 :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - fn_, _ := os.join_path([]string{BASE_PATH, "ed25519_test.json"}, context.allocator) - - log.debug("eddsa/ed25519: starting") - - test_vectors: Test_Vectors(Eddsa_Test_Group) - assert(load(&test_vectors, fn_)) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group, i in test_vectors.test_groups { - mem.free_all() // Probably don't need this, but be safe. - pk_bytes := common.hexbytes_decode(test_group.public_key.pk) - - pk: ed25519.Public_Key - pk_ok := ed25519.public_key_set_bytes(&pk, pk_bytes) - testing.expectf(t, pk_ok, "eddsa/ed25519/%d: invalid public key: %s", i, test_group.public_key.pk) - if !pk_ok { - num_failed += len(test_group.tests) - continue - } - - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "eddsa/ed25519/%d: %s: %+v", - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("eddsa/ed25519/%d: %+v", test_vector.tc_id, test_vector.flags) - } - - msg := common.hexbytes_decode(test_vector.msg) - sig := common.hexbytes_decode(test_vector.sig) - - verify_ok := ed25519.verify(&pk, msg, sig) - result_ok := result_check(test_vector.result, verify_ok) - testing.expectf( - t, - result_ok, - "eddsa/ed25519/%d: verify failed: expected %s actual %v", - test_vector.tc_id, - test_vector.result, - verify_ok, - ) - if !result_ok { - num_failed += 1 - continue - } - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "eddsa/ed25519: ran %d, passed %d, failed %d, skipped %d", - num_ran, - num_passed, - num_failed, - num_skipped, - ) -} - -@(test) -test_ecdsa :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - log.debug("ecdsa: starting") - - files := []string { - "ecdsa_secp256r1_sha256_test.json", - "ecdsa_secp256r1_sha512_test.json", - "ecdsa_secp384r1_sha384_test.json", - } - - for f in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Ecdsa_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - testing.expectf(t, test_ecdsa_impl(t, &test_vectors), "ecdsa failed") - } -} - -test_ecdsa_impl :: proc(t: ^testing.T, test_vectors: ^Test_Vectors(Ecdsa_Test_Group)) -> bool { - curve_str := test_vectors.test_groups[0].public_key.curve - hash_str := test_vectors.test_groups[0].sha - - curve_alg: ecdsa.Curve - switch curve_str { - case "secp256r1": - curve_alg = .SECP256R1 - case "secp384r1": - curve_alg = .SECP384R1 - case: - log.errorf("ecdsa: unsupported curve: %s", curve_str) - } - - hash_alg: hash.Algorithm - switch hash_str { - case "SHA-256": - hash_alg = .SHA256 - case "SHA-384": - hash_alg = .SHA384 - case "SHA-512": - hash_alg = .SHA512 - case: - log.errorf("ecdsa: unsupported hash: %s", hash_str) - } - - log.debugf("ecdsa/%s/%s: starting", curve_str, hash_str) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group, i in test_vectors.test_groups { - pk_bytes := common.hexbytes_decode(test_group.public_key.uncompressed) - - pk: ecdsa.Public_Key - pk_ok := ecdsa.public_key_set_bytes(&pk, curve_alg, pk_bytes) - testing.expectf(t, pk_ok, "ecdsa/%s/%s/%d: invalid public key: %s", curve_str, hash_str, i, test_group.public_key.uncompressed) - if !pk_ok { - num_failed += len(test_group.tests) - continue - } - - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "ecda/%s/%s/%d: %s: %+v", - curve_str, - hash_str, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("ecdsa/%s/%s/%d: %+v", curve_str, hash_str, test_vector.tc_id, test_vector.flags) - } - - msg := common.hexbytes_decode(test_vector.msg) - sig := common.hexbytes_decode(test_vector.sig) - - verify_ok := ecdsa.verify_asn1(&pk, hash_alg, msg, sig) - result_ok := result_check(test_vector.result, verify_ok) - testing.expectf( - t, - result_ok, - "ecdsa/%s/%s/%d: verify failed: expected %s actual %v", - curve_str, - hash_str, - test_vector.tc_id, - test_vector.result, - verify_ok, - ) - if !result_ok { - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "ecdsa/%s/%s: ran %d, passed %d, failed %d, skipped %d", - curve_str, - hash_str, - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -@(test) -test_hkdf :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - log.debug("hkdf: starting") - - files := []string { - "hkdf_sha1_test.json", - "hkdf_sha256_test.json", - "hkdf_sha384_test.json", - "hkdf_sha512_test.json", - } - - for f in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Hkdf_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - testing.expectf(t, test_hkdf_impl(&test_vectors), "hkdf failed") - } -} - -test_hkdf_impl :: proc(test_vectors: ^Test_Vectors(Hkdf_Test_Group)) -> bool { - PREFIX_HKDF :: "HKDF-" - FLAG_SIZE_TOO_LARGE :: "SizeTooLarge" - - alg_str := strings.trim_prefix(test_vectors.algorithm, PREFIX_HKDF) - alg, ok := hash_name_to_algorithm(alg_str) - if !ok { - return false - } - alg_str = strings.to_lower(alg_str) - - log.debugf("hkdf/%s: starting", alg_str) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "hkdf/%s/%d: %s: %+v", - alg_str, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("hkdf/%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) - } - - ikm := common.hexbytes_decode(test_vector.ikm) - salt := common.hexbytes_decode(test_vector.salt) - info := common.hexbytes_decode(test_vector.info) - - if slice.contains(test_vector.flags, FLAG_SIZE_TOO_LARGE) { - log.infof( - "hkdf/%s/%d: skipped, oversized outputs panic", - alg_str, - test_vector.tc_id, - ) - num_skipped += 1 - continue - } - - okm_ := make([]byte, test_vector.size) - hkdf.extract_and_expand(alg, salt, ikm, info, okm_) - - ok = common.hexbytes_compare(test_vector.okm, okm_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(okm_)) - log.errorf( - "hkdf/%s/%d: shared: expected %s actual %s", - alg_str, - test_vector.tc_id, - test_vector.okm, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "hkdf/%s: ran %d, passed %d, failed %d, skipped %d", - alg_str, - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -@(test) -test_mac :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - log.debug("mac: starting") - - files := []string { - "hmac_sha1_test.json", - "hmac_sha224_test.json", - "hmac_sha256_test.json", - "hmac_sha3_224_test.json", - "hmac_sha3_256_test.json", - "hmac_sha3_384_test.json", - "hmac_sha3_512_test.json", - "hmac_sha384_test.json", - // "hmac_sha512_224_test.json", - "hmac_sha512_256_test.json", - "hmac_sha512_test.json", - "hmac_sm3_test.json", - "kmac128_no_customization_test.json", - "kmac256_no_customization_test.json", - "siphash_1_3_test.json", - "siphash_2_4_test.json", - "siphash_4_8_test.json", - } - - for f in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Mac_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - testing.expectf(t, test_mac_impl(&test_vectors), "hkdf failed") - } -} - -test_mac_impl :: proc(test_vectors: ^Test_Vectors(Mac_Test_Group)) -> bool { - PREFIX_HMAC :: "HMAC" - PREFIX_KMAC :: "KMAC" - - mac_alg, hmac_alg, alg_str, ok := mac_algorithm(test_vectors.algorithm) - if !ok { - log.errorf("mac: unsupported algorith: %s", test_vectors.algorithm) - return false - } - - log.debugf("%s: starting", alg_str) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "%s/%d: %s: %+v", - alg_str, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) - } - - key := common.hexbytes_decode(test_vector.key) - msg := common.hexbytes_decode(test_vector.msg) - - tag_ := make([]byte, len(test_vector.tag) / 2) - - #partial switch mac_alg { - case .HMAC: - ctx: hmac.Context - hmac.init(&ctx, hmac_alg, key) - hmac.update(&ctx, msg) - if l := hmac.tag_size(&ctx); l == len(tag_) { - hmac.final(&ctx, tag_) - } else { - // Our hmac package does not support truncation. - tmp := make([]byte, l) - hmac.final(&ctx, tmp) - copy(tag_, tmp) - } - case .KMAC128, .KMAC256: - ctx: kmac.Context - #partial switch mac_alg { - case .KMAC128: - kmac.init_128(&ctx, key, nil) - case .KMAC256: - kmac.init_256(&ctx, key, nil) - } - kmac.update(&ctx, msg) - kmac.final(&ctx, tag_) - case .SIPHASH_1_3: - siphash.sum_1_3(msg, key, tag_) - case .SIPHASH_2_4: - siphash.sum_2_4(msg, key, tag_) - case .SIPHASH_4_8: - siphash.sum_4_8(msg, key, tag_) - } - - ok = common.hexbytes_compare(test_vector.tag, tag_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(tag_)) - log.errorf( - "%s/%d: tag: expected %s actual %s", - alg_str, - test_vector.tc_id, - test_vector.tag, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "%s: ran %d, passed %d, failed %d, skipped %d", - alg_str, - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -@(test) -test_pbkdf2 :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - log.debug("pbkdf2: starting") - - files := []string { - "pbkdf2_hmacsha1_test.json", - "pbkdf2_hmacsha224_test.json", - "pbkdf2_hmacsha256_test.json", - "pbkdf2_hmacsha384_test.json", - "pbkdf2_hmacsha512_test.json", - } - - for f in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Pbkdf_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - testing.expectf(t, test_pbkdf2_impl(&test_vectors), "pbkdf2 failed") - } -} - -test_pbkdf2_impl :: proc( - test_vectors: ^Test_Vectors(Pbkdf_Test_Group), -) -> bool { - PREFIX_PBKDF_HMAC :: "PBKDF2-HMAC" - FLAG_LARGE_ITERATION_COUNT :: "LargeIterationCount" - - alg_str := strings.trim_prefix(test_vectors.algorithm, PREFIX_PBKDF_HMAC) - alg, ok := hash_name_to_algorithm(alg_str) - if !ok { - return false - } - alg_str = strings.to_lower(alg_str) - - log.debugf("pbkdf2/hmac-%s: starting", alg_str) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf( - "pbkdf2/hmac-%s/%d: %s: %+v", - alg_str, - test_vector.tc_id, - comment, - test_vector.flags, - ) - } else { - log.debugf("pbkdf2/hmac-%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) - } - - if slice.contains(test_vector.flags, FLAG_LARGE_ITERATION_COUNT) { - log.infof( - "pbkdf2/hmac-%s/%d: skipped, takes fucking forever", - alg_str, - test_vector.tc_id, - ) - num_skipped += 1 - continue - } - - password := common.hexbytes_decode(test_vector.password) - salt := common.hexbytes_decode(test_vector.salt) - - dk_ := make([]byte, test_vector.dk_len) - pbkdf2.derive(alg, password, salt, test_vector.iteration_count, dk_) - - ok = common.hexbytes_compare(test_vector.dk, dk_) - if !result_check(test_vector.result, ok) { - x := transmute(string)(hex.encode(dk_)) - log.errorf( - "pbkdf2/hmac-%s/%d: shared: expected %s actual %s", - alg_str, - test_vector.tc_id, - test_vector.dk, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "pbkdf2/%s: ran %d, passed %d, failed %d, skipped %d", - alg_str, - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -} - -@(test) -test_ecdh :: proc(t: ^testing.T) { - arena: mem.Arena - arena_backing := make([]byte, ARENA_SIZE) - defer delete(arena_backing) - mem.arena_init(&arena, arena_backing) - context.allocator = mem.arena_allocator(&arena) - - PREFIX_TEST_ECDH :: "ecdh_" - SUFFIX_TEST_ECPOINT :: "_ecpoint" - - files := []string { - "ecdh_secp256r1_ecpoint_test.json", - "ecdh_secp384r1_ecpoint_test.json", - "x25519_test.json", - "x448_test.json", - } - - log.debug("ecdh: starting") - - for f in files { - mem.free_all() // Probably don't need this, but be safe. - - fn, _ := os.join_path([]string{BASE_PATH, f}, context.allocator) - - test_vectors: Test_Vectors(Ecdh_Test_Group) - load_ok := load(&test_vectors, fn) - testing.expectf(t, load_ok, "Unable to load {}", f) - if !load_ok { - continue - } - - alg_str := strings.trim_suffix(f, SUFFIX_TEST_JSON) - alg_str = strings.trim_suffix(alg_str, SUFFIX_TEST_ECPOINT) - alg_str = strings.trim_prefix(alg_str, PREFIX_TEST_ECDH) - testing.expectf(t, test_ecdh_impl(&test_vectors, alg_str), "alg {} failed", alg_str) - } -} - -test_ecdh_impl :: proc( - test_vectors: ^Test_Vectors(Ecdh_Test_Group), - alg_str: string, -) -> bool { - ALG_P256 :: "secp256r1" - ALG_P384 :: "secp384r1" - ALG_X25519 :: "x25519" - ALG_X448 :: "x448" - - // XDH exceptions - FLAG_PUBLIC_KEY_TOO_LONG :: "PublicKeyTooLong" - FLAG_ZERO_SHARED_SECRET :: "ZeroSharedSecret" - - // ECDH exceptions - FLAG_COMPRESSED_POINT :: "CompressedPoint" - FLAG_INVALID_CURVE :: "InvalidCurveAttack" - FLAG_INVALID_ENCODING :: "InvalidEncoding" - - log.debugf("ecdh/%s: starting", alg_str) - - num_ran, num_passed, num_failed, num_skipped: int - for &test_group in test_vectors.test_groups { - for &test_vector in test_group.tests { - num_ran += 1 - - if comment := test_vector.comment; comment != "" { - log.debugf("ecdh/%s/%d: %s: %+v", alg_str, test_vector.tc_id, comment, test_vector.flags) - } else { - log.debugf("ecdh/%s/%d: %+v", alg_str, test_vector.tc_id, test_vector.flags) - } - - raw_pub := common.hexbytes_decode(test_vector.public) - raw_priv := common.hexbytes_decode(test_vector.private) - - curve: ecdh.Curve - priv_key: ecdh.Private_Key - pub_key: ecdh.Public_Key - - is_nist, is_xdh: bool - switch alg_str { - case ALG_P256: - curve = .SECP256R1 - // Ugh, ASN.1 :( - l := len(raw_priv) - if l == 33 { - if raw_priv[0] == 0 { - raw_priv = raw_priv[1:] - } - } else if l < 32 { - // left-pad.odin - tmp := make([]byte, 32) - copy(tmp[32-l:], raw_priv) - raw_priv = tmp - } - is_nist = true - case ALG_P384: - curve = .SECP384R1 - // Ugh, ASN.1 :( - l := len(raw_priv) - if l == 49 { - if raw_priv[0] == 0 { - raw_priv = raw_priv[1:] - } - } else if l < 48 { - // left-pad.odin - tmp := make([]byte, 48) - copy(tmp[48-l:], raw_priv) - raw_priv = tmp - } - is_nist = true - case ALG_X25519: - curve = .X25519 - is_xdh = true - case ALG_X448: - curve = .X448 - is_xdh = true - case: - log.errorf("ecdh: unsupported algorithm: %s", alg_str) - return false - } - - if ok := ecdh.private_key_set_bytes(&priv_key, curve, raw_priv); !ok { - log.errorf( - "ecdh/%s/%d: failed to deserialize private_key: %s %d %x", - alg_str, - test_vector.tc_id, - test_vector.private, - len(raw_priv), - raw_priv, - ) - num_failed += 1 - continue - } - - if ok := ecdh.public_key_set_bytes(&pub_key, curve, raw_pub); !ok { - if is_nist { - if slice.contains(test_vector.flags, FLAG_COMPRESSED_POINT) { - num_passed += 1 - continue - } - if slice.contains(test_vector.flags, FLAG_INVALID_CURVE) { - num_passed += 1 - continue - } - if slice.contains(test_vector.flags, FLAG_INVALID_ENCODING) { - num_passed += 1 - continue - } - } - if slice.contains(test_vector.flags, FLAG_PUBLIC_KEY_TOO_LONG) { - num_passed += 1 - continue - } - - log.errorf( - "ecdh/%s/%d: failed to deserialize public_key: %s", - alg_str, - test_vector.tc_id, - test_vector.public, - ) - num_failed += 1 - continue - } - - shared := make([]byte, ecdh.SHARED_SECRET_SIZES[curve]) - - ok := ecdh.ecdh(&priv_key, &pub_key, shared) - if !ok { - if is_xdh && slice.contains(test_vector.flags, FLAG_ZERO_SHARED_SECRET) { - num_passed += 1 - continue - } - // unused: x := transmute(string)(hex.encode(shared)) - log.errorf( - "ecdh/%s/%d: ecdh failed", - alg_str, - test_vector.tc_id, - ) - num_failed += 1 - continue - } - - ok = common.hexbytes_compare(test_vector.shared, shared) - // "acceptable" results are fine from here because we have - // checked for the all-zero shared secret XDH case already. - if !result_check(test_vector.result, ok, false) { - x := transmute(string)(hex.encode(shared)) - log.errorf( - "ecdh/%s/%d: shared: expected %s actual %s", - alg_str, - test_vector.tc_id, - test_vector.shared, - x, - ) - num_failed += 1 - continue - } - - num_passed += 1 - } - } - - assert(num_ran == test_vectors.number_of_tests) - assert(num_passed + num_failed + num_skipped == num_ran) - - log.infof( - "ecdh/%s: ran %d, passed %d, failed %d, skipped %d", - alg_str, - num_ran, - num_passed, - num_failed, - num_skipped, - ) - - return num_failed == 0 -}