From 46f81a5918fc799c697f042efa02ad7d5fc82ed3 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Sun, 16 Feb 2025 15:40:54 -0600 Subject: [PATCH 01/58] wip: update crypto to go 1.24.0 --- crypto/aes/_asm/standard/go.mod | 0 crypto/aes/_asm/standard/go.sum | 0 crypto/aes/aes.go | 49 + crypto/aes/aes_gcm.go | 185 - crypto/aes/aes_test.go | 255 +- crypto/aes/asm_s390x.s | 193 - crypto/aes/cbc_ppc64x.go | 74 - crypto/aes/cbc_s390x.go | 68 - crypto/aes/cipher.go | 83 - crypto/aes/cipher_asm.go | 114 - crypto/aes/cipher_generic.go | 26 - crypto/aes/cipher_s390x.go | 98 - crypto/aes/ctr_s390x.go | 86 - crypto/aes/gcm_ppc64x.go | 250 - crypto/aes/gcm_s390x.go | 370 -- crypto/aes/modes.go | 37 - crypto/aes/modes_test.go | 112 - crypto/cipher/benchmark_test.go | 32 +- crypto/cipher/cbc.go | 32 +- crypto/cipher/cbc_aes_test.go | 12 +- crypto/cipher/cbc_test.go | 27 +- crypto/cipher/cfb.go | 21 +- crypto/cipher/cipher.go | 37 + crypto/cipher/cipher_test.go | 91 - crypto/cipher/ctr.go | 26 +- crypto/cipher/ctr_aes_test.go | 206 +- crypto/cipher/ctr_test.go | 30 +- crypto/cipher/export_test.go | 9 - crypto/cipher/fuzz_test.go | 4 +- crypto/cipher/gcm.go | 530 +- crypto/cipher/gcm_test.go | 250 +- crypto/cipher/modes_test.go | 126 + crypto/cipher/ofb.go | 13 +- crypto/des/block.go | 6 +- crypto/des/cipher.go | 22 +- crypto/dsa/dsa.go | 17 + crypto/ecdh/ecdh.go | 40 +- crypto/ecdh/ecdh_test.go | 59 +- crypto/ecdh/nist.go | 295 +- crypto/ecdh/x25519.go | 46 +- .../{aes/_asm/gcm/go.mod => ecdsa/boring.go} | 0 crypto/ecdsa/ecdsa.go | 503 +- crypto/ecdsa/ecdsa_legacy.go | 37 +- crypto/ecdsa/ecdsa_noasm.go | 17 - crypto/ecdsa/ecdsa_s390x_test.go | 32 - crypto/ecdsa/ecdsa_test.go | 221 +- crypto/ed25519/ed25519.go | 207 +- crypto/ed25519/ed25519_test.go | 61 +- crypto/ed25519/ed25519vectors_test.go | 39 +- crypto/elliptic/nistec.go | 10 +- crypto/elliptic/nistec_p256.go | 2 +- crypto/fips140/fips140.go | 34 + crypto/hkdf/example_test.go | 55 + crypto/hkdf/hkdf.go | 85 + crypto/hkdf/hkdf_test.go | 414 ++ crypto/hmac/hmac.go | 139 +- crypto/internal/bigmod/_asm/go.mod | 0 crypto/internal/bigmod/_asm/go.sum | 0 crypto/internal/cryptotest/aead.go | 26 +- crypto/internal/cryptotest/allocations.go | 44 + crypto/internal/cryptotest/blockmode.go | 13 + crypto/internal/cryptotest/fetchmodule.go | 59 + crypto/internal/cryptotest/implementations.go | 60 + crypto/internal/cryptotest/stream.go | 13 + .../internal/edwards25519/field/_asm/go.mod | 0 .../internal/edwards25519/field/_asm/go.sum | 0 crypto/internal/entropy/entropy.go | 28 + .../fips140/aes/_asm/ctr/ctr_amd64_asm.go | 127 + crypto/internal/fips140/aes/_asm/ctr/go.mod | 11 + crypto/internal/fips140/aes/_asm/ctr/go.sum | 8 + .../fips140/aes/_asm/standard/aes_amd64.go} | 4 +- .../internal/fips140/aes/_asm/standard/go.mod | 11 + .../internal/fips140/aes/_asm/standard/go.sum | 8 + crypto/internal/fips140/aes/aes.go | 132 + .../fips140/aes/aes_amd64.s} | 0 .../fips140/aes/aes_arm64.s} | 8 +- crypto/internal/fips140/aes/aes_asm.go | 96 + .../fips140/aes/aes_generic.go} | 73 +- crypto/internal/fips140/aes/aes_noasm.go | 26 + .../fips140/aes/aes_ppc64x.s} | 240 +- crypto/internal/fips140/aes/aes_s390x.go | 99 + crypto/internal/fips140/aes/aes_s390x.s | 39 + crypto/internal/fips140/aes/aes_test.go | 120 + crypto/internal/fips140/aes/cast.go | 49 + crypto/internal/fips140/aes/cbc.go | 130 + crypto/internal/fips140/aes/cbc_noasm.go | 15 + crypto/internal/fips140/aes/cbc_ppc64x.go | 31 + crypto/internal/fips140/aes/cbc_s390x.go | 30 + crypto/{ => internal/fips140}/aes/const.go | 9 - crypto/internal/fips140/aes/ctr.go | 149 + crypto/internal/fips140/aes/ctr_amd64.s | 494 ++ crypto/internal/fips140/aes/ctr_arm64.s | 729 +++ crypto/internal/fips140/aes/ctr_arm64_gen.go | 213 + crypto/internal/fips140/aes/ctr_asm.go | 53 + crypto/internal/fips140/aes/ctr_noasm.go | 23 + crypto/internal/fips140/aes/ctr_s390x.go | 49 + .../aes/gcm}/_asm/gcm/gcm_amd64_asm.go | 4 +- .../internal/fips140/aes/gcm/_asm/gcm/go.mod | 11 + .../internal/fips140/aes/gcm/_asm/gcm/go.sum | 8 + crypto/internal/fips140/aes/gcm/cast.go | 45 + crypto/internal/fips140/aes/gcm/cmac.go | 77 + crypto/internal/fips140/aes/gcm/ctrkdf.go | 49 + crypto/internal/fips140/aes/gcm/gcm.go | 144 + .../fips140/aes/gcm}/gcm_amd64.s | 0 .../fips140/aes/gcm}/gcm_arm64.s | 0 crypto/internal/fips140/aes/gcm/gcm_asm.go | 131 + .../internal/fips140/aes/gcm/gcm_generic.go | 105 + crypto/internal/fips140/aes/gcm/gcm_noasm.go | 21 + crypto/internal/fips140/aes/gcm/gcm_nonces.go | 258 + crypto/internal/fips140/aes/gcm/gcm_ppc64x.go | 188 + .../fips140/aes/gcm}/gcm_ppc64x.s | 60 +- crypto/internal/fips140/aes/gcm/gcm_s390x.go | 251 + crypto/internal/fips140/aes/gcm/gcm_s390x.s | 130 + crypto/internal/fips140/aes/gcm/ghash.go | 163 + .../fips140/aes/gcm/interface_test.go | 12 + crypto/internal/fips140/aes/interface_test.go | 15 + crypto/internal/{ => fips140}/alias/alias.go | 0 .../fips140/asan.go} | 7 +- crypto/internal/fips140/bigmod/_asm/go.mod | 12 + crypto/internal/fips140/bigmod/_asm/go.sum | 32 + .../bigmod/_asm/nat_amd64_asm.go | 2 +- crypto/internal/{ => fips140}/bigmod/nat.go | 569 +- .../internal/{ => fips140}/bigmod/nat_386.s | 8 +- .../internal/{ => fips140}/bigmod/nat_amd64.s | 0 .../internal/{ => fips140}/bigmod/nat_arm.s | 8 +- .../internal/{ => fips140}/bigmod/nat_arm64.s | 8 +- .../internal/{ => fips140}/bigmod/nat_asm.go | 13 +- .../{ => fips140}/bigmod/nat_loong64.s | 10 +- .../{ => fips140}/bigmod/nat_noasm.go | 2 +- .../{ => fips140}/bigmod/nat_ppc64x.s | 8 +- .../{ => fips140}/bigmod/nat_riscv64.s | 8 +- .../internal/{ => fips140}/bigmod/nat_s390x.s | 8 +- .../internal/{ => fips140}/bigmod/nat_test.go | 310 +- crypto/internal/fips140/bigmod/nat_wasm.go | 61 + .../fips140/bigmod/testdata/mod_inv_tests.txt | 115 + crypto/internal/fips140/boring.go | 10 + crypto/internal/fips140/cast.go | 83 + crypto/internal/fips140/check/check.go | 109 + crypto/internal/fips140/check/checktest/asm.s | 10 + .../fips140/check/checktest/asm_386.s | 23 + .../fips140/check/checktest/asm_amd64.s | 23 + .../fips140/check/checktest/asm_arm.s | 23 + .../fips140/check/checktest/asm_arm64.s | 23 + .../fips140/check/checktest/asm_none.go | 12 + .../fips140/check/checktest/asm_stub.go | 12 + .../internal/fips140/check/checktest/test.go | 63 + crypto/internal/fips140/drbg/cast.go | 60 + crypto/internal/fips140/drbg/ctrdrbg.go | 136 + crypto/internal/fips140/drbg/rand.go | 101 + crypto/internal/fips140/drbg/rand_test.go | 28 + crypto/internal/fips140/ecdh/cast.go | 54 + crypto/internal/fips140/ecdh/ecdh.go | 310 + crypto/internal/fips140/ecdh/order_test.go | 27 + crypto/internal/fips140/ecdsa/cast.go | 138 + crypto/internal/fips140/ecdsa/ecdsa.go | 504 ++ crypto/internal/fips140/ecdsa/ecdsa_noasm.go | 15 + .../fips140}/ecdsa/ecdsa_s390x.go | 146 +- .../fips140}/ecdsa/ecdsa_s390x.s | 0 crypto/internal/fips140/ecdsa/ecdsa_test.go | 98 + crypto/internal/fips140/ecdsa/hmacdrbg.go | 175 + crypto/internal/fips140/ed25519/cast.go | 78 + crypto/internal/fips140/ed25519/ed25519.go | 342 ++ .../{ => fips140}/edwards25519/doc.go | 0 .../edwards25519/edwards25519.go | 5 +- .../edwards25519/edwards25519_test.go | 20 +- .../edwards25519/field/_asm/fe_amd64_asm.go | 2 +- .../fips140/edwards25519/field/_asm/go.mod | 12 + .../fips140/edwards25519/field/_asm/go.sum | 32 + .../{ => fips140}/edwards25519/field/fe.go | 45 +- .../edwards25519/field/fe_alias_test.go | 0 .../edwards25519/field/fe_amd64.go | 0 .../edwards25519/field/fe_amd64.s | 0 .../edwards25519/field/fe_amd64_noasm.go | 0 .../edwards25519/field/fe_arm64.go | 0 .../edwards25519/field/fe_arm64.s | 0 .../edwards25519/field/fe_arm64_noasm.go | 0 .../edwards25519/field/fe_bench_test.go | 8 + .../edwards25519/field/fe_generic.go | 0 .../edwards25519/field/fe_test.go | 46 +- .../{ => fips140}/edwards25519/scalar.go | 32 +- .../edwards25519/scalar_alias_test.go | 0 .../{ => fips140}/edwards25519/scalar_fiat.go | 0 .../{ => fips140}/edwards25519/scalar_test.go | 35 +- .../{ => fips140}/edwards25519/scalarmult.go | 0 .../edwards25519/scalarmult_test.go | 0 .../{ => fips140}/edwards25519/tables.go | 2 +- .../{ => fips140}/edwards25519/tables_test.go | 0 crypto/internal/fips140/fips140.go | 68 + crypto/internal/fips140/hash.go | 32 + crypto/internal/fips140/hkdf/cast.go | 35 + crypto/internal/fips140/hkdf/hkdf.go | 56 + crypto/internal/fips140/hmac/cast.go | 35 + crypto/internal/fips140/hmac/hmac.go | 172 + crypto/internal/fips140/indicator.go | 62 + crypto/internal/fips140/mlkem/cast.go | 55 + crypto/internal/fips140/mlkem/field.go | 551 ++ crypto/internal/fips140/mlkem/field_test.go | 270 + crypto/internal/fips140/mlkem/generate1024.go | 128 + crypto/internal/fips140/mlkem/mlkem1024.go | 458 ++ crypto/internal/fips140/mlkem/mlkem768.go | 518 ++ crypto/internal/fips140/nistec/_asm/go.mod | 11 + crypto/internal/fips140/nistec/_asm/go.sum | 8 + .../nistec/_asm/p256_asm.go} | 224 +- .../internal/fips140/nistec/benchmark_test.go | 73 + .../{ => fips140}/nistec/fiat/Dockerfile | 0 .../internal/{ => fips140}/nistec/fiat/README | 0 .../nistec/fiat/benchmark_test.go} | 3 +- crypto/internal/fips140/nistec/fiat/cast.go | 7 + .../{ => fips140}/nistec/fiat/generate.go | 2 +- .../{ => fips140}/nistec/fiat/p224.go | 2 +- .../{ => fips140}/nistec/fiat/p224_fiat64.go | 0 .../{ => fips140}/nistec/fiat/p224_invert.go | 0 .../{ => fips140}/nistec/fiat/p256.go | 2 +- .../{ => fips140}/nistec/fiat/p256_fiat64.go | 0 .../{ => fips140}/nistec/fiat/p256_invert.go | 0 .../{ => fips140}/nistec/fiat/p384.go | 2 +- .../{ => fips140}/nistec/fiat/p384_fiat64.go | 0 .../{ => fips140}/nistec/fiat/p384_invert.go | 0 .../{ => fips140}/nistec/fiat/p521.go | 2 +- .../{ => fips140}/nistec/fiat/p521_fiat64.go | 0 .../{ => fips140}/nistec/fiat/p521_invert.go | 0 .../internal/{ => fips140}/nistec/generate.go | 25 +- .../internal/{ => fips140}/nistec/nistec.go | 4 +- crypto/internal/{ => fips140}/nistec/p224.go | 4 +- .../{ => fips140}/nistec/p224_sqrt.go | 3 +- crypto/internal/fips140/nistec/p256.go | 706 +++ .../internal/{ => fips140}/nistec/p256_asm.go | 64 +- .../{ => fips140}/nistec/p256_asm_amd64.s | 154 +- .../{ => fips140}/nistec/p256_asm_arm64.s | 141 +- .../{ => fips140}/nistec/p256_asm_ppc64le.s | 182 +- .../{ => fips140}/nistec/p256_asm_s390x.s | 156 +- .../internal/fips140/nistec/p256_asm_test.go | 53 + .../{ => fips140}/nistec/p256_ordinv.go | 0 .../{ => fips140}/nistec/p256_ordinv_noasm.go | 0 crypto/internal/fips140/nistec/p256_table.go | 10 + .../nistec/p256_table_test.go} | 24 +- crypto/internal/{ => fips140}/nistec/p384.go | 4 +- crypto/internal/{ => fips140}/nistec/p521.go | 4 +- crypto/internal/fips140/notasan.go | 9 + crypto/internal/fips140/notboring.go | 9 + crypto/internal/fips140/pbkdf2/cast.go | 44 + crypto/internal/fips140/pbkdf2/pbkdf2.go | 88 + crypto/internal/fips140/rsa/cast.go | 236 + crypto/internal/fips140/rsa/keygen.go | 388 ++ crypto/internal/fips140/rsa/keygen_test.go | 182 + crypto/internal/fips140/rsa/pkcs1v15.go | 139 + crypto/internal/fips140/rsa/pkcs1v15_test.go | 77 + crypto/internal/fips140/rsa/pkcs1v22.go | 473 ++ crypto/internal/fips140/rsa/pkcs1v22_test.go | 65 + crypto/internal/fips140/rsa/rsa.go | 460 ++ .../fips140/rsa/testdata/gcd_lcm_tests.txt | 279 + .../rsa/testdata/miller_rabin_tests.txt | 344 ++ crypto/internal/fips140/sha256/_asm/go.mod | 11 + crypto/internal/fips140/sha256/_asm/go.sum | 8 + .../sha256/_asm/sha256block_amd64_asm.go | 326 + .../sha256/_asm/sha256block_amd64_avx2.go} | 891 +-- .../sha256/_asm/sha256block_amd64_shani.go | 174 + crypto/internal/fips140/sha256/cast.go | 33 + crypto/internal/fips140/sha256/sha256.go | 232 + .../fips140}/sha256/sha256block.go | 4 +- .../fips140}/sha256/sha256block_386.s | 0 .../fips140/sha256/sha256block_amd64.go | 39 + .../fips140}/sha256/sha256block_amd64.s | 315 +- .../fips140/sha256/sha256block_arm64.go | 29 + .../fips140}/sha256/sha256block_arm64.s | 12 +- .../fips140/sha256/sha256block_asm.go} | 4 +- .../fips140}/sha256/sha256block_loong64.s | 2 +- .../fips140/sha256/sha256block_noasm.go} | 2 +- .../fips140/sha256/sha256block_ppc64x.go | 33 + .../fips140}/sha256/sha256block_ppc64x.s | 4 +- .../fips140}/sha256/sha256block_riscv64.s | 4 +- .../fips140/sha256/sha256block_s390x.go | 31 + .../fips140}/sha256/sha256block_s390x.s | 9 +- crypto/internal/fips140/sha3/_asm/go.mod | 11 + crypto/internal/fips140/sha3/_asm/go.sum | 8 + .../fips140/sha3/_asm/keccakf_amd64_asm.go | 443 ++ crypto/internal/fips140/sha3/cast.go | 33 + crypto/internal/fips140/sha3/hashes.go | 59 + crypto/internal/fips140/sha3/keccakf.go | 432 ++ crypto/internal/fips140/sha3/sha3.go | 236 + crypto/internal/fips140/sha3/sha3_amd64.go | 20 + crypto/internal/fips140/sha3/sha3_amd64.s | 5419 +++++++++++++++++ crypto/internal/fips140/sha3/sha3_noasm.go | 21 + crypto/internal/fips140/sha3/sha3_s390x.go | 196 + crypto/internal/fips140/sha3/sha3_s390x.s | 32 + crypto/internal/fips140/sha3/shake.go | 152 + crypto/internal/fips140/sha512/_asm/go.mod | 11 + crypto/internal/fips140/sha512/_asm/go.sum | 8 + .../sha512/_asm/sha512block_amd64_asm.go | 12 +- crypto/internal/fips140/sha512/cast.go | 37 + crypto/internal/fips140/sha512/sha512.go | 302 + .../fips140}/sha512/sha512block.go | 4 +- .../fips140/sha512/sha512block_amd64.go | 32 + .../fips140}/sha512/sha512block_amd64.s | 8 +- .../fips140/sha512/sha512block_arm64.go | 29 + .../fips140}/sha512/sha512block_arm64.s | 6 +- .../fips140/sha512/sha512block_asm.go} | 4 +- .../fips140}/sha512/sha512block_loong64.s | 2 +- .../fips140/sha512/sha512block_noasm.go} | 2 +- .../fips140/sha512/sha512block_ppc64x.go | 33 + .../fips140}/sha512/sha512block_ppc64x.s | 4 +- .../fips140}/sha512/sha512block_riscv64.s | 28 +- .../fips140/sha512/sha512block_s390x.go | 31 + .../fips140}/sha512/sha512block_s390x.s | 9 +- crypto/internal/fips140/ssh/kdf.go | 56 + .../internal/fips140/subtle/constant_time.go | 60 + crypto/internal/fips140/subtle/xor.go | 30 + .../{ => internal/fips140}/subtle/xor_amd64.s | 0 .../{ => internal/fips140}/subtle/xor_arm64.s | 0 .../fips140/subtle/xor_asm.go} | 2 +- .../fips140}/subtle/xor_generic.go | 2 +- .../fips140}/subtle/xor_loong64.s | 0 .../fips140}/subtle/xor_ppc64x.s | 0 crypto/internal/fips140/subtle/xor_riscv64.s | 169 + crypto/internal/fips140/tls12/cast.go | 40 + crypto/internal/fips140/tls12/tls12.go | 69 + crypto/internal/fips140/tls13/cast.go | 39 + crypto/internal/fips140/tls13/tls13.go | 178 + .../fips140deps/byteorder/byteorder.go | 53 + crypto/internal/fips140deps/cpu/cpu.go | 39 + crypto/internal/fips140deps/fipsdeps.go | 9 + crypto/internal/fips140deps/fipsdeps_test.go | 108 + .../internal/fips140deps/godebug/godebug.go | 23 + crypto/internal/fips140hash/hash.go | 35 + crypto/internal/fips140only/fips140only.go | 34 + .../fips140test/acvp_capabilities.json | 82 + .../fips140test/acvp_test.config.json | 57 + crypto/internal/fips140test/acvp_test.go | 2258 +++++++ .../{alias => fips140test}/alias_test.go | 12 +- crypto/internal/fips140test/cast_test.go | 191 + crypto/internal/fips140test/check_test.go | 169 + crypto/internal/fips140test/cmac_test.go | 48 + crypto/internal/fips140test/ctrdrbg_test.go | 41 + .../internal/fips140test/edwards25519_test.go | 27 + crypto/internal/fips140test/fips_test.go | 443 ++ crypto/internal/fips140test/indicator_test.go | 77 + .../nistec_ordinv_test.go} | 7 +- .../{nistec => fips140test}/nistec_test.go | 71 +- crypto/internal/fips140test/sshkdf_test.go | 52 + crypto/internal/fips140test/xaes_test.go | 150 + crypto/internal/hpke/hpke.go | 200 +- crypto/internal/hpke/hpke_test.go | 70 +- crypto/internal/impl/impl.go | 107 + crypto/internal/mlkem768/mlkem768.go | 888 --- crypto/internal/mlkem768/mlkem768_test.go | 468 -- crypto/internal/nistec/_asm/go.mod | 0 crypto/internal/nistec/_asm/go.sum | 0 crypto/internal/nistec/p256.go | 509 -- crypto/internal/nistec/p256_asm_table.bin | Bin 88064 -> 0 bytes .../sysrand/internal/seccomp/seccomp_linux.go | 83 + .../internal/seccomp/seccomp_unsupported.go | 13 + crypto/internal/sysrand/rand.go | 77 + crypto/internal/sysrand/rand_aix.go | 9 + crypto/internal/sysrand/rand_arc4random.go | 22 + crypto/internal/sysrand/rand_getrandom.go | 64 + crypto/internal/sysrand/rand_js.go | 27 + crypto/internal/sysrand/rand_linux_test.go | 69 + crypto/internal/sysrand/rand_netbsd.go | 24 + crypto/internal/sysrand/rand_plan9.go | 72 + crypto/internal/sysrand/rand_test.go | 118 + crypto/internal/sysrand/rand_wasip1.go | 15 + crypto/internal/sysrand/rand_windows.go | 11 + crypto/md5/gen.go | 2 +- crypto/md5/md5.go | 38 +- crypto/md5/md5_test.go | 1 + crypto/md5/md5block.go | 32 +- crypto/mlkem/example_test.go | 48 + crypto/mlkem/mlkem.go | 192 + crypto/mlkem/mlkem_test.go | 335 + crypto/pbkdf2/pbkdf2.go | 55 + crypto/pbkdf2/pbkdf2_test.go | 254 + crypto/rc4/rc4.go | 7 +- crypto/{aes/_asm/gcm/go.sum => rsa/boring.go} | 0 crypto/rsa/boring_test.go | 152 + crypto/rsa/equal_test.go | 10 +- crypto/rsa/example_test.go | 60 +- crypto/rsa/fips.go | 436 ++ crypto/rsa/pkcs1v15.go | 167 +- crypto/rsa/pkcs1v15_test.go | 37 +- crypto/rsa/pss.go | 386 -- crypto/rsa/pss_test.go | 110 +- crypto/rsa/rsa.go | 629 +- crypto/rsa/rsa_export_test.go | 3 - crypto/rsa/rsa_test.go | 337 +- crypto/rsa/testdata/keygen2048.txt | 719 +++ crypto/sha1/sha1.go | 49 +- crypto/sha1/sha1_test.go | 4 +- crypto/sha1/sha1block_amd64.go | 2 +- crypto/sha256/_asm/go.mod | 0 crypto/sha256/_asm/go.sum | 0 crypto/sha256/fallback_test.go | 35 - crypto/sha256/sha256.go | 230 +- crypto/sha256/sha256_test.go | 80 +- crypto/sha256/sha256block_amd64.go | 12 - crypto/sha256/sha256block_arm64.go | 23 - crypto/sha256/sha256block_s390x.go | 11 - crypto/sha3/sha3.go | 240 + crypto/sha3/sha3_test.go | 502 ++ crypto/sha512/_asm/go.mod | 0 crypto/sha512/_asm/go.sum | 0 crypto/sha512/fallback_test.go | 37 - crypto/sha512/sha512.go | 327 +- crypto/sha512/sha512_test.go | 99 +- crypto/sha512/sha512block_amd64.go | 25 - crypto/sha512/sha512block_arm64.go | 20 - crypto/sha512/sha512block_s390x.go | 11 - crypto/ssl3/tls/handshake_client.go | 3 +- crypto/ssl3/zcrypto_schemas/__init__.py | 0 crypto/subtle/constant_time.go | 36 +- crypto/subtle/dit.go | 50 + crypto/subtle/dit_test.go | 65 + crypto/subtle/xor.go | 16 +- crypto/subtle/xor_amd64.go | 10 - crypto/subtle/xor_arm64.go | 10 - crypto/subtle/xor_linux_test.go | 47 + crypto/subtle/xor_test.go | 85 +- crypto/tls/auth.go | 5 +- crypto/tls/auth_test.go | 9 +- crypto/tls/bogo_config.json | 25 +- crypto/tls/bogo_shim_test.go | 79 +- crypto/tls/cipher_suites.go | 34 +- crypto/tls/common.go | 196 +- crypto/tls/common_string.go | 6 +- crypto/tls/conn_test.go | 4 + crypto/tls/defaults.go | 13 +- crypto/tls/ech.go | 483 +- crypto/tls/fips_test.go | 675 ++ crypto/tls/handshake_client.go | 301 +- crypto/tls/handshake_client_test.go | 133 +- crypto/tls/handshake_client_tls13.go | 94 +- crypto/tls/handshake_messages.go | 4 + crypto/tls/handshake_messages_test.go | 3 + crypto/tls/handshake_server.go | 83 +- crypto/tls/handshake_server_test.go | 111 +- crypto/tls/handshake_server_tls13.go | 241 +- crypto/tls/handshake_test.go | 14 + crypto/tls/internal/fips140tls/fipstls.go | 38 + crypto/tls/key_schedule.go | 124 +- crypto/tls/key_schedule_test.go | 230 +- crypto/tls/notboring.go | 7 - crypto/tls/prf.go | 65 +- .../Client-TLSv10-ClientCert-ECDSA-ECDSA | 80 +- .../Client-TLSv10-ClientCert-ECDSA-RSA | 80 +- .../Client-TLSv12-ClientCert-ECDSA-ECDSA | 96 +- .../Client-TLSv12-ClientCert-ECDSA-RSA | 78 +- .../Client-TLSv13-ClientCert-ECDSA-RSA | 236 +- .../testdata/Server-TLSv10-ECDHE-ECDSA-AES | 66 +- .../testdata/Server-TLSv12-ECDHE-ECDSA-AES | 64 +- .../testdata/Server-TLSv13-ECDHE-ECDSA-AES | 158 +- crypto/tls/tls.go | 9 + crypto/tls/tls_test.go | 244 +- crypto/x509/boring.go | 41 - crypto/x509/boring_test.go | 147 - crypto/x509/cert_pool_test.go | 3 +- crypto/x509/name_constraints_test.go | 20 +- crypto/x509/notboring.go | 9 - crypto/x509/oid_test.go | 63 +- crypto/x509/parser.go | 170 +- crypto/x509/parser_test.go | 85 +- crypto/x509/pkcs1.go | 48 +- crypto/x509/pkcs8.go | 9 + crypto/x509/pkits_test.go | 186 + crypto/x509/revocation/crl/test_crl | Bin 1029676 -> 0 bytes .../google/testdata/APm1SaUzZaPllaSDuZS5yng | Bin 24898 -> 0 bytes .../revocation/google/testdata/crl-set-6375 | Bin 20909 -> 0 bytes .../revocation/google/testdata/test_crlset | Bin 12954 -> 0 bytes .../microsoft/test_disallowedcert.sst | Bin 99369 -> 0 bytes crypto/x509/testdata/nist-pkits/README.md | 6 + .../AllCertificatesNoPoliciesTest2EE.crt | Bin 0 -> 898 bytes .../AllCertificatesSamePoliciesTest10EE.crt | Bin 0 -> 941 bytes .../AllCertificatesSamePoliciesTest13EE.crt | Bin 0 -> 958 bytes .../AllCertificatesanyPolicyTest11EE.crt | Bin 0 -> 914 bytes .../nist-pkits/certs/AnyPolicyTest14EE.crt | Bin 0 -> 903 bytes .../certs/BadCRLIssuerNameCACert.crt | Bin 0 -> 911 bytes .../certs/BadCRLSignatureCACert.crt | Bin 0 -> 909 bytes .../nist-pkits/certs/BadSignedCACert.crt | Bin 0 -> 902 bytes .../certs/BadnotAfterDateCACert.crt | Bin 0 -> 909 bytes .../certs/BadnotBeforeDateCACert.crt | Bin 0 -> 910 bytes .../BasicSelfIssuedCRLSigningKeyCACert.crt | Bin 0 -> 925 bytes .../BasicSelfIssuedCRLSigningKeyCRLCert.crt | Bin 0 -> 1074 bytes .../certs/BasicSelfIssuedNewKeyCACert.crt | Bin 0 -> 917 bytes .../BasicSelfIssuedNewKeyOldWithNewCACert.crt | Bin 0 -> 933 bytes .../certs/BasicSelfIssuedOldKeyCACert.crt | Bin 0 -> 917 bytes .../BasicSelfIssuedOldKeyNewWithOldCACert.crt | Bin 0 -> 1067 bytes .../certs/CPSPointerQualifierTest20EE.crt | Bin 0 -> 1011 bytes .../testdata/nist-pkits/certs/DSACACert.crt | Bin 0 -> 1045 bytes .../certs/DSAParametersInheritedCACert.crt | Bin 0 -> 546 bytes .../certs/DifferentPoliciesTest12EE.crt | Bin 0 -> 914 bytes .../certs/DifferentPoliciesTest3EE.crt | Bin 0 -> 916 bytes .../certs/DifferentPoliciesTest4EE.crt | Bin 0 -> 909 bytes .../certs/DifferentPoliciesTest5EE.crt | Bin 0 -> 917 bytes .../certs/DifferentPoliciesTest7EE.crt | Bin 0 -> 943 bytes .../certs/DifferentPoliciesTest8EE.crt | Bin 0 -> 941 bytes .../certs/DifferentPoliciesTest9EE.crt | Bin 0 -> 931 bytes .../GeneralizedTimeCRLnextUpdateCACert.crt | Bin 0 -> 920 bytes .../testdata/nist-pkits/certs/GoodCACert.crt | Bin 0 -> 896 bytes .../nist-pkits/certs/GoodsubCACert.crt | Bin 0 -> 910 bytes .../GoodsubCAPanyPolicyMapping1to2CACert.crt | Bin 0 -> 968 bytes .../certs/InvalidBadCRLIssuerNameTest5EE.crt | Bin 0 -> 930 bytes .../certs/InvalidBadCRLSignatureTest4EE.crt | Bin 0 -> 926 bytes ...lidBasicSelfIssuedCRLSigningKeyTest7EE.crt | Bin 0 -> 958 bytes ...lidBasicSelfIssuedCRLSigningKeyTest8EE.crt | Bin 0 -> 958 bytes ...nvalidBasicSelfIssuedNewWithOldTest5EE.crt | Bin 0 -> 947 bytes ...nvalidBasicSelfIssuedOldWithNewTest2EE.crt | Bin 0 -> 947 bytes .../certs/InvalidCASignatureTest2EE.crt | Bin 0 -> 899 bytes .../certs/InvalidCAnotAfterDateTest5EE.crt | Bin 0 -> 925 bytes .../certs/InvalidCAnotBeforeDateTest1EE.crt | Bin 0 -> 927 bytes .../InvalidDNSnameConstraintsTest31EE.crt | Bin 0 -> 981 bytes .../InvalidDNSnameConstraintsTest33EE.crt | Bin 0 -> 970 bytes .../InvalidDNSnameConstraintsTest38EE.crt | Bin 0 -> 969 bytes ...alidDNandRFC822nameConstraintsTest28EE.crt | Bin 0 -> 1049 bytes ...alidDNandRFC822nameConstraintsTest29EE.crt | Bin 0 -> 1051 bytes .../InvalidDNnameConstraintsTest10EE.crt | Bin 0 -> 986 bytes .../InvalidDNnameConstraintsTest12EE.crt | Bin 0 -> 991 bytes .../InvalidDNnameConstraintsTest13EE.crt | Bin 0 -> 991 bytes .../InvalidDNnameConstraintsTest15EE.crt | Bin 0 -> 962 bytes .../InvalidDNnameConstraintsTest16EE.crt | Bin 0 -> 962 bytes .../InvalidDNnameConstraintsTest17EE.crt | Bin 0 -> 962 bytes .../InvalidDNnameConstraintsTest20EE.crt | Bin 0 -> 904 bytes .../certs/InvalidDNnameConstraintsTest2EE.crt | Bin 0 -> 957 bytes .../certs/InvalidDNnameConstraintsTest3EE.crt | Bin 0 -> 1113 bytes .../certs/InvalidDNnameConstraintsTest7EE.crt | Bin 0 -> 957 bytes .../certs/InvalidDNnameConstraintsTest8EE.crt | Bin 0 -> 957 bytes .../certs/InvalidDNnameConstraintsTest9EE.crt | Bin 0 -> 957 bytes .../certs/InvalidDSASignatureTest6EE.crt | Bin 0 -> 851 bytes .../certs/InvalidEESignatureTest3EE.crt | Bin 0 -> 893 bytes .../certs/InvalidEEnotAfterDateTest6EE.crt | Bin 0 -> 912 bytes .../certs/InvalidEEnotBeforeDateTest2EE.crt | Bin 0 -> 913 bytes .../InvalidIDPwithindirectCRLTest23EE.crt | Bin 0 -> 925 bytes .../InvalidIDPwithindirectCRLTest26EE.crt | Bin 0 -> 1019 bytes .../certs/InvalidLongSerialNumberTest18EE.crt | Bin 0 -> 948 bytes .../InvalidMappingFromanyPolicyTest7EE.crt | Bin 0 -> 936 bytes .../InvalidMappingToanyPolicyTest8EE.crt | Bin 0 -> 926 bytes .../certs/InvalidMissingCRLTest1EE.crt | Bin 0 -> 909 bytes .../InvalidMissingbasicConstraintsTest1EE.crt | Bin 0 -> 940 bytes .../certs/InvalidNameChainingOrderTest2EE.crt | Bin 0 -> 999 bytes .../certs/InvalidNameChainingTest1EE.crt | Bin 0 -> 914 bytes .../InvalidNegativeSerialNumberTest15EE.crt | Bin 0 -> 937 bytes .../certs/InvalidOldCRLnextUpdateTest11EE.crt | Bin 0 -> 929 bytes .../certs/InvalidPolicyMappingTest10EE.crt | Bin 0 -> 938 bytes .../certs/InvalidPolicyMappingTest2EE.crt | Bin 0 -> 918 bytes .../certs/InvalidPolicyMappingTest4EE.crt | Bin 0 -> 928 bytes .../InvalidRFC822nameConstraintsTest22EE.crt | Bin 0 -> 982 bytes .../InvalidRFC822nameConstraintsTest24EE.crt | Bin 0 -> 993 bytes .../InvalidRFC822nameConstraintsTest26EE.crt | Bin 0 -> 982 bytes .../certs/InvalidRevokedCATest2EE.crt | Bin 0 -> 909 bytes .../certs/InvalidRevokedEETest3EE.crt | Bin 0 -> 903 bytes ...alidSelfIssuedinhibitAnyPolicyTest10EE.crt | Bin 0 -> 919 bytes ...validSelfIssuedinhibitAnyPolicyTest8EE.crt | Bin 0 -> 944 bytes ...SelfIssuedinhibitPolicyMappingTest10EE.crt | Bin 0 -> 952 bytes ...SelfIssuedinhibitPolicyMappingTest11EE.crt | Bin 0 -> 952 bytes ...dSelfIssuedinhibitPolicyMappingTest8EE.crt | Bin 0 -> 954 bytes ...dSelfIssuedinhibitPolicyMappingTest9EE.crt | Bin 0 -> 954 bytes ...lidSelfIssuedpathLenConstraintTest16EE.crt | Bin 0 -> 944 bytes ...SelfIssuedrequireExplicitPolicyTest7EE.crt | Bin 0 -> 925 bytes ...SelfIssuedrequireExplicitPolicyTest8EE.crt | Bin 0 -> 925 bytes ...dSeparateCertificateandCRLKeysTest20EE.crt | Bin 0 -> 960 bytes ...dSeparateCertificateandCRLKeysTest21EE.crt | Bin 0 -> 960 bytes .../InvalidURInameConstraintsTest35EE.crt | Bin 0 -> 987 bytes .../InvalidURInameConstraintsTest37EE.crt | Bin 0 -> 987 bytes ...InvalidUnknownCRLEntryExtensionTest8EE.crt | Bin 0 -> 946 bytes .../InvalidUnknownCRLExtensionTest10EE.crt | Bin 0 -> 935 bytes .../InvalidUnknownCRLExtensionTest9EE.crt | Bin 0 -> 934 bytes ...ownCriticalCertificateExtensionTest2EE.crt | Bin 0 -> 954 bytes .../certs/InvalidWrongCRLTest6EE.crt | Bin 0 -> 910 bytes .../certs/InvalidcAFalseTest2EE.crt | Bin 0 -> 934 bytes .../certs/InvalidcAFalseTest3EE.crt | Bin 0 -> 938 bytes .../certs/InvalidcRLIssuerTest27EE.crt | Bin 0 -> 999 bytes .../certs/InvalidcRLIssuerTest31EE.crt | Bin 0 -> 1136 bytes .../certs/InvalidcRLIssuerTest32EE.crt | Bin 0 -> 1136 bytes .../certs/InvalidcRLIssuerTest34EE.crt | Bin 0 -> 1044 bytes .../certs/InvalidcRLIssuerTest35EE.crt | Bin 0 -> 1128 bytes .../InvaliddeltaCRLIndicatorNoBaseTest1EE.crt | Bin 0 -> 942 bytes .../certs/InvaliddeltaCRLTest10EE.crt | Bin 0 -> 1094 bytes .../certs/InvaliddeltaCRLTest3EE.crt | Bin 0 -> 1093 bytes .../certs/InvaliddeltaCRLTest4EE.crt | Bin 0 -> 1093 bytes .../certs/InvaliddeltaCRLTest6EE.crt | Bin 0 -> 1093 bytes .../certs/InvaliddeltaCRLTest9EE.crt | Bin 0 -> 1093 bytes .../certs/InvaliddistributionPointTest2EE.crt | Bin 0 -> 1071 bytes .../certs/InvaliddistributionPointTest3EE.crt | Bin 0 -> 1071 bytes .../certs/InvaliddistributionPointTest6EE.crt | Bin 0 -> 984 bytes .../certs/InvaliddistributionPointTest8EE.crt | Bin 0 -> 1028 bytes .../certs/InvaliddistributionPointTest9EE.crt | Bin 0 -> 927 bytes .../certs/InvalidinhibitAnyPolicyTest1EE.crt | Bin 0 -> 919 bytes .../certs/InvalidinhibitAnyPolicyTest4EE.crt | Bin 0 -> 923 bytes .../certs/InvalidinhibitAnyPolicyTest5EE.crt | Bin 0 -> 925 bytes .../certs/InvalidinhibitAnyPolicyTest6EE.crt | Bin 0 -> 926 bytes .../InvalidinhibitPolicyMappingTest1EE.crt | Bin 0 -> 950 bytes .../InvalidinhibitPolicyMappingTest3EE.crt | Bin 0 -> 943 bytes .../InvalidinhibitPolicyMappingTest5EE.crt | Bin 0 -> 942 bytes .../InvalidinhibitPolicyMappingTest6EE.crt | Bin 0 -> 947 bytes ...lidkeyUsageCriticalcRLSignFalseTest4EE.crt | Bin 0 -> 954 bytes ...eyUsageCriticalkeyCertSignFalseTest1EE.crt | Bin 0 -> 962 bytes ...keyUsageNotCriticalcRLSignFalseTest5EE.crt | Bin 0 -> 962 bytes ...sageNotCriticalkeyCertSignFalseTest2EE.crt | Bin 0 -> 963 bytes ...alidonlyContainsAttributeCertsTest14EE.crt | Bin 0 -> 945 bytes .../InvalidonlyContainsCACertsTest12EE.crt | Bin 0 -> 931 bytes .../InvalidonlyContainsUserCertsTest11EE.crt | Bin 0 -> 952 bytes .../certs/InvalidonlySomeReasonsTest15EE.crt | Bin 0 -> 924 bytes .../certs/InvalidonlySomeReasonsTest16EE.crt | Bin 0 -> 924 bytes .../certs/InvalidonlySomeReasonsTest17EE.crt | Bin 0 -> 924 bytes .../certs/InvalidonlySomeReasonsTest20EE.crt | Bin 0 -> 1153 bytes .../certs/InvalidonlySomeReasonsTest21EE.crt | Bin 0 -> 1153 bytes .../InvalidpathLenConstraintTest10EE.crt | Bin 0 -> 953 bytes .../InvalidpathLenConstraintTest11EE.crt | Bin 0 -> 940 bytes .../InvalidpathLenConstraintTest12EE.crt | Bin 0 -> 957 bytes .../certs/InvalidpathLenConstraintTest5EE.crt | Bin 0 -> 930 bytes .../certs/InvalidpathLenConstraintTest6EE.crt | Bin 0 -> 947 bytes .../certs/InvalidpathLenConstraintTest9EE.crt | Bin 0 -> 935 bytes .../Invalidpre2000CRLnextUpdateTest12EE.crt | Bin 0 -> 937 bytes ...Invalidpre2000UTCEEnotAfterDateTest7EE.crt | Bin 0 -> 926 bytes .../InvalidrequireExplicitPolicyTest3EE.crt | Bin 0 -> 919 bytes .../InvalidrequireExplicitPolicyTest5EE.crt | Bin 0 -> 925 bytes .../certs/LongSerialNumberCACert.crt | Bin 0 -> 910 bytes .../nist-pkits/certs/Mapping1to2CACert.crt | Bin 0 -> 960 bytes .../certs/MappingFromanyPolicyCACert.crt | Bin 0 -> 961 bytes .../certs/MappingToanyPolicyCACert.crt | Bin 0 -> 965 bytes .../certs/MissingbasicConstraintsCACert.crt | Bin 0 -> 899 bytes .../nist-pkits/certs/NameOrderingCACert.crt | Bin 0 -> 980 bytes .../certs/NegativeSerialNumberCACert.crt | Bin 0 -> 914 bytes .../testdata/nist-pkits/certs/NoCRLCACert.crt | Bin 0 -> 898 bytes .../nist-pkits/certs/NoPoliciesCACert.crt | Bin 0 -> 878 bytes .../NoissuingDistributionPointCACert.crt | Bin 0 -> 919 bytes .../certs/OldCRLnextUpdateCACert.crt | Bin 0 -> 910 bytes .../certs/OverlappingPoliciesTest6EE.crt | Bin 0 -> 948 bytes .../nist-pkits/certs/P12Mapping1to3CACert.crt | Bin 0 -> 978 bytes .../certs/P12Mapping1to3subCACert.crt | Bin 0 -> 1000 bytes .../certs/P12Mapping1to3subsubCACert.crt | Bin 0 -> 980 bytes .../certs/P1Mapping1to234CACert.crt | Bin 0 -> 1017 bytes .../certs/P1Mapping1to234subCACert.crt | Bin 0 -> 1002 bytes .../certs/P1anyPolicyMapping1to2CACert.crt | Bin 0 -> 1329 bytes .../certs/PanyPolicyMapping1to2CACert.crt | Bin 0 -> 965 bytes .../nist-pkits/certs/PoliciesP1234CACert.crt | Bin 0 -> 964 bytes .../certs/PoliciesP1234subCAP123Cert.crt | Bin 0 -> 948 bytes .../PoliciesP1234subsubCAP123P12Cert.crt | Bin 0 -> 947 bytes .../nist-pkits/certs/PoliciesP123CACert.crt | Bin 0 -> 949 bytes .../certs/PoliciesP123subCAP12Cert.crt | Bin 0 -> 931 bytes .../certs/PoliciesP123subsubCAP12P1Cert.crt | Bin 0 -> 926 bytes .../certs/PoliciesP123subsubCAP12P2Cert.crt | Bin 0 -> 926 bytes .../PoliciesP123subsubsubCAP12P2P1Cert.crt | Bin 0 -> 936 bytes .../nist-pkits/certs/PoliciesP12CACert.crt | Bin 0 -> 934 bytes .../certs/PoliciesP12subCAP1Cert.crt | Bin 0 -> 912 bytes .../certs/PoliciesP12subsubCAP1P2Cert.crt | Bin 0 -> 922 bytes .../nist-pkits/certs/PoliciesP2subCA2Cert.crt | Bin 0 -> 918 bytes .../nist-pkits/certs/PoliciesP2subCACert.crt | Bin 0 -> 901 bytes .../nist-pkits/certs/PoliciesP3CACert.crt | Bin 0 -> 919 bytes .../RFC3280MandatoryAttributeTypesCACert.crt | Bin 0 -> 980 bytes .../RFC3280OptionalAttributeTypesCACert.crt | Bin 0 -> 992 bytes .../nist-pkits/certs/RevokedsubCACert.crt | Bin 0 -> 897 bytes ...rfromPrintableStringtoUTF8StringCACert.crt | Bin 0 -> 935 bytes ...CertificateandCRLKeysCA2CRLSigningCert.crt | Bin 0 -> 909 bytes ...eandCRLKeysCA2CertificateSigningCACert.crt | Bin 0 -> 926 bytes ...ateCertificateandCRLKeysCRLSigningCert.crt | Bin 0 -> 909 bytes ...cateandCRLKeysCertificateSigningCACert.crt | Bin 0 -> 926 bytes .../certs/TrustAnchorRootCertificate.crt | Bin 0 -> 843 bytes .../nist-pkits/certs/TwoCRLsCACert.crt | Bin 0 -> 900 bytes .../testdata/nist-pkits/certs/UIDCACert.crt | Bin 0 -> 900 bytes .../UTF8StringCaseInsensitiveMatchCACert.crt | Bin 0 -> 925 bytes .../certs/UTF8StringEncodedNamesCACert.crt | Bin 0 -> 902 bytes .../certs/UnknownCRLEntryExtensionCACert.crt | Bin 0 -> 919 bytes .../certs/UnknownCRLExtensionCACert.crt | Bin 0 -> 913 bytes .../certs/UserNoticeQualifierTest15EE.crt | Bin 0 -> 1026 bytes .../certs/UserNoticeQualifierTest16EE.crt | Bin 0 -> 1145 bytes .../certs/UserNoticeQualifierTest17EE.crt | Bin 0 -> 1014 bytes .../certs/UserNoticeQualifierTest18EE.crt | Bin 0 -> 1240 bytes .../certs/UserNoticeQualifierTest19EE.crt | Bin 0 -> 1263 bytes ...lidBasicSelfIssuedCRLSigningKeyTest6EE.crt | Bin 0 -> 956 bytes .../ValidBasicSelfIssuedNewWithOldTest3EE.crt | Bin 0 -> 945 bytes .../ValidBasicSelfIssuedNewWithOldTest4EE.crt | Bin 0 -> 945 bytes .../ValidBasicSelfIssuedOldWithNewTest1EE.crt | Bin 0 -> 945 bytes .../certs/ValidCertificatePathTest1EE.crt | Bin 0 -> 893 bytes .../certs/ValidDNSnameConstraintsTest30EE.crt | Bin 0 -> 976 bytes .../certs/ValidDNSnameConstraintsTest32EE.crt | Bin 0 -> 976 bytes ...alidDNandRFC822nameConstraintsTest27EE.crt | Bin 0 -> 1044 bytes .../certs/ValidDNnameConstraintsTest11EE.crt | Bin 0 -> 985 bytes .../certs/ValidDNnameConstraintsTest14EE.crt | Bin 0 -> 926 bytes .../certs/ValidDNnameConstraintsTest18EE.crt | Bin 0 -> 932 bytes .../certs/ValidDNnameConstraintsTest19EE.crt | Bin 0 -> 957 bytes .../certs/ValidDNnameConstraintsTest1EE.crt | Bin 0 -> 956 bytes .../certs/ValidDNnameConstraintsTest4EE.crt | Bin 0 -> 1016 bytes .../certs/ValidDNnameConstraintsTest5EE.crt | Bin 0 -> 1109 bytes .../certs/ValidDNnameConstraintsTest6EE.crt | Bin 0 -> 956 bytes .../ValidDSAParameterInheritanceTest5EE.crt | Bin 0 -> 574 bytes .../certs/ValidDSASignaturesTest4EE.crt | Bin 0 -> 835 bytes ...idGeneralizedTimeCRLnextUpdateTest13EE.crt | Bin 0 -> 949 bytes ...alidGeneralizedTimenotAfterDateTest8EE.crt | Bin 0 -> 925 bytes ...lidGeneralizedTimenotBeforeDateTest4EE.crt | Bin 0 -> 926 bytes .../certs/ValidIDPwithindirectCRLTest22EE.crt | Bin 0 -> 923 bytes .../certs/ValidIDPwithindirectCRLTest24EE.crt | Bin 0 -> 1016 bytes .../certs/ValidIDPwithindirectCRLTest25EE.crt | Bin 0 -> 1016 bytes .../certs/ValidLongSerialNumberTest16EE.crt | Bin 0 -> 946 bytes .../certs/ValidLongSerialNumberTest17EE.crt | Bin 0 -> 946 bytes ...ValidNameChainingCapitalizationTest5EE.crt | Bin 0 -> 922 bytes .../ValidNameChainingWhitespaceTest3EE.crt | Bin 0 -> 923 bytes .../ValidNameChainingWhitespaceTest4EE.crt | Bin 0 -> 924 bytes .../nist-pkits/certs/ValidNameUIDsTest6EE.crt | Bin 0 -> 901 bytes .../ValidNegativeSerialNumberTest14EE.crt | Bin 0 -> 936 bytes ...alidNoissuingDistributionPointTest10EE.crt | Bin 0 -> 1069 bytes .../certs/ValidPolicyMappingTest11EE.crt | Bin 0 -> 936 bytes .../certs/ValidPolicyMappingTest12EE.crt | Bin 0 -> 1339 bytes .../certs/ValidPolicyMappingTest13EE.crt | Bin 0 -> 929 bytes .../certs/ValidPolicyMappingTest14EE.crt | Bin 0 -> 929 bytes .../certs/ValidPolicyMappingTest1EE.crt | Bin 0 -> 916 bytes .../certs/ValidPolicyMappingTest3EE.crt | Bin 0 -> 926 bytes .../certs/ValidPolicyMappingTest5EE.crt | Bin 0 -> 924 bytes .../certs/ValidPolicyMappingTest6EE.crt | Bin 0 -> 924 bytes .../certs/ValidPolicyMappingTest9EE.crt | Bin 0 -> 927 bytes ...dRFC3280MandatoryAttributeTypesTest7EE.crt | Bin 0 -> 1011 bytes ...idRFC3280OptionalAttributeTypesTest8EE.crt | Bin 0 -> 1022 bytes .../ValidRFC822nameConstraintsTest21EE.crt | Bin 0 -> 991 bytes .../ValidRFC822nameConstraintsTest23EE.crt | Bin 0 -> 980 bytes .../ValidRFC822nameConstraintsTest25EE.crt | Bin 0 -> 991 bytes ...romPrintableStringtoUTF8StringTest10EE.crt | Bin 0 -> 965 bytes ...ValidSelfIssuedinhibitAnyPolicyTest7EE.crt | Bin 0 -> 939 bytes ...ValidSelfIssuedinhibitAnyPolicyTest9EE.crt | Bin 0 -> 939 bytes ...dSelfIssuedinhibitPolicyMappingTest7EE.crt | Bin 0 -> 949 bytes ...lidSelfIssuedpathLenConstraintTest15EE.crt | Bin 0 -> 938 bytes ...lidSelfIssuedpathLenConstraintTest17EE.crt | Bin 0 -> 941 bytes ...SelfIssuedrequireExplicitPolicyTest6EE.crt | Bin 0 -> 920 bytes ...dSeparateCertificateandCRLKeysTest19EE.crt | Bin 0 -> 958 bytes .../nist-pkits/certs/ValidTwoCRLsTest7EE.crt | Bin 0 -> 906 bytes .../certs/ValidURInameConstraintsTest34EE.crt | Bin 0 -> 994 bytes .../certs/ValidURInameConstraintsTest36EE.crt | Bin 0 -> 997 bytes ...UTF8StringCaseInsensitiveMatchTest11EE.crt | Bin 0 -> 962 bytes .../ValidUTF8StringEncodedNamesTest9EE.crt | Bin 0 -> 924 bytes ...NotCriticalCertificateExtensionTest1EE.crt | Bin 0 -> 952 bytes ...alidbasicConstraintsNotCriticalTest4EE.crt | Bin 0 -> 948 bytes .../certs/ValidcRLIssuerTest28EE.crt | Bin 0 -> 1156 bytes .../certs/ValidcRLIssuerTest29EE.crt | Bin 0 -> 1065 bytes .../certs/ValidcRLIssuerTest30EE.crt | Bin 0 -> 1156 bytes .../certs/ValidcRLIssuerTest33EE.crt | Bin 0 -> 1134 bytes .../nist-pkits/certs/ValiddeltaCRLTest2EE.crt | Bin 0 -> 1091 bytes .../nist-pkits/certs/ValiddeltaCRLTest5EE.crt | Bin 0 -> 1091 bytes .../nist-pkits/certs/ValiddeltaCRLTest7EE.crt | Bin 0 -> 1091 bytes .../nist-pkits/certs/ValiddeltaCRLTest8EE.crt | Bin 0 -> 1091 bytes .../certs/ValiddistributionPointTest1EE.crt | Bin 0 -> 1069 bytes .../certs/ValiddistributionPointTest4EE.crt | Bin 0 -> 982 bytes .../certs/ValiddistributionPointTest5EE.crt | Bin 0 -> 982 bytes .../certs/ValiddistributionPointTest7EE.crt | Bin 0 -> 1069 bytes .../certs/ValidinhibitAnyPolicyTest2EE.crt | Bin 0 -> 931 bytes .../ValidinhibitPolicyMappingTest2EE.crt | Bin 0 -> 938 bytes .../ValidinhibitPolicyMappingTest4EE.crt | Bin 0 -> 941 bytes .../certs/ValidkeyUsageNotCriticalTest3EE.crt | Bin 0 -> 932 bytes .../ValidonlyContainsCACertsTest13EE.crt | Bin 0 -> 946 bytes .../certs/ValidonlySomeReasonsTest18EE.crt | Bin 0 -> 1035 bytes .../certs/ValidonlySomeReasonsTest19EE.crt | Bin 0 -> 1151 bytes .../certs/ValidpathLenConstraintTest13EE.crt | Bin 0 -> 938 bytes .../certs/ValidpathLenConstraintTest14EE.crt | Bin 0 -> 955 bytes .../certs/ValidpathLenConstraintTest7EE.crt | Bin 0 -> 925 bytes .../certs/ValidpathLenConstraintTest8EE.crt | Bin 0 -> 942 bytes .../Validpre2000UTCnotBeforeDateTest3EE.crt | Bin 0 -> 920 bytes .../ValidrequireExplicitPolicyTest1EE.crt | Bin 0 -> 918 bytes .../ValidrequireExplicitPolicyTest2EE.crt | Bin 0 -> 917 bytes .../ValidrequireExplicitPolicyTest4EE.crt | Bin 0 -> 942 bytes .../nist-pkits/certs/WrongCRLCACert.crt | Bin 0 -> 901 bytes .../nist-pkits/certs/anyPolicyCACert.crt | Bin 0 -> 911 bytes .../basicConstraintsCriticalcAFalseCACert.crt | Bin 0 -> 923 bytes .../basicConstraintsNotCriticalCACert.crt | Bin 0 -> 918 bytes ...sicConstraintsNotCriticalcAFalseCACert.crt | Bin 0 -> 924 bytes .../nist-pkits/certs/deltaCRLCA1Cert.crt | Bin 0 -> 901 bytes .../nist-pkits/certs/deltaCRLCA2Cert.crt | Bin 0 -> 901 bytes .../nist-pkits/certs/deltaCRLCA3Cert.crt | Bin 0 -> 901 bytes .../certs/deltaCRLIndicatorNoBaseCACert.crt | Bin 0 -> 917 bytes .../certs/distributionPoint1CACert.crt | Bin 0 -> 910 bytes .../certs/distributionPoint2CACert.crt | Bin 0 -> 910 bytes .../nist-pkits/certs/indirectCRLCA1Cert.crt | Bin 0 -> 904 bytes .../nist-pkits/certs/indirectCRLCA2Cert.crt | Bin 0 -> 904 bytes .../nist-pkits/certs/indirectCRLCA3Cert.crt | Bin 0 -> 904 bytes .../certs/indirectCRLCA3cRLIssuerCert.crt | Bin 0 -> 1010 bytes .../nist-pkits/certs/indirectCRLCA4Cert.crt | Bin 0 -> 904 bytes .../certs/indirectCRLCA4cRLIssuerCert.crt | Bin 0 -> 1144 bytes .../nist-pkits/certs/indirectCRLCA5Cert.crt | Bin 0 -> 904 bytes .../nist-pkits/certs/indirectCRLCA6Cert.crt | Bin 0 -> 904 bytes .../certs/inhibitAnyPolicy0CACert.crt | Bin 0 -> 940 bytes .../certs/inhibitAnyPolicy1CACert.crt | Bin 0 -> 940 bytes .../inhibitAnyPolicy1SelfIssuedCACert.crt | Bin 0 -> 917 bytes .../inhibitAnyPolicy1SelfIssuedsubCA2Cert.crt | Bin 0 -> 919 bytes .../certs/inhibitAnyPolicy1subCA1Cert.crt | Bin 0 -> 915 bytes .../certs/inhibitAnyPolicy1subCA2Cert.crt | Bin 0 -> 915 bytes .../certs/inhibitAnyPolicy1subCAIAP5Cert.crt | Bin 0 -> 941 bytes .../certs/inhibitAnyPolicy1subsubCA2Cert.crt | Bin 0 -> 922 bytes .../certs/inhibitAnyPolicy5CACert.crt | Bin 0 -> 940 bytes .../certs/inhibitAnyPolicy5subCACert.crt | Bin 0 -> 937 bytes .../certs/inhibitAnyPolicy5subsubCACert.crt | Bin 0 -> 926 bytes .../certs/inhibitAnyPolicyTest3EE.crt | Bin 0 -> 921 bytes .../certs/inhibitPolicyMapping0CACert.crt | Bin 0 -> 935 bytes .../certs/inhibitPolicyMapping0subCACert.crt | Bin 0 -> 970 bytes .../certs/inhibitPolicyMapping1P12CACert.crt | Bin 0 -> 953 bytes .../inhibitPolicyMapping1P12subCACert.crt | Bin 0 -> 1018 bytes .../inhibitPolicyMapping1P12subCAIPM5Cert.crt | Bin 0 -> 973 bytes .../inhibitPolicyMapping1P12subsubCACert.crt | Bin 0 -> 998 bytes ...hibitPolicyMapping1P12subsubCAIPM5Cert.crt | Bin 0 -> 1006 bytes .../certs/inhibitPolicyMapping1P1CACert.crt | Bin 0 -> 938 bytes ...nhibitPolicyMapping1P1SelfIssuedCACert.crt | Bin 0 -> 931 bytes ...bitPolicyMapping1P1SelfIssuedsubCACert.crt | Bin 0 -> 979 bytes .../inhibitPolicyMapping1P1subCACert.crt | Bin 0 -> 976 bytes .../inhibitPolicyMapping1P1subsubCACert.crt | Bin 0 -> 982 bytes .../certs/inhibitPolicyMapping5CACert.crt | Bin 0 -> 935 bytes .../certs/inhibitPolicyMapping5subCACert.crt | Bin 0 -> 947 bytes .../inhibitPolicyMapping5subsubCACert.crt | Bin 0 -> 934 bytes .../inhibitPolicyMapping5subsubsubCACert.crt | Bin 0 -> 982 bytes .../keyUsageCriticalcRLSignFalseCACert.crt | Bin 0 -> 923 bytes ...keyUsageCriticalkeyCertSignFalseCACert.crt | Bin 0 -> 927 bytes .../certs/keyUsageNotCriticalCACert.crt | Bin 0 -> 910 bytes .../keyUsageNotCriticalcRLSignFalseCACert.crt | Bin 0 -> 924 bytes ...UsageNotCriticalkeyCertSignFalseCACert.crt | Bin 0 -> 928 bytes .../certs/nameConstraintsDN1CACert.crt | Bin 0 -> 1009 bytes .../nameConstraintsDN1SelfIssuedCACert.crt | Bin 0 -> 921 bytes .../certs/nameConstraintsDN1subCA1Cert.crt | Bin 0 -> 1079 bytes .../certs/nameConstraintsDN1subCA2Cert.crt | Bin 0 -> 1051 bytes .../certs/nameConstraintsDN1subCA3Cert.crt | Bin 0 -> 995 bytes .../certs/nameConstraintsDN2CACert.crt | Bin 0 -> 1095 bytes .../certs/nameConstraintsDN3CACert.crt | Bin 0 -> 1008 bytes .../certs/nameConstraintsDN3subCA1Cert.crt | Bin 0 -> 1022 bytes .../certs/nameConstraintsDN3subCA2Cert.crt | Bin 0 -> 995 bytes .../certs/nameConstraintsDN4CACert.crt | Bin 0 -> 1093 bytes .../certs/nameConstraintsDN5CACert.crt | Bin 0 -> 1123 bytes .../certs/nameConstraintsDNS1CACert.crt | Bin 0 -> 954 bytes .../certs/nameConstraintsDNS2CACert.crt | Bin 0 -> 957 bytes .../certs/nameConstraintsRFC822CA1Cert.crt | Bin 0 -> 958 bytes .../certs/nameConstraintsRFC822CA2Cert.crt | Bin 0 -> 957 bytes .../certs/nameConstraintsRFC822CA3Cert.crt | Bin 0 -> 957 bytes .../certs/nameConstraintsURI1CACert.crt | Bin 0 -> 955 bytes .../certs/nameConstraintsURI2CACert.crt | Bin 0 -> 957 bytes .../onlyContainsAttributeCertsCACert.crt | Bin 0 -> 918 bytes .../certs/onlyContainsCACertsCACert.crt | Bin 0 -> 911 bytes .../certs/onlyContainsUserCertsCACert.crt | Bin 0 -> 913 bytes .../certs/onlySomeReasonsCA1Cert.crt | Bin 0 -> 908 bytes .../certs/onlySomeReasonsCA2Cert.crt | Bin 0 -> 908 bytes .../certs/onlySomeReasonsCA3Cert.crt | Bin 0 -> 908 bytes .../certs/onlySomeReasonsCA4Cert.crt | Bin 0 -> 908 bytes .../certs/pathLenConstraint0CACert.crt | Bin 0 -> 913 bytes .../pathLenConstraint0SelfIssuedCACert.crt | Bin 0 -> 919 bytes .../certs/pathLenConstraint0subCA2Cert.crt | Bin 0 -> 923 bytes .../certs/pathLenConstraint0subCACert.crt | Bin 0 -> 922 bytes .../certs/pathLenConstraint1CACert.crt | Bin 0 -> 913 bytes .../pathLenConstraint1SelfIssuedCACert.crt | Bin 0 -> 919 bytes .../pathLenConstraint1SelfIssuedsubCACert.crt | Bin 0 -> 925 bytes .../certs/pathLenConstraint1subCACert.crt | Bin 0 -> 922 bytes .../certs/pathLenConstraint6CACert.crt | Bin 0 -> 913 bytes .../certs/pathLenConstraint6subCA0Cert.crt | Bin 0 -> 926 bytes .../certs/pathLenConstraint6subCA1Cert.crt | Bin 0 -> 926 bytes .../certs/pathLenConstraint6subCA4Cert.crt | Bin 0 -> 926 bytes .../pathLenConstraint6subsubCA00Cert.crt | Bin 0 -> 934 bytes .../pathLenConstraint6subsubCA11Cert.crt | Bin 0 -> 934 bytes .../pathLenConstraint6subsubCA41Cert.crt | Bin 0 -> 934 bytes .../pathLenConstraint6subsubsubCA11XCert.crt | Bin 0 -> 939 bytes .../pathLenConstraint6subsubsubCA41XCert.crt | Bin 0 -> 939 bytes .../certs/pre2000CRLnextUpdateCACert.crt | Bin 0 -> 914 bytes .../certs/requireExplicitPolicy0CACert.crt | Bin 0 -> 933 bytes .../certs/requireExplicitPolicy0subCACert.crt | Bin 0 -> 930 bytes .../requireExplicitPolicy0subsubCACert.crt | Bin 0 -> 936 bytes .../requireExplicitPolicy0subsubsubCACert.crt | Bin 0 -> 942 bytes .../certs/requireExplicitPolicy10CACert.crt | Bin 0 -> 934 bytes .../requireExplicitPolicy10subCACert.crt | Bin 0 -> 932 bytes .../requireExplicitPolicy10subsubCACert.crt | Bin 0 -> 938 bytes ...requireExplicitPolicy10subsubsubCACert.crt | Bin 0 -> 944 bytes .../certs/requireExplicitPolicy2CACert.crt | Bin 0 -> 933 bytes ...requireExplicitPolicy2SelfIssuedCACert.crt | Bin 0 -> 927 bytes ...uireExplicitPolicy2SelfIssuedsubCACert.crt | Bin 0 -> 933 bytes .../certs/requireExplicitPolicy2subCACert.crt | Bin 0 -> 930 bytes .../certs/requireExplicitPolicy4CACert.crt | Bin 0 -> 933 bytes .../certs/requireExplicitPolicy4subCACert.crt | Bin 0 -> 930 bytes .../requireExplicitPolicy4subsubCACert.crt | Bin 0 -> 936 bytes .../requireExplicitPolicy4subsubsubCACert.crt | Bin 0 -> 942 bytes .../certs/requireExplicitPolicy5CACert.crt | Bin 0 -> 933 bytes .../certs/requireExplicitPolicy5subCACert.crt | Bin 0 -> 930 bytes .../requireExplicitPolicy5subsubCACert.crt | Bin 0 -> 936 bytes .../requireExplicitPolicy5subsubsubCACert.crt | Bin 0 -> 942 bytes .../certs/requireExplicitPolicy7CACert.crt | Bin 0 -> 933 bytes .../requireExplicitPolicy7subCARE2Cert.crt | Bin 0 -> 952 bytes ...quireExplicitPolicy7subsubCARE2RE4Cert.crt | Bin 0 -> 964 bytes ...reExplicitPolicy7subsubsubCARE2RE4Cert.crt | Bin 0 -> 954 bytes crypto/x509/testdata/nist-pkits/vectors.json | 5010 +++++++++++++++ crypto/x509/testdata/policy_intermediate.pem | 11 + .../x509/testdata/policy_intermediate_any.pem | 11 + .../policy_intermediate_duplicate.pem | 12 + .../testdata/policy_intermediate_invalid.pem | 11 + .../testdata/policy_intermediate_mapped.pem | 17 + .../policy_intermediate_mapped_any.pem | 15 + .../policy_intermediate_mapped_oid3.pem | 15 + .../testdata/policy_intermediate_require.pem | 12 + .../testdata/policy_intermediate_require1.pem | 12 + .../testdata/policy_intermediate_require2.pem | 12 + .../policy_intermediate_require_duplicate.pem | 12 + ...olicy_intermediate_require_no_policies.pem | 11 + crypto/x509/testdata/policy_leaf.pem | 11 + crypto/x509/testdata/policy_leaf_any.pem | 11 + .../x509/testdata/policy_leaf_duplicate.pem | 12 + crypto/x509/testdata/policy_leaf_invalid.pem | 11 + crypto/x509/testdata/policy_leaf_none.pem | 10 + crypto/x509/testdata/policy_leaf_oid1.pem | 11 + crypto/x509/testdata/policy_leaf_oid2.pem | 11 + crypto/x509/testdata/policy_leaf_oid3.pem | 11 + crypto/x509/testdata/policy_leaf_oid4.pem | 11 + crypto/x509/testdata/policy_leaf_oid5.pem | 11 + crypto/x509/testdata/policy_leaf_require.pem | 12 + crypto/x509/testdata/policy_leaf_require1.pem | 12 + crypto/x509/testdata/policy_root.pem | 10 + crypto/x509/testdata/policy_root2.pem | 10 + .../policy_root_cross_inhibit_mapping.pem | 11 + crypto/x509/verify.go | 462 +- crypto/x509/verify_test.go | 829 ++- crypto/x509/x509.go | 279 +- crypto/x509/x509_test.go | 185 +- crypto/x509/x509_test_import.go | 23 +- refs/crypto.hash | 2 +- x/crypto/bcrypt/bcrypt.go | 3 +- x/crypto/bn256/bn256.go | 3 +- x/crypto/bn256/bn256_test.go | 3 +- x/crypto/curve25519/curve25519.go | 2 +- x/crypto/curve25519/curve25519_test.go | 3 +- x/crypto/hkdf/example_test.go | 3 +- x/crypto/internal/poly1305/poly1305_test.go | 3 +- .../internal/wycheproof/ecdh_stdlib_test.go | 3 +- x/crypto/nacl/box/box_test.go | 3 +- x/crypto/nacl/secretbox/example_test.go | 3 +- x/crypto/nacl/secretbox/secretbox_test.go | 3 +- x/crypto/nacl/sign/sign_test.go | 3 +- x/crypto/ocsp/ocsp.go | 3 +- x/crypto/ocsp/ocsp_test.go | 3 +- x/crypto/openpgp/elgamal/elgamal.go | 3 +- x/crypto/openpgp/elgamal/elgamal_test.go | 3 +- x/crypto/openpgp/packet/config.go | 3 +- x/crypto/openpgp/packet/ocfb_test.go | 3 +- x/crypto/openpgp/s2k/s2k_test.go | 3 +- x/crypto/otr/otr.go | 3 +- x/crypto/otr/otr_test.go | 3 +- x/crypto/pkcs12/pkcs12_test.go | 3 +- x/crypto/sha3/sha3.go | 3 +- x/crypto/ssh/agent/client_test.go | 3 +- x/crypto/ssh/agent/keyring.go | 3 +- x/crypto/ssh/agent/server_test.go | 3 +- x/crypto/ssh/agent/testdata_test.go | 3 +- x/crypto/ssh/certs_test.go | 3 +- x/crypto/ssh/cipher_test.go | 3 +- x/crypto/ssh/client_auth_test.go | 3 +- x/crypto/ssh/client_test.go | 3 +- x/crypto/ssh/common.go | 3 +- x/crypto/ssh/example_test.go | 3 +- x/crypto/ssh/handshake.go | 3 +- x/crypto/ssh/handshake_test.go | 3 +- x/crypto/ssh/kex.go | 3 +- x/crypto/ssh/kex_test.go | 3 +- x/crypto/ssh/keys.go | 3 +- x/crypto/ssh/keys_test.go | 3 +- x/crypto/ssh/knownhosts/knownhosts.go | 3 +- x/crypto/ssh/test/cert_test.go | 3 +- x/crypto/ssh/test/test_unix_test.go | 3 +- x/crypto/ssh/test/testdata_test.go | 3 +- x/crypto/ssh/testdata_test.go | 3 +- x/crypto/ssh/transport_test.go | 3 +- 952 files changed, 45460 insertions(+), 11762 deletions(-) delete mode 100644 crypto/aes/_asm/standard/go.mod delete mode 100644 crypto/aes/_asm/standard/go.sum create mode 100644 crypto/aes/aes.go delete mode 100644 crypto/aes/aes_gcm.go delete mode 100644 crypto/aes/asm_s390x.s delete mode 100644 crypto/aes/cbc_ppc64x.go delete mode 100644 crypto/aes/cbc_s390x.go delete mode 100644 crypto/aes/cipher.go delete mode 100644 crypto/aes/cipher_asm.go delete mode 100644 crypto/aes/cipher_generic.go delete mode 100644 crypto/aes/cipher_s390x.go delete mode 100644 crypto/aes/ctr_s390x.go delete mode 100644 crypto/aes/gcm_ppc64x.go delete mode 100644 crypto/aes/gcm_s390x.go delete mode 100644 crypto/aes/modes.go delete mode 100644 crypto/aes/modes_test.go delete mode 100644 crypto/cipher/cipher_test.go delete mode 100644 crypto/cipher/export_test.go create mode 100644 crypto/cipher/modes_test.go rename crypto/{aes/_asm/gcm/go.mod => ecdsa/boring.go} (100%) delete mode 100644 crypto/ecdsa/ecdsa_noasm.go delete mode 100644 crypto/ecdsa/ecdsa_s390x_test.go create mode 100644 crypto/fips140/fips140.go create mode 100644 crypto/hkdf/example_test.go create mode 100644 crypto/hkdf/hkdf.go create mode 100644 crypto/hkdf/hkdf_test.go delete mode 100644 crypto/internal/bigmod/_asm/go.mod delete mode 100644 crypto/internal/bigmod/_asm/go.sum create mode 100644 crypto/internal/cryptotest/allocations.go create mode 100644 crypto/internal/cryptotest/fetchmodule.go create mode 100644 crypto/internal/cryptotest/implementations.go delete mode 100644 crypto/internal/edwards25519/field/_asm/go.mod delete mode 100644 crypto/internal/edwards25519/field/_asm/go.sum create mode 100644 crypto/internal/entropy/entropy.go create mode 100644 crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go create mode 100644 crypto/internal/fips140/aes/_asm/ctr/go.mod create mode 100644 crypto/internal/fips140/aes/_asm/ctr/go.sum rename crypto/{aes/_asm/standard/asm_amd64.go => internal/fips140/aes/_asm/standard/aes_amd64.go} (98%) create mode 100644 crypto/internal/fips140/aes/_asm/standard/go.mod create mode 100644 crypto/internal/fips140/aes/_asm/standard/go.sum create mode 100644 crypto/internal/fips140/aes/aes.go rename crypto/{aes/asm_amd64.s => internal/fips140/aes/aes_amd64.s} (100%) rename crypto/{aes/asm_arm64.s => internal/fips140/aes/aes_arm64.s} (99%) create mode 100644 crypto/internal/fips140/aes/aes_asm.go rename crypto/{aes/block.go => internal/fips140/aes/aes_generic.go} (80%) create mode 100644 crypto/internal/fips140/aes/aes_noasm.go rename crypto/{aes/asm_ppc64x.s => internal/fips140/aes/aes_ppc64x.s} (76%) create mode 100644 crypto/internal/fips140/aes/aes_s390x.go create mode 100644 crypto/internal/fips140/aes/aes_s390x.s create mode 100644 crypto/internal/fips140/aes/aes_test.go create mode 100644 crypto/internal/fips140/aes/cast.go create mode 100644 crypto/internal/fips140/aes/cbc.go create mode 100644 crypto/internal/fips140/aes/cbc_noasm.go create mode 100644 crypto/internal/fips140/aes/cbc_ppc64x.go create mode 100644 crypto/internal/fips140/aes/cbc_s390x.go rename crypto/{ => internal/fips140}/aes/const.go (97%) create mode 100644 crypto/internal/fips140/aes/ctr.go create mode 100644 crypto/internal/fips140/aes/ctr_amd64.s create mode 100644 crypto/internal/fips140/aes/ctr_arm64.s create mode 100644 crypto/internal/fips140/aes/ctr_arm64_gen.go create mode 100644 crypto/internal/fips140/aes/ctr_asm.go create mode 100644 crypto/internal/fips140/aes/ctr_noasm.go create mode 100644 crypto/internal/fips140/aes/ctr_s390x.go rename crypto/{aes => internal/fips140/aes/gcm}/_asm/gcm/gcm_amd64_asm.go (99%) create mode 100644 crypto/internal/fips140/aes/gcm/_asm/gcm/go.mod create mode 100644 crypto/internal/fips140/aes/gcm/_asm/gcm/go.sum create mode 100644 crypto/internal/fips140/aes/gcm/cast.go create mode 100644 crypto/internal/fips140/aes/gcm/cmac.go create mode 100644 crypto/internal/fips140/aes/gcm/ctrkdf.go create mode 100644 crypto/internal/fips140/aes/gcm/gcm.go rename crypto/{aes => internal/fips140/aes/gcm}/gcm_amd64.s (100%) rename crypto/{aes => internal/fips140/aes/gcm}/gcm_arm64.s (100%) create mode 100644 crypto/internal/fips140/aes/gcm/gcm_asm.go create mode 100644 crypto/internal/fips140/aes/gcm/gcm_generic.go create mode 100644 crypto/internal/fips140/aes/gcm/gcm_noasm.go create mode 100644 crypto/internal/fips140/aes/gcm/gcm_nonces.go create mode 100644 crypto/internal/fips140/aes/gcm/gcm_ppc64x.go rename crypto/{aes => internal/fips140/aes/gcm}/gcm_ppc64x.s (96%) create mode 100644 crypto/internal/fips140/aes/gcm/gcm_s390x.go create mode 100644 crypto/internal/fips140/aes/gcm/gcm_s390x.s create mode 100644 crypto/internal/fips140/aes/gcm/ghash.go create mode 100644 crypto/internal/fips140/aes/gcm/interface_test.go create mode 100644 crypto/internal/fips140/aes/interface_test.go rename crypto/internal/{ => fips140}/alias/alias.go (100%) rename crypto/{subtle/xor_loong64.go => internal/fips140/asan.go} (65%) create mode 100644 crypto/internal/fips140/bigmod/_asm/go.mod create mode 100644 crypto/internal/fips140/bigmod/_asm/go.sum rename crypto/internal/{ => fips140}/bigmod/_asm/nat_amd64_asm.go (97%) rename crypto/internal/{ => fips140}/bigmod/nat.go (63%) rename crypto/internal/{ => fips140}/bigmod/nat_386.s (85%) rename crypto/internal/{ => fips140}/bigmod/nat_amd64.s (100%) rename crypto/internal/{ => fips140}/bigmod/nat_arm.s (85%) rename crypto/internal/{ => fips140}/bigmod/nat_arm64.s (91%) rename crypto/internal/{ => fips140}/bigmod/nat_asm.go (77%) rename crypto/internal/{ => fips140}/bigmod/nat_loong64.s (92%) rename crypto/internal/{ => fips140}/bigmod/nat_noasm.go (92%) rename crypto/internal/{ => fips140}/bigmod/nat_ppc64x.s (93%) rename crypto/internal/{ => fips140}/bigmod/nat_riscv64.s (94%) rename crypto/internal/{ => fips140}/bigmod/nat_s390x.s (91%) rename crypto/internal/{ => fips140}/bigmod/nat_test.go (60%) create mode 100644 crypto/internal/fips140/bigmod/nat_wasm.go create mode 100644 crypto/internal/fips140/bigmod/testdata/mod_inv_tests.txt create mode 100644 crypto/internal/fips140/boring.go create mode 100644 crypto/internal/fips140/cast.go create mode 100644 crypto/internal/fips140/check/check.go create mode 100644 crypto/internal/fips140/check/checktest/asm.s create mode 100644 crypto/internal/fips140/check/checktest/asm_386.s create mode 100644 crypto/internal/fips140/check/checktest/asm_amd64.s create mode 100644 crypto/internal/fips140/check/checktest/asm_arm.s create mode 100644 crypto/internal/fips140/check/checktest/asm_arm64.s create mode 100644 crypto/internal/fips140/check/checktest/asm_none.go create mode 100644 crypto/internal/fips140/check/checktest/asm_stub.go create mode 100644 crypto/internal/fips140/check/checktest/test.go create mode 100644 crypto/internal/fips140/drbg/cast.go create mode 100644 crypto/internal/fips140/drbg/ctrdrbg.go create mode 100644 crypto/internal/fips140/drbg/rand.go create mode 100644 crypto/internal/fips140/drbg/rand_test.go create mode 100644 crypto/internal/fips140/ecdh/cast.go create mode 100644 crypto/internal/fips140/ecdh/ecdh.go create mode 100644 crypto/internal/fips140/ecdh/order_test.go create mode 100644 crypto/internal/fips140/ecdsa/cast.go create mode 100644 crypto/internal/fips140/ecdsa/ecdsa.go create mode 100644 crypto/internal/fips140/ecdsa/ecdsa_noasm.go rename crypto/{ => internal/fips140}/ecdsa/ecdsa_s390x.go (53%) rename crypto/{ => internal/fips140}/ecdsa/ecdsa_s390x.s (100%) create mode 100644 crypto/internal/fips140/ecdsa/ecdsa_test.go create mode 100644 crypto/internal/fips140/ecdsa/hmacdrbg.go create mode 100644 crypto/internal/fips140/ed25519/cast.go create mode 100644 crypto/internal/fips140/ed25519/ed25519.go rename crypto/internal/{ => fips140}/edwards25519/doc.go (100%) rename crypto/internal/{ => fips140}/edwards25519/edwards25519.go (98%) rename crypto/internal/{ => fips140}/edwards25519/edwards25519_test.go (94%) rename crypto/internal/{ => fips140}/edwards25519/field/_asm/fe_amd64_asm.go (98%) create mode 100644 crypto/internal/fips140/edwards25519/field/_asm/go.mod create mode 100644 crypto/internal/fips140/edwards25519/field/_asm/go.sum rename crypto/internal/{ => fips140}/edwards25519/field/fe.go (91%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_alias_test.go (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_amd64.go (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_amd64.s (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_amd64_noasm.go (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_arm64.go (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_arm64.s (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_arm64_noasm.go (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_bench_test.go (88%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_generic.go (100%) rename crypto/internal/{ => fips140}/edwards25519/field/fe_test.go (88%) rename crypto/internal/{ => fips140}/edwards25519/scalar.go (93%) rename crypto/internal/{ => fips140}/edwards25519/scalar_alias_test.go (100%) rename crypto/internal/{ => fips140}/edwards25519/scalar_fiat.go (100%) rename crypto/internal/{ => fips140}/edwards25519/scalar_test.go (92%) rename crypto/internal/{ => fips140}/edwards25519/scalarmult.go (100%) rename crypto/internal/{ => fips140}/edwards25519/scalarmult_test.go (100%) rename crypto/internal/{ => fips140}/edwards25519/tables.go (98%) rename crypto/internal/{ => fips140}/edwards25519/tables_test.go (100%) create mode 100644 crypto/internal/fips140/fips140.go create mode 100644 crypto/internal/fips140/hash.go create mode 100644 crypto/internal/fips140/hkdf/cast.go create mode 100644 crypto/internal/fips140/hkdf/hkdf.go create mode 100644 crypto/internal/fips140/hmac/cast.go create mode 100644 crypto/internal/fips140/hmac/hmac.go create mode 100644 crypto/internal/fips140/indicator.go create mode 100644 crypto/internal/fips140/mlkem/cast.go create mode 100644 crypto/internal/fips140/mlkem/field.go create mode 100644 crypto/internal/fips140/mlkem/field_test.go create mode 100644 crypto/internal/fips140/mlkem/generate1024.go create mode 100644 crypto/internal/fips140/mlkem/mlkem1024.go create mode 100644 crypto/internal/fips140/mlkem/mlkem768.go create mode 100644 crypto/internal/fips140/nistec/_asm/go.mod create mode 100644 crypto/internal/fips140/nistec/_asm/go.sum rename crypto/internal/{nistec/_asm/p256_asm_amd64.go => fips140/nistec/_asm/p256_asm.go} (90%) create mode 100644 crypto/internal/fips140/nistec/benchmark_test.go rename crypto/internal/{ => fips140}/nistec/fiat/Dockerfile (100%) rename crypto/internal/{ => fips140}/nistec/fiat/README (100%) rename crypto/internal/{nistec/fiat/fiat_test.go => fips140/nistec/fiat/benchmark_test.go} (94%) create mode 100644 crypto/internal/fips140/nistec/fiat/cast.go rename crypto/internal/{ => fips140}/nistec/fiat/generate.go (99%) rename crypto/internal/{ => fips140}/nistec/fiat/p224.go (98%) rename crypto/internal/{ => fips140}/nistec/fiat/p224_fiat64.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p224_invert.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p256.go (98%) rename crypto/internal/{ => fips140}/nistec/fiat/p256_fiat64.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p256_invert.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p384.go (98%) rename crypto/internal/{ => fips140}/nistec/fiat/p384_fiat64.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p384_invert.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p521.go (98%) rename crypto/internal/{ => fips140}/nistec/fiat/p521_fiat64.go (100%) rename crypto/internal/{ => fips140}/nistec/fiat/p521_invert.go (100%) rename crypto/internal/{ => fips140}/nistec/generate.go (97%) rename crypto/internal/{ => fips140}/nistec/nistec.go (81%) rename crypto/internal/{ => fips140}/nistec/p224.go (99%) rename crypto/internal/{ => fips140}/nistec/p224_sqrt.go (97%) create mode 100644 crypto/internal/fips140/nistec/p256.go rename crypto/internal/{ => fips140}/nistec/p256_asm.go (94%) rename crypto/internal/{ => fips140}/nistec/p256_asm_amd64.s (93%) rename crypto/internal/{ => fips140}/nistec/p256_asm_arm64.s (88%) rename crypto/internal/{ => fips140}/nistec/p256_asm_ppc64le.s (94%) rename crypto/internal/{ => fips140}/nistec/p256_asm_s390x.s (94%) create mode 100644 crypto/internal/fips140/nistec/p256_asm_test.go rename crypto/internal/{ => fips140}/nistec/p256_ordinv.go (100%) rename crypto/internal/{ => fips140}/nistec/p256_ordinv_noasm.go (100%) create mode 100644 crypto/internal/fips140/nistec/p256_table.go rename crypto/internal/{nistec/p256_asm_table_test.go => fips140/nistec/p256_table_test.go} (62%) rename crypto/internal/{ => fips140}/nistec/p384.go (99%) rename crypto/internal/{ => fips140}/nistec/p521.go (99%) create mode 100644 crypto/internal/fips140/notasan.go create mode 100644 crypto/internal/fips140/notboring.go create mode 100644 crypto/internal/fips140/pbkdf2/cast.go create mode 100644 crypto/internal/fips140/pbkdf2/pbkdf2.go create mode 100644 crypto/internal/fips140/rsa/cast.go create mode 100644 crypto/internal/fips140/rsa/keygen.go create mode 100644 crypto/internal/fips140/rsa/keygen_test.go create mode 100644 crypto/internal/fips140/rsa/pkcs1v15.go create mode 100644 crypto/internal/fips140/rsa/pkcs1v15_test.go create mode 100644 crypto/internal/fips140/rsa/pkcs1v22.go create mode 100644 crypto/internal/fips140/rsa/pkcs1v22_test.go create mode 100644 crypto/internal/fips140/rsa/rsa.go create mode 100644 crypto/internal/fips140/rsa/testdata/gcd_lcm_tests.txt create mode 100644 crypto/internal/fips140/rsa/testdata/miller_rabin_tests.txt create mode 100644 crypto/internal/fips140/sha256/_asm/go.mod create mode 100644 crypto/internal/fips140/sha256/_asm/go.sum create mode 100644 crypto/internal/fips140/sha256/_asm/sha256block_amd64_asm.go rename crypto/{sha256/_asm/sha256block_amd64_asm.go => internal/fips140/sha256/_asm/sha256block_amd64_avx2.go} (72%) create mode 100644 crypto/internal/fips140/sha256/_asm/sha256block_amd64_shani.go create mode 100644 crypto/internal/fips140/sha256/cast.go create mode 100644 crypto/internal/fips140/sha256/sha256.go rename crypto/{ => internal/fips140}/sha256/sha256block.go (97%) rename crypto/{ => internal/fips140}/sha256/sha256block_386.s (100%) create mode 100644 crypto/internal/fips140/sha256/sha256block_amd64.go rename crypto/{ => internal/fips140}/sha256/sha256block_amd64.s (99%) create mode 100644 crypto/internal/fips140/sha256/sha256block_arm64.go rename crypto/{ => internal/fips140}/sha256/sha256block_arm64.s (93%) rename crypto/{sha256/sha256block_decl.go => internal/fips140/sha256/sha256block_asm.go} (61%) rename crypto/{ => internal/fips140}/sha256/sha256block_loong64.s (99%) rename crypto/{sha256/sha256block_generic.go => internal/fips140/sha256/sha256block_noasm.go} (89%) create mode 100644 crypto/internal/fips140/sha256/sha256block_ppc64x.go rename crypto/{ => internal/fips140}/sha256/sha256block_ppc64x.s (99%) rename crypto/{ => internal/fips140}/sha256/sha256block_riscv64.s (99%) create mode 100644 crypto/internal/fips140/sha256/sha256block_s390x.go rename crypto/{ => internal/fips140}/sha256/sha256block_s390x.s (73%) create mode 100644 crypto/internal/fips140/sha3/_asm/go.mod create mode 100644 crypto/internal/fips140/sha3/_asm/go.sum create mode 100644 crypto/internal/fips140/sha3/_asm/keccakf_amd64_asm.go create mode 100644 crypto/internal/fips140/sha3/cast.go create mode 100644 crypto/internal/fips140/sha3/hashes.go create mode 100644 crypto/internal/fips140/sha3/keccakf.go create mode 100644 crypto/internal/fips140/sha3/sha3.go create mode 100644 crypto/internal/fips140/sha3/sha3_amd64.go create mode 100644 crypto/internal/fips140/sha3/sha3_amd64.s create mode 100644 crypto/internal/fips140/sha3/sha3_noasm.go create mode 100644 crypto/internal/fips140/sha3/sha3_s390x.go create mode 100644 crypto/internal/fips140/sha3/sha3_s390x.s create mode 100644 crypto/internal/fips140/sha3/shake.go create mode 100644 crypto/internal/fips140/sha512/_asm/go.mod create mode 100644 crypto/internal/fips140/sha512/_asm/go.sum rename crypto/{ => internal/fips140}/sha512/_asm/sha512block_amd64_asm.go (99%) create mode 100644 crypto/internal/fips140/sha512/cast.go create mode 100644 crypto/internal/fips140/sha512/sha512.go rename crypto/{ => internal/fips140}/sha512/sha512block.go (98%) create mode 100644 crypto/internal/fips140/sha512/sha512block_amd64.go rename crypto/{ => internal/fips140}/sha512/sha512block_amd64.s (99%) create mode 100644 crypto/internal/fips140/sha512/sha512block_arm64.go rename crypto/{ => internal/fips140}/sha512/sha512block_arm64.s (98%) rename crypto/{sha512/sha512block_decl.go => internal/fips140/sha512/sha512block_asm.go} (64%) rename crypto/{ => internal/fips140}/sha512/sha512block_loong64.s (99%) rename crypto/{sha512/sha512block_generic.go => internal/fips140/sha512/sha512block_noasm.go} (89%) create mode 100644 crypto/internal/fips140/sha512/sha512block_ppc64x.go rename crypto/{ => internal/fips140}/sha512/sha512block_ppc64x.s (99%) rename crypto/{ => internal/fips140}/sha512/sha512block_riscv64.s (96%) create mode 100644 crypto/internal/fips140/sha512/sha512block_s390x.go rename crypto/{ => internal/fips140}/sha512/sha512block_s390x.s (73%) create mode 100644 crypto/internal/fips140/ssh/kdf.go create mode 100644 crypto/internal/fips140/subtle/constant_time.go create mode 100644 crypto/internal/fips140/subtle/xor.go rename crypto/{ => internal/fips140}/subtle/xor_amd64.s (100%) rename crypto/{ => internal/fips140}/subtle/xor_arm64.s (100%) rename crypto/{subtle/xor_ppc64x.go => internal/fips140/subtle/xor_asm.go} (73%) rename crypto/{ => internal/fips140}/subtle/xor_generic.go (95%) rename crypto/{ => internal/fips140}/subtle/xor_loong64.s (100%) rename crypto/{ => internal/fips140}/subtle/xor_ppc64x.s (100%) create mode 100644 crypto/internal/fips140/subtle/xor_riscv64.s create mode 100644 crypto/internal/fips140/tls12/cast.go create mode 100644 crypto/internal/fips140/tls12/tls12.go create mode 100644 crypto/internal/fips140/tls13/cast.go create mode 100644 crypto/internal/fips140/tls13/tls13.go create mode 100644 crypto/internal/fips140deps/byteorder/byteorder.go create mode 100644 crypto/internal/fips140deps/cpu/cpu.go create mode 100644 crypto/internal/fips140deps/fipsdeps.go create mode 100644 crypto/internal/fips140deps/fipsdeps_test.go create mode 100644 crypto/internal/fips140deps/godebug/godebug.go create mode 100644 crypto/internal/fips140hash/hash.go create mode 100644 crypto/internal/fips140only/fips140only.go create mode 100644 crypto/internal/fips140test/acvp_capabilities.json create mode 100644 crypto/internal/fips140test/acvp_test.config.json create mode 100644 crypto/internal/fips140test/acvp_test.go rename crypto/internal/{alias => fips140test}/alias_test.go (87%) create mode 100644 crypto/internal/fips140test/cast_test.go create mode 100644 crypto/internal/fips140test/check_test.go create mode 100644 crypto/internal/fips140test/cmac_test.go create mode 100644 crypto/internal/fips140test/ctrdrbg_test.go create mode 100644 crypto/internal/fips140test/edwards25519_test.go create mode 100644 crypto/internal/fips140test/fips_test.go create mode 100644 crypto/internal/fips140test/indicator_test.go rename crypto/internal/{nistec/p256_ordinv_test.go => fips140test/nistec_ordinv_test.go} (96%) rename crypto/internal/{nistec => fips140test}/nistec_test.go (80%) create mode 100644 crypto/internal/fips140test/sshkdf_test.go create mode 100644 crypto/internal/fips140test/xaes_test.go create mode 100644 crypto/internal/impl/impl.go delete mode 100644 crypto/internal/mlkem768/mlkem768.go delete mode 100644 crypto/internal/mlkem768/mlkem768_test.go delete mode 100644 crypto/internal/nistec/_asm/go.mod delete mode 100644 crypto/internal/nistec/_asm/go.sum delete mode 100644 crypto/internal/nistec/p256.go delete mode 100644 crypto/internal/nistec/p256_asm_table.bin create mode 100644 crypto/internal/sysrand/internal/seccomp/seccomp_linux.go create mode 100644 crypto/internal/sysrand/internal/seccomp/seccomp_unsupported.go create mode 100644 crypto/internal/sysrand/rand.go create mode 100644 crypto/internal/sysrand/rand_aix.go create mode 100644 crypto/internal/sysrand/rand_arc4random.go create mode 100644 crypto/internal/sysrand/rand_getrandom.go create mode 100644 crypto/internal/sysrand/rand_js.go create mode 100644 crypto/internal/sysrand/rand_linux_test.go create mode 100644 crypto/internal/sysrand/rand_netbsd.go create mode 100644 crypto/internal/sysrand/rand_plan9.go create mode 100644 crypto/internal/sysrand/rand_test.go create mode 100644 crypto/internal/sysrand/rand_wasip1.go create mode 100644 crypto/internal/sysrand/rand_windows.go create mode 100644 crypto/mlkem/example_test.go create mode 100644 crypto/mlkem/mlkem.go create mode 100644 crypto/mlkem/mlkem_test.go create mode 100644 crypto/pbkdf2/pbkdf2.go create mode 100644 crypto/pbkdf2/pbkdf2_test.go rename crypto/{aes/_asm/gcm/go.sum => rsa/boring.go} (100%) create mode 100644 crypto/rsa/boring_test.go create mode 100644 crypto/rsa/fips.go delete mode 100644 crypto/rsa/pss.go create mode 100644 crypto/rsa/testdata/keygen2048.txt delete mode 100644 crypto/sha256/_asm/go.mod delete mode 100644 crypto/sha256/_asm/go.sum delete mode 100644 crypto/sha256/fallback_test.go delete mode 100644 crypto/sha256/sha256block_amd64.go delete mode 100644 crypto/sha256/sha256block_arm64.go delete mode 100644 crypto/sha256/sha256block_s390x.go create mode 100644 crypto/sha3/sha3.go create mode 100644 crypto/sha3/sha3_test.go delete mode 100644 crypto/sha512/_asm/go.mod delete mode 100644 crypto/sha512/_asm/go.sum delete mode 100644 crypto/sha512/fallback_test.go delete mode 100644 crypto/sha512/sha512block_amd64.go delete mode 100644 crypto/sha512/sha512block_arm64.go delete mode 100644 crypto/sha512/sha512block_s390x.go delete mode 100644 crypto/ssl3/zcrypto_schemas/__init__.py create mode 100644 crypto/subtle/dit.go create mode 100644 crypto/subtle/dit_test.go delete mode 100644 crypto/subtle/xor_amd64.go delete mode 100644 crypto/subtle/xor_arm64.go create mode 100644 crypto/subtle/xor_linux_test.go create mode 100644 crypto/tls/fips_test.go create mode 100644 crypto/tls/internal/fips140tls/fipstls.go delete mode 100644 crypto/tls/notboring.go delete mode 100644 crypto/x509/boring.go delete mode 100644 crypto/x509/boring_test.go delete mode 100644 crypto/x509/notboring.go create mode 100644 crypto/x509/pkits_test.go delete mode 100644 crypto/x509/revocation/crl/test_crl delete mode 100644 crypto/x509/revocation/google/testdata/APm1SaUzZaPllaSDuZS5yng delete mode 100644 crypto/x509/revocation/google/testdata/crl-set-6375 delete mode 100644 crypto/x509/revocation/google/testdata/test_crlset delete mode 100644 crypto/x509/revocation/microsoft/test_disallowedcert.sst create mode 100644 crypto/x509/testdata/nist-pkits/README.md create mode 100644 crypto/x509/testdata/nist-pkits/certs/AllCertificatesNoPoliciesTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/AllCertificatesSamePoliciesTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/AllCertificatesSamePoliciesTest13EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/AllCertificatesanyPolicyTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/AnyPolicyTest14EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BadCRLIssuerNameCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BadCRLSignatureCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BadSignedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BadnotAfterDateCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BadnotBeforeDateCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BasicSelfIssuedCRLSigningKeyCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BasicSelfIssuedCRLSigningKeyCRLCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BasicSelfIssuedNewKeyCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BasicSelfIssuedNewKeyOldWithNewCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BasicSelfIssuedOldKeyCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/BasicSelfIssuedOldKeyNewWithOldCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/CPSPointerQualifierTest20EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DSACACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DSAParametersInheritedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest12EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/DifferentPoliciesTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/GeneralizedTimeCRLnextUpdateCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/GoodCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/GoodsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/GoodsubCAPanyPolicyMapping1to2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidBadCRLIssuerNameTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidBadCRLSignatureTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidBasicSelfIssuedCRLSigningKeyTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidBasicSelfIssuedCRLSigningKeyTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidBasicSelfIssuedNewWithOldTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidBasicSelfIssuedOldWithNewTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidCASignatureTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidCAnotAfterDateTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidCAnotBeforeDateTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNSnameConstraintsTest31EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNSnameConstraintsTest33EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNSnameConstraintsTest38EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNandRFC822nameConstraintsTest28EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNandRFC822nameConstraintsTest29EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest12EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest13EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest15EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest16EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest17EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest20EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDNnameConstraintsTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidDSASignatureTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidEESignatureTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidEEnotAfterDateTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidEEnotBeforeDateTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidIDPwithindirectCRLTest23EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidIDPwithindirectCRLTest26EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidLongSerialNumberTest18EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidMappingFromanyPolicyTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidMappingToanyPolicyTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidMissingCRLTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidMissingbasicConstraintsTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidNameChainingOrderTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidNameChainingTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidNegativeSerialNumberTest15EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidOldCRLnextUpdateTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidPolicyMappingTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidPolicyMappingTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidPolicyMappingTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidRFC822nameConstraintsTest22EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidRFC822nameConstraintsTest24EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidRFC822nameConstraintsTest26EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidRevokedCATest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidRevokedEETest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedinhibitAnyPolicyTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedinhibitAnyPolicyTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedinhibitPolicyMappingTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedinhibitPolicyMappingTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedinhibitPolicyMappingTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedinhibitPolicyMappingTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedpathLenConstraintTest16EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedrequireExplicitPolicyTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSelfIssuedrequireExplicitPolicyTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSeparateCertificateandCRLKeysTest20EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidSeparateCertificateandCRLKeysTest21EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidURInameConstraintsTest35EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidURInameConstraintsTest37EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidUnknownCRLEntryExtensionTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidUnknownCRLExtensionTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidUnknownCRLExtensionTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidUnknownCriticalCertificateExtensionTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidWrongCRLTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcAFalseTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcAFalseTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcRLIssuerTest27EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcRLIssuerTest31EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcRLIssuerTest32EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcRLIssuerTest34EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidcRLIssuerTest35EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddeltaCRLIndicatorNoBaseTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddeltaCRLTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddeltaCRLTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddeltaCRLTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddeltaCRLTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddeltaCRLTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddistributionPointTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddistributionPointTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddistributionPointTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddistributionPointTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvaliddistributionPointTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitAnyPolicyTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitAnyPolicyTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitAnyPolicyTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitAnyPolicyTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitPolicyMappingTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitPolicyMappingTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitPolicyMappingTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidinhibitPolicyMappingTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidkeyUsageCriticalcRLSignFalseTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidkeyUsageCriticalkeyCertSignFalseTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidkeyUsageNotCriticalcRLSignFalseTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidkeyUsageNotCriticalkeyCertSignFalseTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlyContainsAttributeCertsTest14EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlyContainsCACertsTest12EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlyContainsUserCertsTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlySomeReasonsTest15EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlySomeReasonsTest16EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlySomeReasonsTest17EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlySomeReasonsTest20EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidonlySomeReasonsTest21EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidpathLenConstraintTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidpathLenConstraintTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidpathLenConstraintTest12EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidpathLenConstraintTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidpathLenConstraintTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidpathLenConstraintTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/Invalidpre2000CRLnextUpdateTest12EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/Invalidpre2000UTCEEnotAfterDateTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidrequireExplicitPolicyTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/InvalidrequireExplicitPolicyTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/LongSerialNumberCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/Mapping1to2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/MappingFromanyPolicyCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/MappingToanyPolicyCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/MissingbasicConstraintsCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/NameOrderingCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/NegativeSerialNumberCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/NoCRLCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/NoPoliciesCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/NoissuingDistributionPointCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/OldCRLnextUpdateCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/OverlappingPoliciesTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/P12Mapping1to3CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/P12Mapping1to3subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/P12Mapping1to3subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/P1Mapping1to234CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/P1Mapping1to234subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/P1anyPolicyMapping1to2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PanyPolicyMapping1to2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP1234CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP1234subCAP123Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP1234subsubCAP123P12Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP123CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP123subCAP12Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP123subsubCAP12P1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP123subsubCAP12P2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP123subsubsubCAP12P2P1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP12CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP12subCAP1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP12subsubCAP1P2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP2subCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP2subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/PoliciesP3CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/RFC3280MandatoryAttributeTypesCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/RFC3280OptionalAttributeTypesCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/RevokedsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/RolloverfromPrintableStringtoUTF8StringCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/SeparateCertificateandCRLKeysCA2CRLSigningCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/SeparateCertificateandCRLKeysCA2CertificateSigningCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/SeparateCertificateandCRLKeysCRLSigningCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/SeparateCertificateandCRLKeysCertificateSigningCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/TrustAnchorRootCertificate.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/TwoCRLsCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UIDCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UTF8StringCaseInsensitiveMatchCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UTF8StringEncodedNamesCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UnknownCRLEntryExtensionCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UnknownCRLExtensionCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UserNoticeQualifierTest15EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UserNoticeQualifierTest16EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UserNoticeQualifierTest17EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UserNoticeQualifierTest18EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/UserNoticeQualifierTest19EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidBasicSelfIssuedCRLSigningKeyTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidBasicSelfIssuedNewWithOldTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidBasicSelfIssuedNewWithOldTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidBasicSelfIssuedOldWithNewTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidCertificatePathTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNSnameConstraintsTest30EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNSnameConstraintsTest32EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNandRFC822nameConstraintsTest27EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest14EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest18EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest19EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDNnameConstraintsTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDSAParameterInheritanceTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidDSASignaturesTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidGeneralizedTimeCRLnextUpdateTest13EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidGeneralizedTimenotAfterDateTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidGeneralizedTimenotBeforeDateTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidIDPwithindirectCRLTest22EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidIDPwithindirectCRLTest24EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidIDPwithindirectCRLTest25EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidLongSerialNumberTest16EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidLongSerialNumberTest17EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidNameChainingCapitalizationTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidNameChainingWhitespaceTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidNameChainingWhitespaceTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidNameUIDsTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidNegativeSerialNumberTest14EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidNoissuingDistributionPointTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest12EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest13EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest14EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidPolicyMappingTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidRFC3280MandatoryAttributeTypesTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidRFC3280OptionalAttributeTypesTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidRFC822nameConstraintsTest21EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidRFC822nameConstraintsTest23EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidRFC822nameConstraintsTest25EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidRolloverfromPrintableStringtoUTF8StringTest10EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSelfIssuedinhibitAnyPolicyTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSelfIssuedinhibitAnyPolicyTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSelfIssuedinhibitPolicyMappingTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSelfIssuedpathLenConstraintTest15EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSelfIssuedpathLenConstraintTest17EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSelfIssuedrequireExplicitPolicyTest6EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidSeparateCertificateandCRLKeysTest19EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidTwoCRLsTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidURInameConstraintsTest34EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidURInameConstraintsTest36EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidUTF8StringCaseInsensitiveMatchTest11EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidUTF8StringEncodedNamesTest9EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidUnknownNotCriticalCertificateExtensionTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidbasicConstraintsNotCriticalTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidcRLIssuerTest28EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidcRLIssuerTest29EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidcRLIssuerTest30EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidcRLIssuerTest33EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddeltaCRLTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddeltaCRLTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddeltaCRLTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddeltaCRLTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddistributionPointTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddistributionPointTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddistributionPointTest5EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValiddistributionPointTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidinhibitAnyPolicyTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidinhibitPolicyMappingTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidinhibitPolicyMappingTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidkeyUsageNotCriticalTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidonlyContainsCACertsTest13EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidonlySomeReasonsTest18EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidonlySomeReasonsTest19EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidpathLenConstraintTest13EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidpathLenConstraintTest14EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidpathLenConstraintTest7EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidpathLenConstraintTest8EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/Validpre2000UTCnotBeforeDateTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidrequireExplicitPolicyTest1EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidrequireExplicitPolicyTest2EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/ValidrequireExplicitPolicyTest4EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/WrongCRLCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/anyPolicyCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/basicConstraintsCriticalcAFalseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/basicConstraintsNotCriticalCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/basicConstraintsNotCriticalcAFalseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/deltaCRLCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/deltaCRLCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/deltaCRLCA3Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/deltaCRLIndicatorNoBaseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/distributionPoint1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/distributionPoint2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA3Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA3cRLIssuerCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA4Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA4cRLIssuerCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA5Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/indirectCRLCA6Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy0CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1SelfIssuedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1SelfIssuedsubCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1subCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1subCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1subCAIAP5Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy1subsubCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy5CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy5subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicy5subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitAnyPolicyTest3EE.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping0CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping0subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P12CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P12subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P12subCAIPM5Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P12subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P12subsubCAIPM5Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P1SelfIssuedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P1SelfIssuedsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P1subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping1P1subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping5CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping5subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping5subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/inhibitPolicyMapping5subsubsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/keyUsageCriticalcRLSignFalseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/keyUsageCriticalkeyCertSignFalseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/keyUsageNotCriticalCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/keyUsageNotCriticalcRLSignFalseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/keyUsageNotCriticalkeyCertSignFalseCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN1SelfIssuedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN1subCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN1subCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN1subCA3Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN3CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN3subCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN3subCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN4CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDN5CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDNS1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsDNS2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsRFC822CA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsRFC822CA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsRFC822CA3Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsURI1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/nameConstraintsURI2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlyContainsAttributeCertsCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlyContainsCACertsCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlyContainsUserCertsCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlySomeReasonsCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlySomeReasonsCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlySomeReasonsCA3Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/onlySomeReasonsCA4Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint0CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint0SelfIssuedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint0subCA2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint0subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint1CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint1SelfIssuedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint1SelfIssuedsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint1subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subCA0Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subCA1Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subCA4Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subsubCA00Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subsubCA11Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subsubCA41Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subsubsubCA11XCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pathLenConstraint6subsubsubCA41XCert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/pre2000CRLnextUpdateCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy0CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy0subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy0subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy0subsubsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy10CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy10subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy10subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy10subsubsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy2CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy2SelfIssuedCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy2SelfIssuedsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy2subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy4CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy4subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy4subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy4subsubsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy5CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy5subCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy5subsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy5subsubsubCACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy7CACert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy7subCARE2Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy7subsubCARE2RE4Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/certs/requireExplicitPolicy7subsubsubCARE2RE4Cert.crt create mode 100644 crypto/x509/testdata/nist-pkits/vectors.json create mode 100644 crypto/x509/testdata/policy_intermediate.pem create mode 100644 crypto/x509/testdata/policy_intermediate_any.pem create mode 100644 crypto/x509/testdata/policy_intermediate_duplicate.pem create mode 100644 crypto/x509/testdata/policy_intermediate_invalid.pem create mode 100644 crypto/x509/testdata/policy_intermediate_mapped.pem create mode 100644 crypto/x509/testdata/policy_intermediate_mapped_any.pem create mode 100644 crypto/x509/testdata/policy_intermediate_mapped_oid3.pem create mode 100644 crypto/x509/testdata/policy_intermediate_require.pem create mode 100644 crypto/x509/testdata/policy_intermediate_require1.pem create mode 100644 crypto/x509/testdata/policy_intermediate_require2.pem create mode 100644 crypto/x509/testdata/policy_intermediate_require_duplicate.pem create mode 100644 crypto/x509/testdata/policy_intermediate_require_no_policies.pem create mode 100644 crypto/x509/testdata/policy_leaf.pem create mode 100644 crypto/x509/testdata/policy_leaf_any.pem create mode 100644 crypto/x509/testdata/policy_leaf_duplicate.pem create mode 100644 crypto/x509/testdata/policy_leaf_invalid.pem create mode 100644 crypto/x509/testdata/policy_leaf_none.pem create mode 100644 crypto/x509/testdata/policy_leaf_oid1.pem create mode 100644 crypto/x509/testdata/policy_leaf_oid2.pem create mode 100644 crypto/x509/testdata/policy_leaf_oid3.pem create mode 100644 crypto/x509/testdata/policy_leaf_oid4.pem create mode 100644 crypto/x509/testdata/policy_leaf_oid5.pem create mode 100644 crypto/x509/testdata/policy_leaf_require.pem create mode 100644 crypto/x509/testdata/policy_leaf_require1.pem create mode 100644 crypto/x509/testdata/policy_root.pem create mode 100644 crypto/x509/testdata/policy_root2.pem create mode 100644 crypto/x509/testdata/policy_root_cross_inhibit_mapping.pem diff --git a/crypto/aes/_asm/standard/go.mod b/crypto/aes/_asm/standard/go.mod deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/aes/_asm/standard/go.sum b/crypto/aes/_asm/standard/go.sum deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/aes/aes.go b/crypto/aes/aes.go new file mode 100644 index 00000000000..23c0a1d08e0 --- /dev/null +++ b/crypto/aes/aes.go @@ -0,0 +1,49 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package aes implements AES encryption (formerly Rijndael), as defined in +// U.S. Federal Information Processing Standards Publication 197. +// +// The AES operations in this package are not implemented using constant-time algorithms. +// An exception is when running on systems with enabled hardware support for AES +// that makes these operations constant-time. Examples include amd64 systems using AES-NI +// extensions and s390x systems using Message-Security-Assist extensions. +// On such systems, when the result of NewCipher is passed to cipher.NewGCM, +// the GHASH operation used by GCM is also constant-time. +package aes + +import ( + "strconv" + + "github.com/runZeroInc/excrypto/crypto/cipher" + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" +) + +// The AES block size in bytes. +const BlockSize = 16 + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/aes: invalid key size " + strconv.Itoa(int(k)) +} + +// NewCipher creates and returns a new [cipher.Block]. +// The key argument must be the AES key, +// either 16, 24, or 32 bytes to select +// AES-128, AES-192, or AES-256. +func NewCipher(key []byte) (cipher.Block, error) { + k := len(key) + switch k { + default: + return nil, KeySizeError(k) + case 16, 24, 32: + break + } + if boring.Enabled { + return boring.NewAESCipher(key) + } + return aes.New(key) +} diff --git a/crypto/aes/aes_gcm.go b/crypto/aes/aes_gcm.go deleted file mode 100644 index ae0788438f5..00000000000 --- a/crypto/aes/aes_gcm.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (amd64 || arm64) && !purego - -package aes - -import ( - "errors" - - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/crypto/subtle" -) - -// The following functions are defined in gcm_*.s. - -//go:noescape -func gcmAesInit(productTable *[256]byte, ks []uint32) - -//go:noescape -func gcmAesData(productTable *[256]byte, data []byte, T *[16]byte) - -//go:noescape -func gcmAesEnc(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) - -//go:noescape -func gcmAesDec(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) - -//go:noescape -func gcmAesFinish(productTable *[256]byte, tagMask, T *[16]byte, pLen, dLen uint64) - -const ( - gcmBlockSize = 16 - gcmTagSize = 16 - gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. - gcmStandardNonceSize = 12 -) - -var errOpen = errors.New("cipher: message authentication failed") - -// Assert that aesCipherGCM implements the gcmAble interface. -var _ gcmAble = (*aesCipherGCM)(nil) - -// NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only -// called by [crypto/cipher.NewGCM] via the gcmAble interface. -func (c *aesCipherGCM) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) { - g := &gcmAsm{ks: c.enc[:c.l], nonceSize: nonceSize, tagSize: tagSize} - gcmAesInit(&g.productTable, g.ks) - return g, nil -} - -type gcmAsm struct { - // ks is the key schedule, the length of which depends on the size of - // the AES key. - ks []uint32 - // productTable contains pre-computed multiples of the binary-field - // element used in GHASH. - productTable [256]byte - // nonceSize contains the expected size of the nonce, in bytes. - nonceSize int - // tagSize contains the size of the tag, in bytes. - tagSize int -} - -func (g *gcmAsm) NonceSize() int { - return g.nonceSize -} - -func (g *gcmAsm) Overhead() int { - return g.tagSize -} - -// sliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} - -// Seal encrypts and authenticates plaintext. See the [cipher.AEAD] interface for -// details. -func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != g.nonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize { - panic("crypto/cipher: message too large for GCM") - } - - var counter, tagMask [gcmBlockSize]byte - - if len(nonce) == gcmStandardNonceSize { - // Init counter to nonce||1 - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - // Otherwise counter = GHASH(nonce) - gcmAesData(&g.productTable, nonce, &counter) - gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0)) - } - - encryptBlockAsm(len(g.ks)/4-1, &g.ks[0], &tagMask[0], &counter[0]) - - var tagOut [gcmTagSize]byte - gcmAesData(&g.productTable, data, &tagOut) - - ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) - if alias.InexactOverlap(out[:len(plaintext)], plaintext) { - panic("crypto/cipher: invalid buffer overlap") - } - if len(plaintext) > 0 { - gcmAesEnc(&g.productTable, out, plaintext, &counter, &tagOut, g.ks) - } - gcmAesFinish(&g.productTable, &tagMask, &tagOut, uint64(len(plaintext)), uint64(len(data))) - copy(out[len(plaintext):], tagOut[:]) - - return ret -} - -// Open authenticates and decrypts ciphertext. See the [cipher.AEAD] interface -// for details. -func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(nonce) != g.nonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - // Sanity check to prevent the authentication from always succeeding if an implementation - // leaves tagSize uninitialized, for example. - if g.tagSize < gcmMinimumTagSize { - panic("crypto/cipher: incorrect GCM tag size") - } - - if len(ciphertext) < g.tagSize { - return nil, errOpen - } - if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) { - return nil, errOpen - } - - tag := ciphertext[len(ciphertext)-g.tagSize:] - ciphertext = ciphertext[:len(ciphertext)-g.tagSize] - - // See GCM spec, section 7.1. - var counter, tagMask [gcmBlockSize]byte - - if len(nonce) == gcmStandardNonceSize { - // Init counter to nonce||1 - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - // Otherwise counter = GHASH(nonce) - gcmAesData(&g.productTable, nonce, &counter) - gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0)) - } - - encryptBlockAsm(len(g.ks)/4-1, &g.ks[0], &tagMask[0], &counter[0]) - - var expectedTag [gcmTagSize]byte - gcmAesData(&g.productTable, data, &expectedTag) - - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("crypto/cipher: invalid buffer overlap") - } - if len(ciphertext) > 0 { - gcmAesDec(&g.productTable, out, ciphertext, &counter, &expectedTag, g.ks) - } - gcmAesFinish(&g.productTable, &tagMask, &expectedTag, uint64(len(ciphertext)), uint64(len(data))) - - if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { - clear(out) - return nil, errOpen - } - - return ret, nil -} diff --git a/crypto/aes/aes_test.go b/crypto/aes/aes_test.go index efb9f541e1b..82ad145501b 100644 --- a/crypto/aes/aes_test.go +++ b/crypto/aes/aes_test.go @@ -10,238 +10,9 @@ import ( "testing" ) -// See const.go for overview of math here. - -// Test that powx is initialized correctly. -// (Can adapt this code to generate it too.) -func TestPowx(t *testing.T) { - p := 1 - for i := 0; i < len(powx); i++ { - if powx[i] != byte(p) { - t.Errorf("powx[%d] = %#x, want %#x", i, powx[i], p) - } - p <<= 1 - if p&0x100 != 0 { - p ^= poly - } - } -} - -// Multiply b and c as GF(2) polynomials modulo poly -func mul(b, c uint32) uint32 { - i := b - j := c - s := uint32(0) - for k := uint32(1); k < 0x100 && j != 0; k <<= 1 { - // Invariant: k == 1<>8 - } - } -} - -// Test that decryption tables are correct. -// (Can adapt this code to generate them too.) -func TestTd(t *testing.T) { - for i := 0; i < 256; i++ { - s := uint32(sbox1[i]) - s9 := mul(s, 0x9) - sb := mul(s, 0xb) - sd := mul(s, 0xd) - se := mul(s, 0xe) - w := se<<24 | s9<<16 | sd<<8 | sb - td := [][256]uint32{td0, td1, td2, td3} - for j := 0; j < 4; j++ { - if x := td[j][i]; x != w { - t.Fatalf("td[%d][%d] = %#x, want %#x", j, i, x, w) - } - w = w<<24 | w>>8 - } - } -} - // Test vectors are from FIPS 197: // https://csrc.nist.gov/publications/fips/fips197/fips-197.pdf -// Appendix A of FIPS 197: Key expansion examples -type KeyTest struct { - key []byte - enc []uint32 - dec []uint32 // decryption expansion; not in FIPS 197, computed from C implementation. -} - -var keyTests = []KeyTest{ - { - // A.1. Expansion of a 128-bit Cipher Key - []byte{0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c}, - []uint32{ - 0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x09cf4f3c, - 0xa0fafe17, 0x88542cb1, 0x23a33939, 0x2a6c7605, - 0xf2c295f2, 0x7a96b943, 0x5935807a, 0x7359f67f, - 0x3d80477d, 0x4716fe3e, 0x1e237e44, 0x6d7a883b, - 0xef44a541, 0xa8525b7f, 0xb671253b, 0xdb0bad00, - 0xd4d1c6f8, 0x7c839d87, 0xcaf2b8bc, 0x11f915bc, - 0x6d88a37a, 0x110b3efd, 0xdbf98641, 0xca0093fd, - 0x4e54f70e, 0x5f5fc9f3, 0x84a64fb2, 0x4ea6dc4f, - 0xead27321, 0xb58dbad2, 0x312bf560, 0x7f8d292f, - 0xac7766f3, 0x19fadc21, 0x28d12941, 0x575c006e, - 0xd014f9a8, 0xc9ee2589, 0xe13f0cc8, 0xb6630ca6, - }, - []uint32{ - 0xd014f9a8, 0xc9ee2589, 0xe13f0cc8, 0xb6630ca6, - 0xc7b5a63, 0x1319eafe, 0xb0398890, 0x664cfbb4, - 0xdf7d925a, 0x1f62b09d, 0xa320626e, 0xd6757324, - 0x12c07647, 0xc01f22c7, 0xbc42d2f3, 0x7555114a, - 0x6efcd876, 0xd2df5480, 0x7c5df034, 0xc917c3b9, - 0x6ea30afc, 0xbc238cf6, 0xae82a4b4, 0xb54a338d, - 0x90884413, 0xd280860a, 0x12a12842, 0x1bc89739, - 0x7c1f13f7, 0x4208c219, 0xc021ae48, 0x969bf7b, - 0xcc7505eb, 0x3e17d1ee, 0x82296c51, 0xc9481133, - 0x2b3708a7, 0xf262d405, 0xbc3ebdbf, 0x4b617d62, - 0x2b7e1516, 0x28aed2a6, 0xabf71588, 0x9cf4f3c, - }, - }, - { - // A.2. Expansion of a 192-bit Cipher Key - []byte{ - 0x8e, 0x73, 0xb0, 0xf7, 0xda, 0x0e, 0x64, 0x52, 0xc8, 0x10, 0xf3, 0x2b, 0x80, 0x90, 0x79, 0xe5, - 0x62, 0xf8, 0xea, 0xd2, 0x52, 0x2c, 0x6b, 0x7b, - }, - []uint32{ - 0x8e73b0f7, 0xda0e6452, 0xc810f32b, 0x809079e5, - 0x62f8ead2, 0x522c6b7b, 0xfe0c91f7, 0x2402f5a5, - 0xec12068e, 0x6c827f6b, 0x0e7a95b9, 0x5c56fec2, - 0x4db7b4bd, 0x69b54118, 0x85a74796, 0xe92538fd, - 0xe75fad44, 0xbb095386, 0x485af057, 0x21efb14f, - 0xa448f6d9, 0x4d6dce24, 0xaa326360, 0x113b30e6, - 0xa25e7ed5, 0x83b1cf9a, 0x27f93943, 0x6a94f767, - 0xc0a69407, 0xd19da4e1, 0xec1786eb, 0x6fa64971, - 0x485f7032, 0x22cb8755, 0xe26d1352, 0x33f0b7b3, - 0x40beeb28, 0x2f18a259, 0x6747d26b, 0x458c553e, - 0xa7e1466c, 0x9411f1df, 0x821f750a, 0xad07d753, - 0xca400538, 0x8fcc5006, 0x282d166a, 0xbc3ce7b5, - 0xe98ba06f, 0x448c773c, 0x8ecc7204, 0x01002202, - }, - nil, - }, - { - // A.3. Expansion of a 256-bit Cipher Key - []byte{ - 0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d, 0x77, 0x81, - 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3, 0x09, 0x14, 0xdf, 0xf4, - }, - []uint32{ - 0x603deb10, 0x15ca71be, 0x2b73aef0, 0x857d7781, - 0x1f352c07, 0x3b6108d7, 0x2d9810a3, 0x0914dff4, - 0x9ba35411, 0x8e6925af, 0xa51a8b5f, 0x2067fcde, - 0xa8b09c1a, 0x93d194cd, 0xbe49846e, 0xb75d5b9a, - 0xd59aecb8, 0x5bf3c917, 0xfee94248, 0xde8ebe96, - 0xb5a9328a, 0x2678a647, 0x98312229, 0x2f6c79b3, - 0x812c81ad, 0xdadf48ba, 0x24360af2, 0xfab8b464, - 0x98c5bfc9, 0xbebd198e, 0x268c3ba7, 0x09e04214, - 0x68007bac, 0xb2df3316, 0x96e939e4, 0x6c518d80, - 0xc814e204, 0x76a9fb8a, 0x5025c02d, 0x59c58239, - 0xde136967, 0x6ccc5a71, 0xfa256395, 0x9674ee15, - 0x5886ca5d, 0x2e2f31d7, 0x7e0af1fa, 0x27cf73c3, - 0x749c47ab, 0x18501dda, 0xe2757e4f, 0x7401905a, - 0xcafaaae3, 0xe4d59b34, 0x9adf6ace, 0xbd10190d, - 0xfe4890d1, 0xe6188d0b, 0x046df344, 0x706c631e, - }, - nil, - }, -} - -// Test key expansion against FIPS 197 examples. -func TestExpandKey(t *testing.T) { -L: - for i, tt := range keyTests { - enc := make([]uint32, len(tt.enc)) - var dec []uint32 - if tt.dec != nil { - dec = make([]uint32, len(tt.dec)) - } - // This test could only test Go version of expandKey because asm - // version might use different memory layout for expanded keys - // This is OK because we don't expose expanded keys to the outside - expandKeyGo(tt.key, enc, dec) - for j, v := range enc { - if v != tt.enc[j] { - t.Errorf("key %d: enc[%d] = %#x, want %#x", i, j, v, tt.enc[j]) - continue L - } - } - for j, v := range dec { - if v != tt.dec[j] { - t.Errorf("key %d: dec[%d] = %#x, want %#x", i, j, v, tt.dec[j]) - continue L - } - } - } -} - // Appendix B, C of FIPS 197: Cipher examples, Example vectors. type CryptTest struct { key []byte @@ -282,6 +53,10 @@ var encryptTests = []CryptTest{ // Test Cipher Encrypt method against FIPS 197 examples. func TestCipherEncrypt(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", testCipherEncrypt) +} + +func testCipherEncrypt(t *testing.T) { for i, tt := range encryptTests { c, err := NewCipher(tt.key) if err != nil { @@ -301,6 +76,10 @@ func TestCipherEncrypt(t *testing.T) { // Test Cipher Decrypt against FIPS 197 examples. func TestCipherDecrypt(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", testCipherDecrypt) +} + +func testCipherDecrypt(t *testing.T) { for i, tt := range encryptTests { c, err := NewCipher(tt.key) if err != nil { @@ -320,6 +99,10 @@ func TestCipherDecrypt(t *testing.T) { // Test AES against the general cipher.Block interface tester func TestAESBlock(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", testAESBlock) +} + +func testAESBlock(t *testing.T) { for _, keylen := range []int{128, 192, 256} { t.Run(fmt.Sprintf("AES-%d", keylen), func(t *testing.T) { cryptotest.TestBlock(t, keylen/8, NewCipher) @@ -365,20 +148,6 @@ func benchmarkDecrypt(b *testing.B, tt CryptTest) { } } -func BenchmarkExpand(b *testing.B) { - b.Run("AES-128", func(b *testing.B) { benchmarkExpand(b, encryptTests[1]) }) - b.Run("AES-192", func(b *testing.B) { benchmarkExpand(b, encryptTests[2]) }) - b.Run("AES-256", func(b *testing.B) { benchmarkExpand(b, encryptTests[3]) }) -} - -func benchmarkExpand(b *testing.B, tt CryptTest) { - c := &aesCipher{l: uint8(len(tt.key) + 28)} - b.ResetTimer() - for i := 0; i < b.N; i++ { - expandKey(tt.key, c.enc[:c.l], c.dec[:c.l]) - } -} - func BenchmarkCreateCipher(b *testing.B) { b.Run("AES-128", func(b *testing.B) { benchmarkCreateCipher(b, encryptTests[1]) }) b.Run("AES-192", func(b *testing.B) { benchmarkCreateCipher(b, encryptTests[2]) }) diff --git a/crypto/aes/asm_s390x.s b/crypto/aes/asm_s390x.s deleted file mode 100644 index 5da0d8bf9cb..00000000000 --- a/crypto/aes/asm_s390x.s +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -#include "textflag.h" - -// func cryptBlocks(c code, key, dst, src *byte, length int) -TEXT ·cryptBlocks(SB),NOSPLIT,$0-40 - MOVD key+8(FP), R1 - MOVD dst+16(FP), R2 - MOVD src+24(FP), R4 - MOVD length+32(FP), R5 - MOVD c+0(FP), R0 -loop: - KM R2, R4 // cipher message (KM) - BVS loop // branch back if interrupted - XOR R0, R0 - RET - -// func cryptBlocksChain(c code, iv, key, dst, src *byte, length int) -TEXT ·cryptBlocksChain(SB),NOSPLIT,$48-48 - LA params-48(SP), R1 - MOVD iv+8(FP), R8 - MOVD key+16(FP), R9 - MVC $16, 0(R8), 0(R1) // move iv into params - MVC $32, 0(R9), 16(R1) // move key into params - MOVD dst+24(FP), R2 - MOVD src+32(FP), R4 - MOVD length+40(FP), R5 - MOVD c+0(FP), R0 -loop: - KMC R2, R4 // cipher message with chaining (KMC) - BVS loop // branch back if interrupted - XOR R0, R0 - MVC $16, 0(R1), 0(R8) // update iv - RET - -// func xorBytes(dst, a, b []byte) int -TEXT ·xorBytes(SB),NOSPLIT,$0-80 - MOVD dst_base+0(FP), R1 - MOVD a_base+24(FP), R2 - MOVD b_base+48(FP), R3 - MOVD a_len+32(FP), R4 - MOVD b_len+56(FP), R5 - CMPBLE R4, R5, skip - MOVD R5, R4 -skip: - MOVD R4, ret+72(FP) - MOVD $0, R5 - CMPBLT R4, $8, tail -loop: - MOVD 0(R2)(R5*1), R7 - MOVD 0(R3)(R5*1), R8 - XOR R7, R8 - MOVD R8, 0(R1)(R5*1) - LAY 8(R5), R5 - SUB $8, R4 - CMPBGE R4, $8, loop -tail: - CMPBEQ R4, $0, done - MOVB 0(R2)(R5*1), R7 - MOVB 0(R3)(R5*1), R8 - XOR R7, R8 - MOVB R8, 0(R1)(R5*1) - LAY 1(R5), R5 - SUB $1, R4 - BR tail -done: - RET - -// func cryptBlocksGCM(fn code, key, dst, src, buf []byte, cnt *[16]byte) -TEXT ·cryptBlocksGCM(SB),NOSPLIT,$0-112 - MOVD src_len+64(FP), R0 - MOVD buf_base+80(FP), R1 - MOVD cnt+104(FP), R12 - LMG (R12), R2, R3 - - // Check that the src size is less than or equal to the buffer size. - MOVD buf_len+88(FP), R4 - CMP R0, R4 - BGT crash - - // Check that the src size is a multiple of 16-bytes. - MOVD R0, R4 - AND $0xf, R4 - BLT crash // non-zero - - // Check that the src size is less than or equal to the dst size. - MOVD dst_len+40(FP), R4 - CMP R0, R4 - BGT crash - - MOVD R2, R4 - MOVD R2, R6 - MOVD R2, R8 - MOVD R3, R5 - MOVD R3, R7 - MOVD R3, R9 - ADDW $1, R5 - ADDW $2, R7 - ADDW $3, R9 -incr: - CMP R0, $64 - BLT tail - STMG R2, R9, (R1) - ADDW $4, R3 - ADDW $4, R5 - ADDW $4, R7 - ADDW $4, R9 - MOVD $64(R1), R1 - SUB $64, R0 - BR incr -tail: - CMP R0, $0 - BEQ crypt - STMG R2, R3, (R1) - ADDW $1, R3 - MOVD $16(R1), R1 - SUB $16, R0 - BR tail -crypt: - STMG R2, R3, (R12) // update next counter value - MOVD fn+0(FP), R0 // function code (encryption) - MOVD key_base+8(FP), R1 // key - MOVD buf_base+80(FP), R2 // counter values - MOVD dst_base+32(FP), R4 // dst - MOVD src_base+56(FP), R6 // src - MOVD src_len+64(FP), R7 // len -loop: - KMCTR R4, R2, R6 // cipher message with counter (KMCTR) - BVS loop // branch back if interrupted - RET -crash: - MOVD $0, (R0) - RET - -// func ghash(key *gcmHashKey, hash *[16]byte, data []byte) -TEXT ·ghash(SB),NOSPLIT,$32-40 - MOVD $65, R0 // GHASH function code - MOVD key+0(FP), R2 - LMG (R2), R6, R7 - MOVD hash+8(FP), R8 - LMG (R8), R4, R5 - MOVD $params-32(SP), R1 - STMG R4, R7, (R1) - LMG data+16(FP), R2, R3 // R2=base, R3=len -loop: - KIMD R0, R2 // compute intermediate message digest (KIMD) - BVS loop // branch back if interrupted - MVC $16, (R1), (R8) - MOVD $0, R0 - RET - -// func kmaGCM(fn code, key, dst, src, aad []byte, tag *[16]byte, cnt *gcmCount) -TEXT ·kmaGCM(SB),NOSPLIT,$112-120 - MOVD fn+0(FP), R0 - MOVD $params-112(SP), R1 - - // load ptr/len pairs - LMG dst+32(FP), R2, R3 // R2=base R3=len - LMG src+56(FP), R4, R5 // R4=base R5=len - LMG aad+80(FP), R6, R7 // R6=base R7=len - - // setup parameters - MOVD cnt+112(FP), R8 - XC $12, (R1), (R1) // reserved - MVC $4, 12(R8), 12(R1) // set chain value - MVC $16, (R8), 64(R1) // set initial counter value - XC $32, 16(R1), 16(R1) // set hash subkey and tag - SLD $3, R7, R12 - MOVD R12, 48(R1) // set total AAD length - SLD $3, R5, R12 - MOVD R12, 56(R1) // set total plaintext/ciphertext length - - LMG key+8(FP), R8, R9 // R8=base R9=len - MVC $16, (R8), 80(R1) // set key - CMPBEQ R9, $16, kma - MVC $8, 16(R8), 96(R1) - CMPBEQ R9, $24, kma - MVC $8, 24(R8), 104(R1) - -kma: - KMA R2, R6, R4 // Cipher Message with Authentication - BVS kma - - MOVD tag+104(FP), R2 - MVC $16, 16(R1), 0(R2) // copy tag to output - MOVD cnt+112(FP), R8 - MVC $4, 12(R1), 12(R8) // update counter value - - RET diff --git a/crypto/aes/cbc_ppc64x.go b/crypto/aes/cbc_ppc64x.go deleted file mode 100644 index 0ec39143bf8..00000000000 --- a/crypto/aes/cbc_ppc64x.go +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2021 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (ppc64 || ppc64le) && !purego - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" -) - -// Assert that aesCipherAsm implements the cbcEncAble and cbcDecAble interfaces. -var _ cbcEncAble = (*aesCipherAsm)(nil) -var _ cbcDecAble = (*aesCipherAsm)(nil) - -const cbcEncrypt = 1 -const cbcDecrypt = 0 - -type cbc struct { - b *aesCipherAsm - enc int - iv [BlockSize]byte -} - -func (b *aesCipherAsm) NewCBCEncrypter(iv []byte) cipher.BlockMode { - var c cbc - c.b = b - c.enc = cbcEncrypt - copy(c.iv[:], iv) - return &c -} - -func (b *aesCipherAsm) NewCBCDecrypter(iv []byte) cipher.BlockMode { - var c cbc - c.b = b - c.enc = cbcDecrypt - copy(c.iv[:], iv) - return &c -} - -func (x *cbc) BlockSize() int { return BlockSize } - -// cryptBlocksChain invokes the cipher message identifying encrypt or decrypt. -// -//go:noescape -func cryptBlocksChain(src, dst *byte, length int, key *uint32, iv *byte, enc int, nr int) - -func (x *cbc) CryptBlocks(dst, src []byte) { - if len(src)%BlockSize != 0 { - panic("crypto/cipher: input not full blocks") - } - if len(dst) < len(src) { - panic("crypto/cipher: output smaller than input") - } - if alias.InexactOverlap(dst[:len(src)], src) { - panic("crypto/cipher: invalid buffer overlap") - } - if len(src) > 0 { - if x.enc == cbcEncrypt { - cryptBlocksChain(&src[0], &dst[0], len(src), &x.b.enc[0], &x.iv[0], x.enc, int(x.b.l)/4-1) - } else { - cryptBlocksChain(&src[0], &dst[0], len(src), &x.b.dec[0], &x.iv[0], x.enc, int(x.b.l)/4-1) - } - } -} - -func (x *cbc) SetIV(iv []byte) { - if len(iv) != BlockSize { - panic("cipher: incorrect length IV") - } - copy(x.iv[:], iv) -} diff --git a/crypto/aes/cbc_s390x.go b/crypto/aes/cbc_s390x.go deleted file mode 100644 index 04fbb60496f..00000000000 --- a/crypto/aes/cbc_s390x.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" -) - -// Assert that aesCipherAsm implements the cbcEncAble and cbcDecAble interfaces. -var _ cbcEncAble = (*aesCipherAsm)(nil) -var _ cbcDecAble = (*aesCipherAsm)(nil) - -type cbc struct { - b *aesCipherAsm - c code - iv [BlockSize]byte -} - -func (b *aesCipherAsm) NewCBCEncrypter(iv []byte) cipher.BlockMode { - var c cbc - c.b = b - c.c = b.function - copy(c.iv[:], iv) - return &c -} - -func (b *aesCipherAsm) NewCBCDecrypter(iv []byte) cipher.BlockMode { - var c cbc - c.b = b - c.c = b.function + 128 // decrypt function code is encrypt + 128 - copy(c.iv[:], iv) - return &c -} - -func (x *cbc) BlockSize() int { return BlockSize } - -// cryptBlocksChain invokes the cipher message with chaining (KMC) instruction -// with the given function code. The length must be a multiple of BlockSize (16). -// -//go:noescape -func cryptBlocksChain(c code, iv, key, dst, src *byte, length int) - -func (x *cbc) CryptBlocks(dst, src []byte) { - if len(src)%BlockSize != 0 { - panic("crypto/cipher: input not full blocks") - } - if len(dst) < len(src) { - panic("crypto/cipher: output smaller than input") - } - if alias.InexactOverlap(dst[:len(src)], src) { - panic("crypto/cipher: invalid buffer overlap") - } - if len(src) > 0 { - cryptBlocksChain(x.c, &x.iv[0], &x.b.key[0], &dst[0], &src[0], len(src)) - } -} - -func (x *cbc) SetIV(iv []byte) { - if len(iv) != BlockSize { - panic("cipher: incorrect length IV") - } - copy(x.iv[:], iv) -} diff --git a/crypto/aes/cipher.go b/crypto/aes/cipher.go deleted file mode 100644 index fb4cf3116ed..00000000000 --- a/crypto/aes/cipher.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes - -import ( - "strconv" - - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/crypto/internal/boring" -) - -// The AES block size in bytes. -const BlockSize = 16 - -// A cipher is an instance of AES encryption using a particular key. -type aesCipher struct { - l uint8 // only this length of the enc and dec array is actually used - enc [28 + 32]uint32 - dec [28 + 32]uint32 -} - -type KeySizeError int - -func (k KeySizeError) Error() string { - return "crypto/aes: invalid key size " + strconv.Itoa(int(k)) -} - -// NewCipher creates and returns a new [cipher.Block]. -// The key argument should be the AES key, -// either 16, 24, or 32 bytes to select -// AES-128, AES-192, or AES-256. -func NewCipher(key []byte) (cipher.Block, error) { - k := len(key) - switch k { - default: - return nil, KeySizeError(k) - case 16, 24, 32: - break - } - if boring.Enabled { - return boring.NewAESCipher(key) - } - return newCipher(key) -} - -// newCipherGeneric creates and returns a new cipher.Block -// implemented in pure Go. -func newCipherGeneric(key []byte) (cipher.Block, error) { - c := aesCipher{l: uint8(len(key) + 28)} - expandKeyGo(key, c.enc[:c.l], c.dec[:c.l]) - return &c, nil -} - -func (c *aesCipher) BlockSize() int { return BlockSize } - -func (c *aesCipher) Encrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { - panic("crypto/aes: invalid buffer overlap") - } - encryptBlockGo(c.enc[:c.l], dst, src) -} - -func (c *aesCipher) Decrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { - panic("crypto/aes: invalid buffer overlap") - } - decryptBlockGo(c.dec[:c.l], dst, src) -} diff --git a/crypto/aes/cipher_asm.go b/crypto/aes/cipher_asm.go deleted file mode 100644 index e2b14b77c17..00000000000 --- a/crypto/aes/cipher_asm.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (amd64 || arm64 || ppc64 || ppc64le) && !purego - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/crypto/internal/boring" - "github.com/runZeroInc/excrypto/internal/cpu" - "github.com/runZeroInc/excrypto/internal/goarch" -) - -// defined in asm_*.s - -//go:noescape -func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) - -//go:noescape -func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) - -//go:noescape -func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) - -type aesCipherAsm struct { - aesCipher -} - -// aesCipherGCM implements crypto/cipher.gcmAble so that crypto/cipher.NewGCM -// will use the optimised implementation in aes_gcm.go when possible. -// Instances of this type only exist when hasGCMAsm returns true. Likewise, -// the gcmAble implementation is in aes_gcm.go. -type aesCipherGCM struct { - aesCipherAsm -} - -var supportsAES = cpu.X86.HasAES || cpu.ARM64.HasAES || goarch.IsPpc64 == 1 || goarch.IsPpc64le == 1 -var supportsGFMUL = cpu.X86.HasPCLMULQDQ || cpu.ARM64.HasPMULL - -func newCipher(key []byte) (cipher.Block, error) { - if !supportsAES { - return newCipherGeneric(key) - } - // Note that under certain circumstances, we only return the inner aesCipherAsm. - // This avoids an unnecessary allocation of the aesCipher struct. - c := aesCipherGCM{aesCipherAsm{aesCipher{l: uint8(len(key) + 28)}}} - var rounds int - switch len(key) { - case 128 / 8: - rounds = 10 - case 192 / 8: - rounds = 12 - case 256 / 8: - rounds = 14 - default: - return nil, KeySizeError(len(key)) - } - - expandKeyAsm(rounds, &key[0], &c.enc[0], &c.dec[0]) - if supportsAES && supportsGFMUL { - return &c, nil - } - return &c.aesCipherAsm, nil -} - -func (c *aesCipherAsm) BlockSize() int { return BlockSize } - -func (c *aesCipherAsm) Encrypt(dst, src []byte) { - boring.Unreachable() - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { - panic("crypto/aes: invalid buffer overlap") - } - encryptBlockAsm(int(c.l)/4-1, &c.enc[0], &dst[0], &src[0]) -} - -func (c *aesCipherAsm) Decrypt(dst, src []byte) { - boring.Unreachable() - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { - panic("crypto/aes: invalid buffer overlap") - } - decryptBlockAsm(int(c.l)/4-1, &c.dec[0], &dst[0], &src[0]) -} - -// expandKey is used by BenchmarkExpand to ensure that the asm implementation -// of key expansion is used for the benchmark when it is available. -func expandKey(key []byte, enc, dec []uint32) { - if supportsAES { - rounds := 10 // rounds needed for AES128 - switch len(key) { - case 192 / 8: - rounds = 12 - case 256 / 8: - rounds = 14 - } - expandKeyAsm(rounds, &key[0], &enc[0], &dec[0]) - } else { - expandKeyGo(key, enc, dec) - } -} diff --git a/crypto/aes/cipher_generic.go b/crypto/aes/cipher_generic.go deleted file mode 100644 index 79e49d846ff..00000000000 --- a/crypto/aes/cipher_generic.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (!amd64 && !s390x && !ppc64 && !ppc64le && !arm64) || purego - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" -) - -// newCipher calls the newCipherGeneric function -// directly. Platforms with hardware accelerated -// implementations of AES should implement their -// own version of newCipher (which may then call -// newCipherGeneric if needed). -func newCipher(key []byte) (cipher.Block, error) { - return newCipherGeneric(key) -} - -// expandKey is used by BenchmarkExpand and should -// call an assembly implementation if one is available. -func expandKey(key []byte, enc, dec []uint32) { - expandKeyGo(key, enc, dec) -} diff --git a/crypto/aes/cipher_s390x.go b/crypto/aes/cipher_s390x.go deleted file mode 100644 index a1a6f6c0f2b..00000000000 --- a/crypto/aes/cipher_s390x.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/internal/cpu" -) - -type code int - -// Function codes for the cipher message family of instructions. -const ( - aes128 code = 18 - aes192 = 19 - aes256 = 20 -) - -type aesCipherAsm struct { - function code // code for cipher message instruction - key []byte // key (128, 192 or 256 bits) - storage [32]byte // array backing key slice -} - -// cryptBlocks invokes the cipher message (KM) instruction with -// the given function code. This is equivalent to AES in ECB -// mode. The length must be a multiple of BlockSize (16). -// -//go:noescape -func cryptBlocks(c code, key, dst, src *byte, length int) - -func newCipher(key []byte) (cipher.Block, error) { - // The aesCipherAsm type implements the cbcEncAble, cbcDecAble, - // ctrAble and gcmAble interfaces. We therefore need to check - // for all the features required to implement these modes. - // Keep in sync with crypto/tls/common.go. - if !(cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM)) { - return newCipherGeneric(key) - } - - var function code - switch len(key) { - case 128 / 8: - function = aes128 - case 192 / 8: - function = aes192 - case 256 / 8: - function = aes256 - default: - return nil, KeySizeError(len(key)) - } - - var c aesCipherAsm - c.function = function - c.key = c.storage[:len(key)] - copy(c.key, key) - return &c, nil -} - -func (c *aesCipherAsm) BlockSize() int { return BlockSize } - -func (c *aesCipherAsm) Encrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { - panic("crypto/aes: invalid buffer overlap") - } - cryptBlocks(c.function, &c.key[0], &dst[0], &src[0], BlockSize) -} - -func (c *aesCipherAsm) Decrypt(dst, src []byte) { - if len(src) < BlockSize { - panic("crypto/aes: input not full block") - } - if len(dst) < BlockSize { - panic("crypto/aes: output not full block") - } - if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { - panic("crypto/aes: invalid buffer overlap") - } - // The decrypt function code is equal to the function code + 128. - cryptBlocks(c.function+128, &c.key[0], &dst[0], &src[0], BlockSize) -} - -// expandKey is used by BenchmarkExpand. cipher message (KM) does not need key -// expansion so there is no assembly equivalent. -func expandKey(key []byte, enc, dec []uint32) { - expandKeyGo(key, enc, dec) -} diff --git a/crypto/aes/ctr_s390x.go b/crypto/aes/ctr_s390x.go deleted file mode 100644 index 49f6ecba47d..00000000000 --- a/crypto/aes/ctr_s390x.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/internal/byteorder" -) - -// Assert that aesCipherAsm implements the ctrAble interface. -var _ ctrAble = (*aesCipherAsm)(nil) - -// xorBytes xors the contents of a and b and places the resulting values into -// dst. If a and b are not the same length then the number of bytes processed -// will be equal to the length of shorter of the two. Returns the number -// of bytes processed. -// -//go:noescape -func xorBytes(dst, a, b []byte) int - -// streamBufferSize is the number of bytes of encrypted counter values to cache. -const streamBufferSize = 32 * BlockSize - -type aesctr struct { - block *aesCipherAsm // block cipher - ctr [2]uint64 // next value of the counter (big endian) - buffer []byte // buffer for the encrypted counter values - storage [streamBufferSize]byte // array backing buffer slice -} - -// NewCTR returns a Stream which encrypts/decrypts using the AES block -// cipher in counter mode. The length of iv must be the same as [BlockSize]. -func (c *aesCipherAsm) NewCTR(iv []byte) cipher.Stream { - if len(iv) != BlockSize { - panic("cipher.NewCTR: IV length must equal block size") - } - var ac aesctr - ac.block = c - ac.ctr[0] = byteorder.BeUint64(iv[0:]) // high bits - ac.ctr[1] = byteorder.BeUint64(iv[8:]) // low bits - ac.buffer = ac.storage[:0] - return &ac -} - -func (c *aesctr) refill() { - // Fill up the buffer with an incrementing count. - c.buffer = c.storage[:streamBufferSize] - c0, c1 := c.ctr[0], c.ctr[1] - for i := 0; i < streamBufferSize; i += 16 { - byteorder.BePutUint64(c.buffer[i+0:], c0) - byteorder.BePutUint64(c.buffer[i+8:], c1) - - // Increment in big endian: c0 is high, c1 is low. - c1++ - if c1 == 0 { - // add carry - c0++ - } - } - c.ctr[0], c.ctr[1] = c0, c1 - // Encrypt the buffer using AES in ECB mode. - cryptBlocks(c.block.function, &c.block.key[0], &c.buffer[0], &c.buffer[0], streamBufferSize) -} - -func (c *aesctr) XORKeyStream(dst, src []byte) { - if len(dst) < len(src) { - panic("crypto/cipher: output smaller than input") - } - if alias.InexactOverlap(dst[:len(src)], src) { - panic("crypto/cipher: invalid buffer overlap") - } - for len(src) > 0 { - if len(c.buffer) == 0 { - c.refill() - } - n := xorBytes(dst, src, c.buffer) - c.buffer = c.buffer[n:] - src = src[n:] - dst = dst[n:] - } -} diff --git a/crypto/aes/gcm_ppc64x.go b/crypto/aes/gcm_ppc64x.go deleted file mode 100644 index e7125a0f9a1..00000000000 --- a/crypto/aes/gcm_ppc64x.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2019 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build (ppc64le || ppc64) && !purego - -package aes - -import ( - "errors" - "runtime" - - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/crypto/subtle" - "github.com/runZeroInc/excrypto/internal/byteorder" -) - -// This file implements GCM using an optimized GHASH function. - -//go:noescape -func gcmInit(productTable *[256]byte, h []byte) - -//go:noescape -func gcmHash(output []byte, productTable *[256]byte, inp []byte, len int) - -//go:noescape -func gcmMul(output []byte, productTable *[256]byte) - -const ( - gcmCounterSize = 16 - gcmBlockSize = 16 - gcmTagSize = 16 - gcmStandardNonceSize = 12 -) - -var errOpen = errors.New("cipher: message authentication failed") - -// Assert that aesCipherGCM implements the gcmAble interface. -var _ gcmAble = (*aesCipherAsm)(nil) - -type gcmAsm struct { - cipher *aesCipherAsm - // ks is the key schedule, the length of which depends on the size of - // the AES key. - ks []uint32 - // productTable contains pre-computed multiples of the binary-field - // element used in GHASH. - productTable [256]byte - // nonceSize contains the expected size of the nonce, in bytes. - nonceSize int - // tagSize contains the size of the tag, in bytes. - tagSize int -} - -func counterCryptASM(nr int, out, in []byte, counter *[gcmBlockSize]byte, key *uint32) - -// NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only -// called by [crypto/cipher.NewGCM] via the gcmAble interface. -func (c *aesCipherAsm) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) { - var h1, h2 uint64 - g := &gcmAsm{cipher: c, ks: c.enc[:c.l], nonceSize: nonceSize, tagSize: tagSize} - - hle := make([]byte, gcmBlockSize) - - c.Encrypt(hle, hle) - - // Reverse the bytes in each 8 byte chunk - // Load little endian, store big endian - if runtime.GOARCH == "ppc64le" { - h1 = byteorder.LeUint64(hle[:8]) - h2 = byteorder.LeUint64(hle[8:]) - } else { - h1 = byteorder.BeUint64(hle[:8]) - h2 = byteorder.BeUint64(hle[8:]) - } - byteorder.BePutUint64(hle[:8], h1) - byteorder.BePutUint64(hle[8:], h2) - gcmInit(&g.productTable, hle) - - return g, nil -} - -func (g *gcmAsm) NonceSize() int { - return g.nonceSize -} - -func (g *gcmAsm) Overhead() int { - return g.tagSize -} - -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} - -// deriveCounter computes the initial GCM counter state from the given nonce. -func (g *gcmAsm) deriveCounter(counter *[gcmBlockSize]byte, nonce []byte) { - if len(nonce) == gcmStandardNonceSize { - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - var hash [16]byte - g.paddedGHASH(&hash, nonce) - lens := gcmLengths(0, uint64(len(nonce))*8) - g.paddedGHASH(&hash, lens[:]) - copy(counter[:], hash[:]) - } -} - -// counterCrypt encrypts in using AES in counter mode and places the result -// into out. counter is the initial count value and will be updated with the next -// count value. The length of out must be greater than or equal to the length -// of in. -// counterCryptASM implements counterCrypt which then allows the loop to -// be unrolled and optimized. -func (g *gcmAsm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) { - counterCryptASM(int(g.cipher.l)/4-1, out, in, counter, &g.cipher.enc[0]) - -} - -// increments the rightmost 32-bits of the count value by 1. -func gcmInc32(counterBlock *[16]byte) { - c := counterBlock[len(counterBlock)-4:] - x := byteorder.BeUint32(c) + 1 - byteorder.BePutUint32(c, x) -} - -// paddedGHASH pads data with zeroes until its length is a multiple of -// 16-bytes. It then calculates a new value for hash using the ghash -// algorithm. -func (g *gcmAsm) paddedGHASH(hash *[16]byte, data []byte) { - if siz := len(data) - (len(data) % gcmBlockSize); siz > 0 { - gcmHash(hash[:], &g.productTable, data[:], siz) - data = data[siz:] - } - if len(data) > 0 { - var s [16]byte - copy(s[:], data) - gcmHash(hash[:], &g.productTable, s[:], len(s)) - } -} - -// auth calculates GHASH(ciphertext, additionalData), masks the result with -// tagMask and writes the result to out. -func (g *gcmAsm) auth(out, ciphertext, aad []byte, tagMask *[gcmTagSize]byte) { - var hash [16]byte - g.paddedGHASH(&hash, aad) - g.paddedGHASH(&hash, ciphertext) - lens := gcmLengths(uint64(len(aad))*8, uint64(len(ciphertext))*8) - g.paddedGHASH(&hash, lens[:]) - - copy(out, hash[:]) - for i := range out { - out[i] ^= tagMask[i] - } -} - -// Seal encrypts and authenticates plaintext. See the [cipher.AEAD] interface for -// details. -func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != g.nonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize { - panic("cipher: message too large for GCM") - } - - ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) - if alias.InexactOverlap(out[:len(plaintext)], plaintext) { - panic("crypto/cipher: invalid buffer overlap") - } - - var counter, tagMask [gcmBlockSize]byte - g.deriveCounter(&counter, nonce) - - g.cipher.Encrypt(tagMask[:], counter[:]) - gcmInc32(&counter) - - g.counterCrypt(out, plaintext, &counter) - g.auth(out[len(plaintext):], out[:len(plaintext)], data, &tagMask) - - return ret -} - -// Open authenticates and decrypts ciphertext. See the [cipher.AEAD] interface -// for details. -func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(nonce) != g.nonceSize { - panic("cipher: incorrect nonce length given to GCM") - } - if len(ciphertext) < g.tagSize { - return nil, errOpen - } - if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) { - return nil, errOpen - } - - tag := ciphertext[len(ciphertext)-g.tagSize:] - ciphertext = ciphertext[:len(ciphertext)-g.tagSize] - - var counter, tagMask [gcmBlockSize]byte - g.deriveCounter(&counter, nonce) - - g.cipher.Encrypt(tagMask[:], counter[:]) - gcmInc32(&counter) - - var expectedTag [gcmTagSize]byte - g.auth(expectedTag[:], ciphertext, data, &tagMask) - - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("crypto/cipher: invalid buffer overlap") - } - - if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { - clear(out) - return nil, errOpen - } - - g.counterCrypt(out, ciphertext, &counter) - return ret, nil -} - -func gcmLengths(len0, len1 uint64) [16]byte { - return [16]byte{ - byte(len0 >> 56), - byte(len0 >> 48), - byte(len0 >> 40), - byte(len0 >> 32), - byte(len0 >> 24), - byte(len0 >> 16), - byte(len0 >> 8), - byte(len0), - byte(len1 >> 56), - byte(len1 >> 48), - byte(len1 >> 40), - byte(len1 >> 32), - byte(len1 >> 24), - byte(len1 >> 16), - byte(len1 >> 8), - byte(len1), - } -} diff --git a/crypto/aes/gcm_s390x.go b/crypto/aes/gcm_s390x.go deleted file mode 100644 index 752beac892d..00000000000 --- a/crypto/aes/gcm_s390x.go +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package aes - -import ( - "errors" - - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/crypto/subtle" - "github.com/runZeroInc/excrypto/internal/byteorder" - "github.com/runZeroInc/excrypto/internal/cpu" -) - -// This file contains two implementations of AES-GCM. The first implementation -// (gcmAsm) uses the KMCTR instruction to encrypt using AES in counter mode and -// the KIMD instruction for GHASH. The second implementation (gcmKMA) uses the -// newer KMA instruction which performs both operations. - -// gcmCount represents a 16-byte big-endian count value. -type gcmCount [16]byte - -// inc increments the rightmost 32-bits of the count value by 1. -func (x *gcmCount) inc() { - byteorder.BePutUint32(x[len(x)-4:], byteorder.BeUint32(x[len(x)-4:])+1) -} - -// gcmLengths writes len0 || len1 as big-endian values to a 16-byte array. -func gcmLengths(len0, len1 uint64) [16]byte { - v := [16]byte{} - byteorder.BePutUint64(v[0:], len0) - byteorder.BePutUint64(v[8:], len1) - return v -} - -// gcmHashKey represents the 16-byte hash key required by the GHASH algorithm. -type gcmHashKey [16]byte - -type gcmAsm struct { - block *aesCipherAsm - hashKey gcmHashKey - nonceSize int - tagSize int -} - -const ( - gcmBlockSize = 16 - gcmTagSize = 16 - gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. - gcmStandardNonceSize = 12 -) - -var errOpen = errors.New("cipher: message authentication failed") - -// Assert that aesCipherAsm implements the gcmAble interface. -var _ gcmAble = (*aesCipherAsm)(nil) - -// NewGCM returns the AES cipher wrapped in Galois Counter Mode. This is only -// called by [crypto/cipher.NewGCM] via the gcmAble interface. -func (c *aesCipherAsm) NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) { - var hk gcmHashKey - c.Encrypt(hk[:], hk[:]) - g := gcmAsm{ - block: c, - hashKey: hk, - nonceSize: nonceSize, - tagSize: tagSize, - } - if cpu.S390X.HasAESGCM { - g := gcmKMA{g} - return &g, nil - } - return &g, nil -} - -func (g *gcmAsm) NonceSize() int { - return g.nonceSize -} - -func (g *gcmAsm) Overhead() int { - return g.tagSize -} - -// sliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} - -// ghash uses the GHASH algorithm to hash data with the given key. The initial -// hash value is given by hash which will be updated with the new hash value. -// The length of data must be a multiple of 16-bytes. -// -//go:noescape -func ghash(key *gcmHashKey, hash *[16]byte, data []byte) - -// paddedGHASH pads data with zeroes until its length is a multiple of -// 16-bytes. It then calculates a new value for hash using the GHASH algorithm. -func (g *gcmAsm) paddedGHASH(hash *[16]byte, data []byte) { - siz := len(data) &^ 0xf // align size to 16-bytes - if siz > 0 { - ghash(&g.hashKey, hash, data[:siz]) - data = data[siz:] - } - if len(data) > 0 { - var s [16]byte - copy(s[:], data) - ghash(&g.hashKey, hash, s[:]) - } -} - -// cryptBlocksGCM encrypts src using AES in counter mode using the given -// function code and key. The rightmost 32-bits of the counter are incremented -// between each block as required by the GCM spec. The initial counter value -// is given by cnt, which is updated with the value of the next counter value -// to use. -// -// The lengths of both dst and buf must be greater than or equal to the length -// of src. buf may be partially or completely overwritten during the execution -// of the function. -// -//go:noescape -func cryptBlocksGCM(fn code, key, dst, src, buf []byte, cnt *gcmCount) - -// counterCrypt encrypts src using AES in counter mode and places the result -// into dst. cnt is the initial count value and will be updated with the next -// count value. The length of dst must be greater than or equal to the length -// of src. -func (g *gcmAsm) counterCrypt(dst, src []byte, cnt *gcmCount) { - // Copying src into a buffer improves performance on some models when - // src and dst point to the same underlying array. We also need a - // buffer for counter values. - var ctrbuf, srcbuf [2048]byte - for len(src) >= 16 { - siz := len(src) - if len(src) > len(ctrbuf) { - siz = len(ctrbuf) - } - siz &^= 0xf // align siz to 16-bytes - copy(srcbuf[:], src[:siz]) - cryptBlocksGCM(g.block.function, g.block.key, dst[:siz], srcbuf[:siz], ctrbuf[:], cnt) - src = src[siz:] - dst = dst[siz:] - } - if len(src) > 0 { - var x [16]byte - g.block.Encrypt(x[:], cnt[:]) - for i := range src { - dst[i] = src[i] ^ x[i] - } - cnt.inc() - } -} - -// deriveCounter computes the initial GCM counter state from the given nonce. -// See NIST SP 800-38D, section 7.1. -func (g *gcmAsm) deriveCounter(nonce []byte) gcmCount { - // GCM has two modes of operation with respect to the initial counter - // state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path" - // for nonces of other lengths. For a 96-bit nonce, the nonce, along - // with a four-byte big-endian counter starting at one, is used - // directly as the starting counter. For other nonce sizes, the counter - // is computed by passing it through the GHASH function. - var counter gcmCount - if len(nonce) == gcmStandardNonceSize { - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - var hash [16]byte - g.paddedGHASH(&hash, nonce) - lens := gcmLengths(0, uint64(len(nonce))*8) - g.paddedGHASH(&hash, lens[:]) - copy(counter[:], hash[:]) - } - return counter -} - -// auth calculates GHASH(ciphertext, additionalData), masks the result with -// tagMask and writes the result to out. -func (g *gcmAsm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmTagSize]byte) { - var hash [16]byte - g.paddedGHASH(&hash, additionalData) - g.paddedGHASH(&hash, ciphertext) - lens := gcmLengths(uint64(len(additionalData))*8, uint64(len(ciphertext))*8) - g.paddedGHASH(&hash, lens[:]) - - copy(out, hash[:]) - for i := range out { - out[i] ^= tagMask[i] - } -} - -// Seal encrypts and authenticates plaintext. See the [cipher.AEAD] interface for -// details. -func (g *gcmAsm) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != g.nonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize { - panic("crypto/cipher: message too large for GCM") - } - - ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) - if alias.InexactOverlap(out[:len(plaintext)], plaintext) { - panic("crypto/cipher: invalid buffer overlap") - } - - counter := g.deriveCounter(nonce) - - var tagMask [gcmBlockSize]byte - g.block.Encrypt(tagMask[:], counter[:]) - counter.inc() - - var tagOut [gcmTagSize]byte - g.counterCrypt(out, plaintext, &counter) - g.auth(tagOut[:], out[:len(plaintext)], data, &tagMask) - copy(out[len(plaintext):], tagOut[:]) - - return ret -} - -// Open authenticates and decrypts ciphertext. See the [cipher.AEAD] interface -// for details. -func (g *gcmAsm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(nonce) != g.nonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - // Sanity check to prevent the authentication from always succeeding if an implementation - // leaves tagSize uninitialized, for example. - if g.tagSize < gcmMinimumTagSize { - panic("crypto/cipher: incorrect GCM tag size") - } - if len(ciphertext) < g.tagSize { - return nil, errOpen - } - if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) { - return nil, errOpen - } - - tag := ciphertext[len(ciphertext)-g.tagSize:] - ciphertext = ciphertext[:len(ciphertext)-g.tagSize] - - counter := g.deriveCounter(nonce) - - var tagMask [gcmBlockSize]byte - g.block.Encrypt(tagMask[:], counter[:]) - counter.inc() - - var expectedTag [gcmTagSize]byte - g.auth(expectedTag[:], ciphertext, data, &tagMask) - - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("crypto/cipher: invalid buffer overlap") - } - - if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { - // The AESNI code decrypts and authenticates concurrently, and - // so overwrites dst in the event of a tag mismatch. That - // behavior is mimicked here in order to be consistent across - // platforms. - clear(out) - return nil, errOpen - } - - g.counterCrypt(out, ciphertext, &counter) - return ret, nil -} - -// gcmKMA implements the cipher.AEAD interface using the KMA instruction. It should -// only be used if hasKMA is true. -type gcmKMA struct { - gcmAsm -} - -// flags for the KMA instruction -const ( - kmaHS = 1 << 10 // hash subkey supplied - kmaLAAD = 1 << 9 // last series of additional authenticated data - kmaLPC = 1 << 8 // last series of plaintext or ciphertext blocks - kmaDecrypt = 1 << 7 // decrypt -) - -// kmaGCM executes the encryption or decryption operation given by fn. The tag -// will be calculated and written to tag. cnt should contain the current -// counter state and will be overwritten with the updated counter state. -// TODO(mundaym): could pass in hash subkey -// -//go:noescape -func kmaGCM(fn code, key, dst, src, aad []byte, tag *[16]byte, cnt *gcmCount) - -// Seal encrypts and authenticates plaintext. See the [cipher.AEAD] interface for -// details. -func (g *gcmKMA) Seal(dst, nonce, plaintext, data []byte) []byte { - if len(nonce) != g.nonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - if uint64(len(plaintext)) > ((1<<32)-2)*BlockSize { - panic("crypto/cipher: message too large for GCM") - } - - ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) - if alias.InexactOverlap(out[:len(plaintext)], plaintext) { - panic("crypto/cipher: invalid buffer overlap") - } - - counter := g.deriveCounter(nonce) - fc := g.block.function | kmaLAAD | kmaLPC - - var tag [gcmTagSize]byte - kmaGCM(fc, g.block.key, out[:len(plaintext)], plaintext, data, &tag, &counter) - copy(out[len(plaintext):], tag[:]) - - return ret -} - -// Open authenticates and decrypts ciphertext. See the [cipher.AEAD] interface -// for details. -func (g *gcmKMA) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(nonce) != g.nonceSize { - panic("crypto/cipher: incorrect nonce length given to GCM") - } - if len(ciphertext) < g.tagSize { - return nil, errOpen - } - if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(BlockSize)+uint64(g.tagSize) { - return nil, errOpen - } - - tag := ciphertext[len(ciphertext)-g.tagSize:] - ciphertext = ciphertext[:len(ciphertext)-g.tagSize] - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("crypto/cipher: invalid buffer overlap") - } - - if g.tagSize < gcmMinimumTagSize { - panic("crypto/cipher: incorrect GCM tag size") - } - - counter := g.deriveCounter(nonce) - fc := g.block.function | kmaLAAD | kmaLPC | kmaDecrypt - - var expectedTag [gcmTagSize]byte - kmaGCM(fc, g.block.key, out[:len(ciphertext)], ciphertext, data, &expectedTag, &counter) - - if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { - // The AESNI code decrypts and authenticates concurrently, and - // so overwrites dst in the event of a tag mismatch. That - // behavior is mimicked here in order to be consistent across - // platforms. - clear(out) - return nil, errOpen - } - - return ret, nil -} diff --git a/crypto/aes/modes.go b/crypto/aes/modes.go deleted file mode 100644 index c48fc7aaf76..00000000000 --- a/crypto/aes/modes.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" -) - -// gcmAble is implemented by cipher.Blocks that can provide an optimized -// implementation of GCM through the AEAD interface. -// See crypto/cipher/gcm.go. -type gcmAble interface { - NewGCM(nonceSize, tagSize int) (cipher.AEAD, error) -} - -// cbcEncAble is implemented by cipher.Blocks that can provide an optimized -// implementation of CBC encryption through the cipher.BlockMode interface. -// See crypto/cipher/cbc.go. -type cbcEncAble interface { - NewCBCEncrypter(iv []byte) cipher.BlockMode -} - -// cbcDecAble is implemented by cipher.Blocks that can provide an optimized -// implementation of CBC decryption through the cipher.BlockMode interface. -// See crypto/cipher/cbc.go. -type cbcDecAble interface { - NewCBCDecrypter(iv []byte) cipher.BlockMode -} - -// ctrAble is implemented by cipher.Blocks that can provide an optimized -// implementation of CTR through the cipher.Stream interface. -// See crypto/cipher/ctr.go. -type ctrAble interface { - NewCTR(iv []byte) cipher.Stream -} diff --git a/crypto/aes/modes_test.go b/crypto/aes/modes_test.go deleted file mode 100644 index e0726049d32..00000000000 --- a/crypto/aes/modes_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package aes - -import ( - "github.com/runZeroInc/excrypto/crypto/cipher" - "testing" -) - -// Check that the optimized implementations of cipher modes will -// be picked up correctly. - -// testInterface can be asserted to check that a type originates -// from this test group. -type testInterface interface { - InAESPackage() bool -} - -// testBlock implements the cipher.Block interface and any *Able -// interfaces that need to be tested. -type testBlock struct{} - -func (*testBlock) BlockSize() int { return 0 } -func (*testBlock) Encrypt(a, b []byte) {} -func (*testBlock) Decrypt(a, b []byte) {} -func (*testBlock) NewGCM(int, int) (cipher.AEAD, error) { - return &testAEAD{}, nil -} -func (*testBlock) NewCBCEncrypter([]byte) cipher.BlockMode { - return &testBlockMode{} -} -func (*testBlock) NewCBCDecrypter([]byte) cipher.BlockMode { - return &testBlockMode{} -} -func (*testBlock) NewCTR([]byte) cipher.Stream { - return &testStream{} -} - -// testAEAD implements the cipher.AEAD interface. -type testAEAD struct{} - -func (*testAEAD) NonceSize() int { return 0 } -func (*testAEAD) Overhead() int { return 0 } -func (*testAEAD) Seal(a, b, c, d []byte) []byte { return []byte{} } -func (*testAEAD) Open(a, b, c, d []byte) ([]byte, error) { return []byte{}, nil } -func (*testAEAD) InAESPackage() bool { return true } - -// Test the gcmAble interface is detected correctly by the cipher package. -func TestGCMAble(t *testing.T) { - b := cipher.Block(&testBlock{}) - if _, ok := b.(gcmAble); !ok { - t.Fatalf("testBlock does not implement the gcmAble interface") - } - aead, err := cipher.NewGCM(b) - if err != nil { - t.Fatalf("%v", err) - } - if _, ok := aead.(testInterface); !ok { - t.Fatalf("cipher.NewGCM did not use gcmAble interface") - } -} - -// testBlockMode implements the cipher.BlockMode interface. -type testBlockMode struct{} - -func (*testBlockMode) BlockSize() int { return 0 } -func (*testBlockMode) CryptBlocks(a, b []byte) {} -func (*testBlockMode) InAESPackage() bool { return true } - -// Test the cbcEncAble interface is detected correctly by the cipher package. -func TestCBCEncAble(t *testing.T) { - b := cipher.Block(&testBlock{}) - if _, ok := b.(cbcEncAble); !ok { - t.Fatalf("testBlock does not implement the cbcEncAble interface") - } - bm := cipher.NewCBCEncrypter(b, []byte{}) - if _, ok := bm.(testInterface); !ok { - t.Fatalf("cipher.NewCBCEncrypter did not use cbcEncAble interface") - } -} - -// Test the cbcDecAble interface is detected correctly by the cipher package. -func TestCBCDecAble(t *testing.T) { - b := cipher.Block(&testBlock{}) - if _, ok := b.(cbcDecAble); !ok { - t.Fatalf("testBlock does not implement the cbcDecAble interface") - } - bm := cipher.NewCBCDecrypter(b, []byte{}) - if _, ok := bm.(testInterface); !ok { - t.Fatalf("cipher.NewCBCDecrypter did not use cbcDecAble interface") - } -} - -// testStream implements the cipher.Stream interface. -type testStream struct{} - -func (*testStream) XORKeyStream(a, b []byte) {} -func (*testStream) InAESPackage() bool { return true } - -// Test the ctrAble interface is detected correctly by the cipher package. -func TestCTRAble(t *testing.T) { - b := cipher.Block(&testBlock{}) - if _, ok := b.(ctrAble); !ok { - t.Fatalf("testBlock does not implement the ctrAble interface") - } - s := cipher.NewCTR(b, []byte{}) - if _, ok := s.(testInterface); !ok { - t.Fatalf("cipher.NewCTR did not use ctrAble interface") - } -} diff --git a/crypto/cipher/benchmark_test.go b/crypto/cipher/benchmark_test.go index 533966e0322..0ac053086d8 100644 --- a/crypto/cipher/benchmark_test.go +++ b/crypto/cipher/benchmark_test.go @@ -86,28 +86,16 @@ func benchmarkAESStream(b *testing.B, mode func(cipher.Block, []byte) cipher.Str const almost1K = 1024 - 5 const almost8K = 8*1024 - 5 -func BenchmarkAESCFBEncrypt1K(b *testing.B) { - benchmarkAESStream(b, cipher.NewCFBEncrypter, make([]byte, almost1K)) -} - -func BenchmarkAESCFBDecrypt1K(b *testing.B) { - benchmarkAESStream(b, cipher.NewCFBDecrypter, make([]byte, almost1K)) -} - -func BenchmarkAESCFBDecrypt8K(b *testing.B) { - benchmarkAESStream(b, cipher.NewCFBDecrypter, make([]byte, almost8K)) -} - -func BenchmarkAESOFB1K(b *testing.B) { - benchmarkAESStream(b, cipher.NewOFB, make([]byte, almost1K)) -} - -func BenchmarkAESCTR1K(b *testing.B) { - benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost1K)) -} - -func BenchmarkAESCTR8K(b *testing.B) { - benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost8K)) +func BenchmarkAESCTR(b *testing.B) { + b.Run("50", func(b *testing.B) { + benchmarkAESStream(b, cipher.NewCTR, make([]byte, 50)) + }) + b.Run("1K", func(b *testing.B) { + benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost1K)) + }) + b.Run("8K", func(b *testing.B) { + benchmarkAESStream(b, cipher.NewCTR, make([]byte, almost8K)) + }) } func BenchmarkAESCBCEncrypt1K(b *testing.B) { diff --git a/crypto/cipher/cbc.go b/crypto/cipher/cbc.go index 6bc78c1f95d..5fc35e96790 100644 --- a/crypto/cipher/cbc.go +++ b/crypto/cipher/cbc.go @@ -14,7 +14,9 @@ package cipher import ( "bytes" - "github.com/runZeroInc/excrypto/crypto/internal/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -37,9 +39,8 @@ func newCBC(b Block, iv []byte) *cbc { type cbcEncrypter cbc // cbcEncAble is an interface implemented by ciphers that have a specific -// optimized implementation of CBC encryption, like crypto/aes. -// NewCBCEncrypter will check for this interface and return the specific -// BlockMode if found. +// optimized implementation of CBC encryption. crypto/aes doesn't use this +// anymore, and we'd like to eventually remove it. type cbcEncAble interface { NewCBCEncrypter(iv []byte) BlockMode } @@ -51,6 +52,12 @@ func NewCBCEncrypter(b Block, iv []byte) BlockMode { if len(iv) != b.BlockSize() { panic("cipher.NewCBCEncrypter: IV length must equal block size") } + if b, ok := b.(*aes.Block); ok { + return aes.NewCBCEncrypter(b, [16]byte(iv)) + } + if fips140only.Enabled { + panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode") + } if cbc, ok := b.(cbcEncAble); ok { return cbc.NewCBCEncrypter(iv) } @@ -80,6 +87,9 @@ func (x *cbcEncrypter) CryptBlocks(dst, src []byte) { if alias.InexactOverlap(dst[:len(src)], src) { panic("crypto/cipher: invalid buffer overlap") } + if _, ok := x.b.(*aes.Block); ok { + panic("crypto/cipher: internal error: generic CBC used with AES") + } iv := x.iv @@ -108,9 +118,8 @@ func (x *cbcEncrypter) SetIV(iv []byte) { type cbcDecrypter cbc // cbcDecAble is an interface implemented by ciphers that have a specific -// optimized implementation of CBC decryption, like crypto/aes. -// NewCBCDecrypter will check for this interface and return the specific -// BlockMode if found. +// optimized implementation of CBC decryption. crypto/aes doesn't use this +// anymore, and we'd like to eventually remove it. type cbcDecAble interface { NewCBCDecrypter(iv []byte) BlockMode } @@ -122,6 +131,12 @@ func NewCBCDecrypter(b Block, iv []byte) BlockMode { if len(iv) != b.BlockSize() { panic("cipher.NewCBCDecrypter: IV length must equal block size") } + if b, ok := b.(*aes.Block); ok { + return aes.NewCBCDecrypter(b, [16]byte(iv)) + } + if fips140only.Enabled { + panic("crypto/cipher: use of CBC with non-AES ciphers is not allowed in FIPS 140-only mode") + } if cbc, ok := b.(cbcDecAble); ok { return cbc.NewCBCDecrypter(iv) } @@ -151,6 +166,9 @@ func (x *cbcDecrypter) CryptBlocks(dst, src []byte) { if alias.InexactOverlap(dst[:len(src)], src) { panic("crypto/cipher: invalid buffer overlap") } + if _, ok := x.b.(*aes.Block); ok { + panic("crypto/cipher: internal error: generic CBC used with AES") + } if len(src) == 0 { return } diff --git a/crypto/cipher/cbc_aes_test.go b/crypto/cipher/cbc_aes_test.go index 0d98a50d158..f45e8a12a2f 100644 --- a/crypto/cipher/cbc_aes_test.go +++ b/crypto/cipher/cbc_aes_test.go @@ -12,9 +12,11 @@ package cipher_test import ( "bytes" + "testing" + "github.com/runZeroInc/excrypto/crypto/aes" "github.com/runZeroInc/excrypto/crypto/cipher" - "testing" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" ) var cbcAESTests = []struct { @@ -64,6 +66,10 @@ var cbcAESTests = []struct { } func TestCBCEncrypterAES(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", testCBCEncrypterAES) +} + +func testCBCEncrypterAES(t *testing.T) { for _, test := range cbcAESTests { c, err := aes.NewCipher(test.key) if err != nil { @@ -84,6 +90,10 @@ func TestCBCEncrypterAES(t *testing.T) { } func TestCBCDecrypterAES(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", testCBCDecrypterAES) +} + +func testCBCDecrypterAES(t *testing.T) { for _, test := range cbcAESTests { c, err := aes.NewCipher(test.key) if err != nil { diff --git a/crypto/cipher/cbc_test.go b/crypto/cipher/cbc_test.go index 532c3ccd0c0..1b154871b14 100644 --- a/crypto/cipher/cbc_test.go +++ b/crypto/cipher/cbc_test.go @@ -18,22 +18,23 @@ import ( // Test CBC Blockmode against the general cipher.BlockMode interface tester func TestCBCBlockMode(t *testing.T) { - for _, keylen := range []int{128, 192, 256} { + cryptotest.TestAllImplementations(t, "aes", func(t *testing.T) { + for _, keylen := range []int{128, 192, 256} { + t.Run(fmt.Sprintf("AES-%d", keylen), func(t *testing.T) { + rng := newRandReader(t) - t.Run(fmt.Sprintf("AES-%d", keylen), func(t *testing.T) { - rng := newRandReader(t) + key := make([]byte, keylen/8) + rng.Read(key) - key := make([]byte, keylen/8) - rng.Read(key) + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - - cryptotest.TestBlockMode(t, block, cipher.NewCBCEncrypter, cipher.NewCBCDecrypter) - }) - } + cryptotest.TestBlockMode(t, block, cipher.NewCBCEncrypter, cipher.NewCBCDecrypter) + }) + } + }) t.Run("DES", func(t *testing.T) { rng := newRandReader(t) diff --git a/crypto/cipher/cfb.go b/crypto/cipher/cfb.go index eaf629ca4cd..5238a7823ea 100644 --- a/crypto/cipher/cfb.go +++ b/crypto/cipher/cfb.go @@ -7,7 +7,8 @@ package cipher import ( - "github.com/runZeroInc/excrypto/crypto/internal/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -53,14 +54,32 @@ func (x *cfb) XORKeyStream(dst, src []byte) { // NewCFBEncrypter returns a [Stream] which encrypts with cipher feedback mode, // using the given [Block]. The iv must be the same length as the [Block]'s block // size. +// +// Deprecated: CFB mode is not authenticated, which generally enables active +// attacks to manipulate and recover the plaintext. It is recommended that +// applications use [AEAD] modes instead. The standard library implementation of +// CFB is also unoptimized and not validated as part of the FIPS 140-3 module. +// If an unauthenticated [Stream] mode is required, use [NewCTR] instead. func NewCFBEncrypter(block Block, iv []byte) Stream { + if fips140only.Enabled { + panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode") + } return newCFB(block, iv, false) } // NewCFBDecrypter returns a [Stream] which decrypts with cipher feedback mode, // using the given [Block]. The iv must be the same length as the [Block]'s block // size. +// +// Deprecated: CFB mode is not authenticated, which generally enables active +// attacks to manipulate and recover the plaintext. It is recommended that +// applications use [AEAD] modes instead. The standard library implementation of +// CFB is also unoptimized and not validated as part of the FIPS 140-3 module. +// If an unauthenticated [Stream] mode is required, use [NewCTR] instead. func NewCFBDecrypter(block Block, iv []byte) Stream { + if fips140only.Enabled { + panic("crypto/cipher: use of CFB is not allowed in FIPS 140-only mode") + } return newCFB(block, iv, true) } diff --git a/crypto/cipher/cipher.go b/crypto/cipher/cipher.go index df6f596b4d0..4d631991ee1 100644 --- a/crypto/cipher/cipher.go +++ b/crypto/cipher/cipher.go @@ -59,3 +59,40 @@ type BlockMode interface { // maintains state and does not reset at each CryptBlocks call. CryptBlocks(dst, src []byte) } + +// AEAD is a cipher mode providing authenticated encryption with associated +// data. For a description of the methodology, see +// https://en.wikipedia.org/wiki/Authenticated_encryption. +type AEAD interface { + // NonceSize returns the size of the nonce that must be passed to Seal + // and Open. + NonceSize() int + + // Overhead returns the maximum difference between the lengths of a + // plaintext and its ciphertext. + Overhead() int + + // Seal encrypts and authenticates plaintext, authenticates the + // additional data and appends the result to dst, returning the updated + // slice. The nonce must be NonceSize() bytes long and unique for all + // time, for a given key. + // + // To reuse plaintext's storage for the encrypted output, use plaintext[:0] + // as dst. Otherwise, the remaining capacity of dst must not overlap plaintext. + // dst and additionalData may not overlap. + Seal(dst, nonce, plaintext, additionalData []byte) []byte + + // Open decrypts and authenticates ciphertext, authenticates the + // additional data and, if successful, appends the resulting plaintext + // to dst, returning the updated slice. The nonce must be NonceSize() + // bytes long and both it and the additional data must match the + // value passed to Seal. + // + // To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] + // as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext. + // dst and additionalData may not overlap. + // + // Even if the function fails, the contents of dst, up to its capacity, + // may be overwritten. + Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) +} diff --git a/crypto/cipher/cipher_test.go b/crypto/cipher/cipher_test.go deleted file mode 100644 index 65ea0016f28..00000000000 --- a/crypto/cipher/cipher_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cipher_test - -import ( - "bytes" - "testing" - - "github.com/runZeroInc/excrypto/crypto/aes" - "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/des" -) - -func TestCryptBlocks(t *testing.T) { - buf := make([]byte, 16) - block, _ := aes.NewCipher(buf) - - mode := cipher.NewCBCDecrypter(block, buf) - mustPanic(t, "crypto/cipher: input not full blocks", func() { mode.CryptBlocks(buf, buf[:3]) }) - mustPanic(t, "crypto/cipher: output smaller than input", func() { mode.CryptBlocks(buf[:3], buf) }) - - mode = cipher.NewCBCEncrypter(block, buf) - mustPanic(t, "crypto/cipher: input not full blocks", func() { mode.CryptBlocks(buf, buf[:3]) }) - mustPanic(t, "crypto/cipher: output smaller than input", func() { mode.CryptBlocks(buf[:3], buf) }) -} - -func mustPanic(t *testing.T, msg string, f func()) { - defer func() { - err := recover() - if err == nil { - t.Errorf("function did not panic, wanted %q", msg) - } else if err != msg { - t.Errorf("got panic %v, wanted %q", err, msg) - } - }() - f() -} - -func TestEmptyPlaintext(t *testing.T) { - var key [16]byte - a, err := aes.NewCipher(key[:16]) - if err != nil { - t.Fatal(err) - } - d, err := des.NewCipher(key[:8]) - if err != nil { - t.Fatal(err) - } - - s := 16 - pt := make([]byte, s) - ct := make([]byte, s) - for i := 0; i < 16; i++ { - pt[i], ct[i] = byte(i), byte(i) - } - - assertEqual := func(name string, got, want []byte) { - if !bytes.Equal(got, want) { - t.Fatalf("%s: got %v, want %v", name, got, want) - } - } - - for _, b := range []cipher.Block{a, d} { - iv := make([]byte, b.BlockSize()) - cbce := cipher.NewCBCEncrypter(b, iv) - cbce.CryptBlocks(ct, pt[:0]) - assertEqual("CBC encrypt", ct, pt) - - cbcd := cipher.NewCBCDecrypter(b, iv) - cbcd.CryptBlocks(ct, pt[:0]) - assertEqual("CBC decrypt", ct, pt) - - cfbe := cipher.NewCFBEncrypter(b, iv) - cfbe.XORKeyStream(ct, pt[:0]) - assertEqual("CFB encrypt", ct, pt) - - cfbd := cipher.NewCFBDecrypter(b, iv) - cfbd.XORKeyStream(ct, pt[:0]) - assertEqual("CFB decrypt", ct, pt) - - ctr := cipher.NewCTR(b, iv) - ctr.XORKeyStream(ct, pt[:0]) - assertEqual("CTR", ct, pt) - - ofb := cipher.NewOFB(b, iv) - ofb.XORKeyStream(ct, pt[:0]) - assertEqual("OFB", ct, pt) - } -} diff --git a/crypto/cipher/ctr.go b/crypto/cipher/ctr.go index 4b6e0890d2a..293883ebfef 100644 --- a/crypto/cipher/ctr.go +++ b/crypto/cipher/ctr.go @@ -15,7 +15,9 @@ package cipher import ( "bytes" - "github.com/runZeroInc/excrypto/crypto/internal/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -29,8 +31,8 @@ type ctr struct { const streamBufferSize = 512 // ctrAble is an interface implemented by ciphers that have a specific optimized -// implementation of CTR, like crypto/aes. NewCTR will check for this interface -// and return the specific Stream if found. +// implementation of CTR. crypto/aes doesn't use this anymore, and we'd like to +// eventually remove it. type ctrAble interface { NewCTR(iv []byte) Stream } @@ -38,6 +40,12 @@ type ctrAble interface { // NewCTR returns a [Stream] which encrypts/decrypts using the given [Block] in // counter mode. The length of iv must be the same as the [Block]'s block size. func NewCTR(block Block, iv []byte) Stream { + if block, ok := block.(*aes.Block); ok { + return aesCtrWrapper{aes.NewCTR(block, iv)} + } + if fips140only.Enabled { + panic("crypto/cipher: use of CTR with non-AES ciphers is not allowed in FIPS 140-only mode") + } if ctr, ok := block.(ctrAble); ok { return ctr.NewCTR(iv) } @@ -56,6 +64,15 @@ func NewCTR(block Block, iv []byte) Stream { } } +// aesCtrWrapper hides extra methods from aes.CTR. +type aesCtrWrapper struct { + c *aes.CTR +} + +func (x aesCtrWrapper) XORKeyStream(dst, src []byte) { + x.c.XORKeyStream(dst, src) +} + func (x *ctr) refill() { remain := len(x.out) - x.outUsed copy(x.out, x.out[x.outUsed:]) @@ -84,6 +101,9 @@ func (x *ctr) XORKeyStream(dst, src []byte) { if alias.InexactOverlap(dst[:len(src)], src) { panic("crypto/cipher: invalid buffer overlap") } + if _, ok := x.b.(*aes.Block); ok { + panic("crypto/cipher: internal error: generic CTR used with AES") + } for len(src) > 0 { if x.outUsed >= len(x.out)-x.b.BlockSize() { x.refill() diff --git a/crypto/cipher/ctr_aes_test.go b/crypto/cipher/ctr_aes_test.go index 472ad9cc6e4..e330e6ff474 100644 --- a/crypto/cipher/ctr_aes_test.go +++ b/crypto/cipher/ctr_aes_test.go @@ -12,9 +12,18 @@ package cipher_test import ( "bytes" + fipsaes "crypto/internal/fips140/aes" + "encoding/hex" + "fmt" + "math/rand" + "sort" + "strings" + "testing" + "github.com/runZeroInc/excrypto/crypto/aes" "github.com/runZeroInc/excrypto/crypto/cipher" - "testing" + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" ) var commonCounter = []byte{0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff} @@ -66,6 +75,10 @@ var ctrAESTests = []struct { } func TestCTR_AES(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", testCTR_AES) +} + +func testCTR_AES(t *testing.T) { for _, tt := range ctrAESTests { test := tt.name @@ -100,3 +113,194 @@ func TestCTR_AES(t *testing.T) { } } } + +func makeTestingCiphers(aesBlock cipher.Block, iv []byte) (genericCtr, multiblockCtr cipher.Stream) { + return cipher.NewCTR(wrap(aesBlock), iv), cipher.NewCTR(aesBlock, iv) +} + +func randBytes(t *testing.T, r *rand.Rand, count int) []byte { + t.Helper() + buf := make([]byte, count) + n, err := r.Read(buf) + if err != nil { + t.Fatal(err) + } + if n != count { + t.Fatal("short read from Rand") + } + return buf +} + +const aesBlockSize = 16 + +type ctrAble interface { + NewCTR(iv []byte) cipher.Stream +} + +// Verify that multiblock AES CTR (src/crypto/aes/ctr_*.s) +// produces the same results as generic single-block implementation. +// This test runs checks on random IV. +func TestCTR_AES_multiblock_random_IV(t *testing.T) { + r := rand.New(rand.NewSource(54321)) + iv := randBytes(t, r, aesBlockSize) + const Size = 100 + + for _, keySize := range []int{16, 24, 32} { + keySize := keySize + t.Run(fmt.Sprintf("keySize=%d", keySize), func(t *testing.T) { + key := randBytes(t, r, keySize) + aesBlock, err := aes.NewCipher(key) + if err != nil { + t.Fatal(err) + } + genericCtr, _ := makeTestingCiphers(aesBlock, iv) + + plaintext := randBytes(t, r, Size) + + // Generate reference ciphertext. + genericCiphertext := make([]byte, len(plaintext)) + genericCtr.XORKeyStream(genericCiphertext, plaintext) + + // Split the text in 3 parts in all possible ways and encrypt them + // individually using multiblock implementation to catch edge cases. + + for part1 := 0; part1 <= Size; part1++ { + part1 := part1 + t.Run(fmt.Sprintf("part1=%d", part1), func(t *testing.T) { + for part2 := 0; part2 <= Size-part1; part2++ { + part2 := part2 + t.Run(fmt.Sprintf("part2=%d", part2), func(t *testing.T) { + _, multiblockCtr := makeTestingCiphers(aesBlock, iv) + multiblockCiphertext := make([]byte, len(plaintext)) + multiblockCtr.XORKeyStream(multiblockCiphertext[:part1], plaintext[:part1]) + multiblockCtr.XORKeyStream(multiblockCiphertext[part1:part1+part2], plaintext[part1:part1+part2]) + multiblockCtr.XORKeyStream(multiblockCiphertext[part1+part2:], plaintext[part1+part2:]) + if !bytes.Equal(genericCiphertext, multiblockCiphertext) { + t.Fatal("multiblock CTR's output does not match generic CTR's output") + } + }) + } + }) + } + }) + } +} + +func parseHex(str string) []byte { + b, err := hex.DecodeString(strings.ReplaceAll(str, " ", "")) + if err != nil { + panic(err) + } + return b +} + +// Verify that multiblock AES CTR (src/crypto/aes/ctr_*.s) +// produces the same results as generic single-block implementation. +// This test runs checks on edge cases (IV overflows). +func TestCTR_AES_multiblock_overflow_IV(t *testing.T) { + r := rand.New(rand.NewSource(987654)) + + const Size = 4096 + plaintext := randBytes(t, r, Size) + + ivs := [][]byte{ + parseHex("00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF FF"), + parseHex("FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF"), + parseHex("FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00"), + parseHex("FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF fe"), + parseHex("00 00 00 00 00 00 00 00 FF FF FF FF FF FF FF fe"), + parseHex("FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 00"), + parseHex("00 00 00 00 00 00 00 01 FF FF FF FF FF FF FF 00"), + parseHex("00 00 00 00 00 00 00 01 FF FF FF FF FF FF FF FF"), + parseHex("00 00 00 00 00 00 00 01 FF FF FF FF FF FF FF fe"), + parseHex("00 00 00 00 00 00 00 01 FF FF FF FF FF FF FF 00"), + } + + for _, keySize := range []int{16, 24, 32} { + keySize := keySize + t.Run(fmt.Sprintf("keySize=%d", keySize), func(t *testing.T) { + for _, iv := range ivs { + key := randBytes(t, r, keySize) + aesBlock, err := aes.NewCipher(key) + if err != nil { + t.Fatal(err) + } + + t.Run(fmt.Sprintf("iv=%s", hex.EncodeToString(iv)), func(t *testing.T) { + for _, offset := range []int{0, 1, 16, 1024} { + offset := offset + t.Run(fmt.Sprintf("offset=%d", offset), func(t *testing.T) { + genericCtr, multiblockCtr := makeTestingCiphers(aesBlock, iv) + + // Generate reference ciphertext. + genericCiphertext := make([]byte, Size) + genericCtr.XORKeyStream(genericCiphertext, plaintext) + + multiblockCiphertext := make([]byte, Size) + multiblockCtr.XORKeyStream(multiblockCiphertext, plaintext[:offset]) + multiblockCtr.XORKeyStream(multiblockCiphertext[offset:], plaintext[offset:]) + if !bytes.Equal(genericCiphertext, multiblockCiphertext) { + t.Fatal("multiblock CTR's output does not match generic CTR's output") + } + }) + } + }) + } + }) + } +} + +// Check that method XORKeyStreamAt works correctly. +func TestCTR_AES_multiblock_XORKeyStreamAt(t *testing.T) { + if boring.Enabled { + t.Skip("XORKeyStreamAt is not available in boring mode") + } + + r := rand.New(rand.NewSource(12345)) + const Size = 32 * 1024 * 1024 + plaintext := randBytes(t, r, Size) + + for _, keySize := range []int{16, 24, 32} { + keySize := keySize + t.Run(fmt.Sprintf("keySize=%d", keySize), func(t *testing.T) { + key := randBytes(t, r, keySize) + iv := randBytes(t, r, aesBlockSize) + + aesBlock, err := aes.NewCipher(key) + if err != nil { + t.Fatal(err) + } + genericCtr, _ := makeTestingCiphers(aesBlock, iv) + ctrAt := fipsaes.NewCTR(aesBlock.(*fipsaes.Block), iv) + + // Generate reference ciphertext. + genericCiphertext := make([]byte, Size) + genericCtr.XORKeyStream(genericCiphertext, plaintext) + + multiblockCiphertext := make([]byte, Size) + // Split the range to random slices. + const N = 1000 + boundaries := make([]int, 0, N+2) + for i := 0; i < N; i++ { + boundaries = append(boundaries, r.Intn(Size)) + } + boundaries = append(boundaries, 0) + boundaries = append(boundaries, Size) + sort.Ints(boundaries) + + for _, i := range r.Perm(N + 1) { + begin := boundaries[i] + end := boundaries[i+1] + ctrAt.XORKeyStreamAt( + multiblockCiphertext[begin:end], + plaintext[begin:end], + uint64(begin), + ) + } + + if !bytes.Equal(genericCiphertext, multiblockCiphertext) { + t.Fatal("multiblock CTR's output does not match generic CTR's output") + } + }) + } +} diff --git a/crypto/cipher/ctr_test.go b/crypto/cipher/ctr_test.go index 839f16315ce..bba0fec52dd 100644 --- a/crypto/cipher/ctr_test.go +++ b/crypto/cipher/ctr_test.go @@ -18,7 +18,7 @@ type noopBlock int func (b noopBlock) BlockSize() int { return int(b) } func (noopBlock) Encrypt(dst, src []byte) { copy(dst, src) } -func (noopBlock) Decrypt(dst, src []byte) { copy(dst, src) } +func (noopBlock) Decrypt(dst, src []byte) { panic("unreachable") } func inc(b []byte) { for i := len(b) - 1; i >= 0; i++ { @@ -59,23 +59,23 @@ func TestCTR(t *testing.T) { } func TestCTRStream(t *testing.T) { + cryptotest.TestAllImplementations(t, "aes", func(t *testing.T) { + for _, keylen := range []int{128, 192, 256} { + t.Run(fmt.Sprintf("AES-%d", keylen), func(t *testing.T) { + rng := newRandReader(t) - for _, keylen := range []int{128, 192, 256} { + key := make([]byte, keylen/8) + rng.Read(key) - t.Run(fmt.Sprintf("AES-%d", keylen), func(t *testing.T) { - rng := newRandReader(t) + block, err := aes.NewCipher(key) + if err != nil { + panic(err) + } - key := make([]byte, keylen/8) - rng.Read(key) - - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - - cryptotest.TestStreamFromBlock(t, block, cipher.NewCTR) - }) - } + cryptotest.TestStreamFromBlock(t, block, cipher.NewCTR) + }) + } + }) t.Run("DES", func(t *testing.T) { rng := newRandReader(t) diff --git a/crypto/cipher/export_test.go b/crypto/cipher/export_test.go deleted file mode 100644 index 5ecd67b28ba..00000000000 --- a/crypto/cipher/export_test.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package cipher - -// Export internal functions for testing. -var NewCBCGenericEncrypter = newCBCGenericEncrypter -var NewCBCGenericDecrypter = newCBCGenericDecrypter diff --git a/crypto/cipher/fuzz_test.go b/crypto/cipher/fuzz_test.go index 66c85cbe0ae..fbafa13f0a6 100644 --- a/crypto/cipher/fuzz_test.go +++ b/crypto/cipher/fuzz_test.go @@ -45,7 +45,7 @@ func TestFuzz(t *testing.T) { c, _ := aes.NewCipher(ft.key) cbcAsm := cipher.NewCBCEncrypter(c, commonIV) - cbcGeneric := cipher.NewCBCGenericEncrypter(c, commonIV) + cbcGeneric := cipher.NewCBCEncrypter(wrap(c), commonIV) if testing.Short() { timeout = time.NewTimer(10 * time.Millisecond) @@ -76,7 +76,7 @@ func TestFuzz(t *testing.T) { } cbcAsm = cipher.NewCBCDecrypter(c, commonIV) - cbcGeneric = cipher.NewCBCGenericDecrypter(c, commonIV) + cbcGeneric = cipher.NewCBCDecrypter(wrap(c), commonIV) if testing.Short() { timeout = time.NewTimer(10 * time.Millisecond) diff --git a/crypto/cipher/gcm.go b/crypto/cipher/gcm.go index 76f164e5794..ab58631b220 100644 --- a/crypto/cipher/gcm.go +++ b/crypto/cipher/gcm.go @@ -1,4 +1,4 @@ -// Copyright 2013 The Go Authors. All rights reserved. +// Copyright 2024 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. @@ -6,76 +6,21 @@ package cipher import ( "errors" + "internal/byteorder" - "github.com/runZeroInc/excrypto/crypto/internal/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" - "github.com/runZeroInc/excrypto/internal/byteorder" ) -// AEAD is a cipher mode providing authenticated encryption with associated -// data. For a description of the methodology, see -// https://en.wikipedia.org/wiki/Authenticated_encryption. -type AEAD interface { - // NonceSize returns the size of the nonce that must be passed to Seal - // and Open. - NonceSize() int - - // Overhead returns the maximum difference between the lengths of a - // plaintext and its ciphertext. - Overhead() int - - // Seal encrypts and authenticates plaintext, authenticates the - // additional data and appends the result to dst, returning the updated - // slice. The nonce must be NonceSize() bytes long and unique for all - // time, for a given key. - // - // To reuse plaintext's storage for the encrypted output, use plaintext[:0] - // as dst. Otherwise, the remaining capacity of dst must not overlap plaintext. - Seal(dst, nonce, plaintext, additionalData []byte) []byte - - // Open decrypts and authenticates ciphertext, authenticates the - // additional data and, if successful, appends the resulting plaintext - // to dst, returning the updated slice. The nonce must be NonceSize() - // bytes long and both it and the additional data must match the - // value passed to Seal. - // - // To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] - // as dst. Otherwise, the remaining capacity of dst must not overlap ciphertext. - // - // Even if the function fails, the contents of dst, up to its capacity, - // may be overwritten. - Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) -} - -// gcmAble is an interface implemented by ciphers that have a specific optimized -// implementation of GCM, like crypto/aes. NewGCM will check for this interface -// and return the specific AEAD if found. -type gcmAble interface { - NewGCM(nonceSize, tagSize int) (AEAD, error) -} - -// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM -// standard and make binary.BigEndian suitable for marshaling these values, the -// bits are stored in big endian order. For example: -// -// the coefficient of x⁰ can be obtained by v.low >> 63. -// the coefficient of x⁶³ can be obtained by v.low & 1. -// the coefficient of x⁶⁴ can be obtained by v.high >> 63. -// the coefficient of x¹²⁷ can be obtained by v.high & 1. -type gcmFieldElement struct { - low, high uint64 -} - -// gcm represents a Galois Counter Mode with a specific key. See -// https://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-revised-spec.pdf -type gcm struct { - cipher Block - nonceSize int - tagSize int - // productTable contains the first sixteen powers of the key, H. - // However, they are in bit reversed order. See NewGCMWithNonceSize. - productTable [16]gcmFieldElement -} +const ( + gcmBlockSize = 16 + gcmStandardNonceSize = 12 + gcmTagSize = 16 + gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. +) // NewGCM returns the given 128-bit, block cipher wrapped in Galois Counter Mode // with the standard nonce length. @@ -84,7 +29,10 @@ type gcm struct { // An exception is when the underlying [Block] was created by aes.NewCipher // on systems with hardware support for AES. See the [crypto/aes] package documentation for details. func NewGCM(cipher Block) (AEAD, error) { - return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, gcmTagSize) + if fips140only.Enabled { + return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") + } + return newGCM(cipher, gcmStandardNonceSize, gcmTagSize) } // NewGCMWithNonceSize returns the given 128-bit, block cipher wrapped in Galois @@ -95,7 +43,10 @@ func NewGCM(cipher Block) (AEAD, error) { // cryptosystem that uses non-standard nonce lengths. All other users should use // [NewGCM], which is faster and more resistant to misuse. func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { - return newGCMWithNonceAndTagSize(cipher, size, gcmTagSize) + if fips140only.Enabled { + return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") + } + return newGCM(cipher, size, gcmTagSize) } // NewGCMWithTagSize returns the given 128-bit, block cipher wrapped in Galois @@ -107,88 +58,214 @@ func NewGCMWithNonceSize(cipher Block, size int) (AEAD, error) { // cryptosystem that uses non-standard tag lengths. All other users should use // [NewGCM], which is more resistant to misuse. func NewGCMWithTagSize(cipher Block, tagSize int) (AEAD, error) { - return newGCMWithNonceAndTagSize(cipher, gcmStandardNonceSize, tagSize) + if fips140only.Enabled { + return nil, errors.New("crypto/cipher: use of GCM with arbitrary IVs is not allowed in FIPS 140-only mode, use NewGCMWithRandomNonce") + } + return newGCM(cipher, gcmStandardNonceSize, tagSize) } -func newGCMWithNonceAndTagSize(cipher Block, nonceSize, tagSize int) (AEAD, error) { - if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize { - return nil, errors.New("cipher: incorrect tag size given to GCM") +func newGCM(cipher Block, nonceSize, tagSize int) (AEAD, error) { + c, ok := cipher.(*aes.Block) + if !ok { + if fips140only.Enabled { + return nil, errors.New("crypto/cipher: use of GCM with non-AES ciphers is not allowed in FIPS 140-only mode") + } + return newGCMFallback(cipher, nonceSize, tagSize) } + // We don't return gcm.New directly, because it would always return a non-nil + // AEAD interface value with type *gcm.GCM even if the *gcm.GCM is nil. + g, err := gcm.New(c, nonceSize, tagSize) + if err != nil { + return nil, err + } + return g, nil +} - if nonceSize <= 0 { - return nil, errors.New("cipher: the nonce can't have zero length, or the security of the key will be immediately compromised") +// NewGCMWithRandomNonce returns the given cipher wrapped in Galois Counter +// Mode, with randomly-generated nonces. The cipher must have been created by +// [aes.NewCipher]. +// +// It generates a random 96-bit nonce, which is prepended to the ciphertext by Seal, +// and is extracted from the ciphertext by Open. The NonceSize of the AEAD is zero, +// while the Overhead is 28 bytes (the combination of nonce size and tag size). +// +// A given key MUST NOT be used to encrypt more than 2^32 messages, to limit the +// risk of a random nonce collision to negligible levels. +func NewGCMWithRandomNonce(cipher Block) (AEAD, error) { + c, ok := cipher.(*aes.Block) + if !ok { + return nil, errors.New("cipher: NewGCMWithRandomNonce requires aes.Block") + } + g, err := gcm.New(c, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err } + return gcmWithRandomNonce{g}, nil +} - if cipher, ok := cipher.(gcmAble); ok { - return cipher.NewGCM(nonceSize, tagSize) +type gcmWithRandomNonce struct { + *gcm.GCM +} + +func (g gcmWithRandomNonce) NonceSize() int { + return 0 +} + +func (g gcmWithRandomNonce) Overhead() int { + return gcmStandardNonceSize + gcmTagSize +} + +func (g gcmWithRandomNonce) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if len(nonce) != 0 { + panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce") } - if cipher.BlockSize() != gcmBlockSize { - return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") + ret, out := sliceForAppend(dst, gcmStandardNonceSize+len(plaintext)+gcmTagSize) + if alias.InexactOverlap(out, plaintext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") } + nonce = out[:gcmStandardNonceSize] + ciphertext := out[gcmStandardNonceSize:] - var key [gcmBlockSize]byte - cipher.Encrypt(key[:], key[:]) + // The AEAD interface allows using plaintext[:0] or ciphertext[:0] as dst. + // + // This is kind of a problem when trying to prepend or trim a nonce, because the + // actual AES-GCTR blocks end up overlapping but not exactly. + // + // In Open, we write the output *before* the input, so unless we do something + // weird like working through a chunk of block backwards, it works out. + // + // In Seal, we could work through the input backwards or intentionally load + // ahead before writing. + // + // However, the crypto/internal/fips140/aes/gcm APIs also check for exact overlap, + // so for now we just do a memmove if we detect overlap. + // + // ┌───────────────────────────┬ ─ ─ + // │PPPPPPPPPPPPPPPPPPPPPPPPPPP│ │ + // └▽─────────────────────────▲┴ ─ ─ + // ╲ Seal ╲ + // ╲ Open ╲ + // ┌───▼─────────────────────────△──┐ + // │NN|CCCCCCCCCCCCCCCCCCCCCCCCCCC|T│ + // └────────────────────────────────┘ + // + if alias.AnyOverlap(out, plaintext) { + copy(ciphertext, plaintext) + plaintext = ciphertext[:len(plaintext)] + } - g := &gcm{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize} + gcm.SealWithRandomNonce(g.GCM, nonce, ciphertext, plaintext, additionalData) + return ret +} - // We precompute 16 multiples of |key|. However, when we do lookups - // into this table we'll be using bits from a field element and - // therefore the bits will be in the reverse order. So normally one - // would expect, say, 4*key to be in index 4 of the table but due to - // this bit ordering it will actually be in index 0010 (base 2) = 2. - x := gcmFieldElement{ - byteorder.BeUint64(key[:8]), - byteorder.BeUint64(key[8:]), +func (g gcmWithRandomNonce) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if len(nonce) != 0 { + panic("crypto/cipher: non-empty nonce passed to GCMWithRandomNonce") + } + if len(ciphertext) < gcmStandardNonceSize+gcmTagSize { + return nil, errOpen } - g.productTable[reverseBits(1)] = x - for i := 2; i < 16; i += 2 { - g.productTable[reverseBits(i)] = gcmDouble(&g.productTable[reverseBits(i/2)]) - g.productTable[reverseBits(i+1)] = gcmAdd(&g.productTable[reverseBits(i)], &x) + ret, out := sliceForAppend(dst, len(ciphertext)-gcmStandardNonceSize-gcmTagSize) + if alias.InexactOverlap(out, ciphertext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } + // See the discussion in Seal. Note that if there is any overlap at this + // point, it's because out = ciphertext, so out must have enough capacity + // even if we sliced the tag off. Also note how [AEAD] specifies that "the + // contents of dst, up to its capacity, may be overwritten". + if alias.AnyOverlap(out, ciphertext) { + nonce = make([]byte, gcmStandardNonceSize) + copy(nonce, ciphertext) + copy(out[:len(ciphertext)], ciphertext[gcmStandardNonceSize:]) + ciphertext = out[:len(ciphertext)-gcmStandardNonceSize] + } else { + nonce = ciphertext[:gcmStandardNonceSize] + ciphertext = ciphertext[gcmStandardNonceSize:] } - return g, nil + _, err := g.GCM.Open(out[:0], nonce, ciphertext, additionalData) + if err != nil { + return nil, err + } + return ret, nil } -const ( - gcmBlockSize = 16 - gcmTagSize = 16 - gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. - gcmStandardNonceSize = 12 -) +// gcmAble is an interface implemented by ciphers that have a specific optimized +// implementation of GCM. crypto/aes doesn't use this anymore, and we'd like to +// eventually remove it. +type gcmAble interface { + NewGCM(nonceSize, tagSize int) (AEAD, error) +} -func (g *gcm) NonceSize() int { +func newGCMFallback(cipher Block, nonceSize, tagSize int) (AEAD, error) { + if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize { + return nil, errors.New("cipher: incorrect tag size given to GCM") + } + if nonceSize <= 0 { + return nil, errors.New("cipher: the nonce can't have zero length") + } + if cipher, ok := cipher.(gcmAble); ok { + return cipher.NewGCM(nonceSize, tagSize) + } + if cipher.BlockSize() != gcmBlockSize { + return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") + } + return &gcmFallback{cipher: cipher, nonceSize: nonceSize, tagSize: tagSize}, nil +} + +// gcmFallback is only used for non-AES ciphers, which regrettably we +// theoretically support. It's a copy of the generic implementation from +// crypto/internal/fips140/aes/gcm/gcm_generic.go, refer to that file for more details. +type gcmFallback struct { + cipher Block + nonceSize int + tagSize int +} + +func (g *gcmFallback) NonceSize() int { return g.nonceSize } -func (g *gcm) Overhead() int { +func (g *gcmFallback) Overhead() int { return g.tagSize } -func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte { +func (g *gcmFallback) Seal(dst, nonce, plaintext, additionalData []byte) []byte { if len(nonce) != g.nonceSize { panic("crypto/cipher: incorrect nonce length given to GCM") } - if uint64(len(plaintext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize()) { + if g.nonceSize == 0 { + panic("crypto/cipher: incorrect GCM nonce size") + } + if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize { panic("crypto/cipher: message too large for GCM") } ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) if alias.InexactOverlap(out, plaintext) { - panic("crypto/cipher: invalid buffer overlap") + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") } - var counter, tagMask [gcmBlockSize]byte - g.deriveCounter(&counter, nonce) - - g.cipher.Encrypt(tagMask[:], counter[:]) - gcmInc32(&counter) + var H, counter, tagMask [gcmBlockSize]byte + g.cipher.Encrypt(H[:], H[:]) + deriveCounter(&H, &counter, nonce) + gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter) - g.counterCrypt(out, plaintext, &counter) + gcmCounterCryptGeneric(g.cipher, out, plaintext, &counter) var tag [gcmTagSize]byte - g.auth(tag[:], out[:len(plaintext)], data, &tagMask) + gcmAuth(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData) copy(out[len(plaintext):], tag[:]) return ret @@ -196,12 +273,10 @@ func (g *gcm) Seal(dst, nonce, plaintext, data []byte) []byte { var errOpen = errors.New("cipher: message authentication failed") -func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { +func (g *gcmFallback) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { if len(nonce) != g.nonceSize { panic("crypto/cipher: incorrect nonce length given to GCM") } - // Sanity check to prevent the authentication from always succeeding if an implementation - // leaves tagSize uninitialized, for example. if g.tagSize < gcmMinimumTagSize { panic("crypto/cipher: incorrect GCM tag size") } @@ -209,144 +284,82 @@ func (g *gcm) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { if len(ciphertext) < g.tagSize { return nil, errOpen } - if uint64(len(ciphertext)) > ((1<<32)-2)*uint64(g.cipher.BlockSize())+uint64(g.tagSize) { + if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) { return nil, errOpen } - tag := ciphertext[len(ciphertext)-g.tagSize:] - ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize) + if alias.InexactOverlap(out, ciphertext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } - var counter, tagMask [gcmBlockSize]byte - g.deriveCounter(&counter, nonce) + var H, counter, tagMask [gcmBlockSize]byte + g.cipher.Encrypt(H[:], H[:]) + deriveCounter(&H, &counter, nonce) + gcmCounterCryptGeneric(g.cipher, tagMask[:], tagMask[:], &counter) - g.cipher.Encrypt(tagMask[:], counter[:]) - gcmInc32(&counter) + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] var expectedTag [gcmTagSize]byte - g.auth(expectedTag[:], ciphertext, data, &tagMask) - - ret, out := sliceForAppend(dst, len(ciphertext)) - if alias.InexactOverlap(out, ciphertext) { - panic("crypto/cipher: invalid buffer overlap") - } - + gcmAuth(expectedTag[:], &H, &tagMask, ciphertext, additionalData) if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { - // The AESNI code decrypts and authenticates concurrently, and - // so overwrites dst in the event of a tag mismatch. That - // behavior is mimicked here in order to be consistent across - // platforms. + // We sometimes decrypt and authenticate concurrently, so we overwrite + // dst in the event of a tag mismatch. To be consistent across platforms + // and to avoid releasing unauthenticated plaintext, we clear the buffer + // in the event of an error. clear(out) return nil, errOpen } - g.counterCrypt(out, ciphertext, &counter) + gcmCounterCryptGeneric(g.cipher, out, ciphertext, &counter) return ret, nil } -// reverseBits reverses the order of the bits of 4-bit number in i. -func reverseBits(i int) int { - i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) - i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) - return i -} - -// gcmAdd adds two elements of GF(2¹²⁸) and returns the sum. -func gcmAdd(x, y *gcmFieldElement) gcmFieldElement { - // Addition in a characteristic 2 field is just XOR. - return gcmFieldElement{x.low ^ y.low, x.high ^ y.high} -} - -// gcmDouble returns the result of doubling an element of GF(2¹²⁸). -func gcmDouble(x *gcmFieldElement) (double gcmFieldElement) { - msbSet := x.high&1 == 1 - - // Because of the bit-ordering, doubling is actually a right shift. - double.high = x.high >> 1 - double.high |= x.low << 63 - double.low = x.low >> 1 - - // If the most-significant bit was set before shifting then it, - // conceptually, becomes a term of x^128. This is greater than the - // irreducible polynomial so the result has to be reduced. The - // irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to - // eliminate the term at x^128 which also means subtracting the other - // four terms. In characteristic 2 fields, subtraction == addition == - // XOR. - if msbSet { - double.low ^= 0xe100000000000000 +func deriveCounter(H, counter *[gcmBlockSize]byte, nonce []byte) { + if len(nonce) == gcmStandardNonceSize { + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + lenBlock := make([]byte, 16) + byteorder.BEPutUint64(lenBlock[8:], uint64(len(nonce))*8) + J := gcm.GHASH(H, nonce, lenBlock) + copy(counter[:], J) } - - return } -var gcmReductionTable = []uint16{ - 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, - 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, -} - -// mul sets y to y*H, where H is the GCM key, fixed during NewGCMWithNonceSize. -func (g *gcm) mul(y *gcmFieldElement) { - var z gcmFieldElement - - for i := 0; i < 2; i++ { - word := y.high - if i == 1 { - word = y.low - } +func gcmCounterCryptGeneric(b Block, out, src []byte, counter *[gcmBlockSize]byte) { + var mask [gcmBlockSize]byte + for len(src) >= gcmBlockSize { + b.Encrypt(mask[:], counter[:]) + gcmInc32(counter) - // Multiplication works by multiplying z by 16 and adding in - // one of the precomputed multiples of H. - for j := 0; j < 64; j += 4 { - msw := z.high & 0xf - z.high >>= 4 - z.high |= z.low << 60 - z.low >>= 4 - z.low ^= uint64(gcmReductionTable[msw]) << 48 - - // the values in |table| are ordered for - // little-endian bit positions. See the comment - // in NewGCMWithNonceSize. - t := &g.productTable[word&0xf] - - z.low ^= t.low - z.high ^= t.high - word >>= 4 - } + subtle.XORBytes(out, src, mask[:]) + out = out[gcmBlockSize:] + src = src[gcmBlockSize:] } - - *y = z -} - -// updateBlocks extends y with more polynomial terms from blocks, based on -// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks. -func (g *gcm) updateBlocks(y *gcmFieldElement, blocks []byte) { - for len(blocks) > 0 { - y.low ^= byteorder.BeUint64(blocks) - y.high ^= byteorder.BeUint64(blocks[8:]) - g.mul(y) - blocks = blocks[gcmBlockSize:] + if len(src) > 0 { + b.Encrypt(mask[:], counter[:]) + gcmInc32(counter) + subtle.XORBytes(out, src, mask[:]) } } -// update extends y with more polynomial terms from data. If data is not a -// multiple of gcmBlockSize bytes long then the remainder is zero padded. -func (g *gcm) update(y *gcmFieldElement, data []byte) { - fullBlocks := (len(data) >> 4) << 4 - g.updateBlocks(y, data[:fullBlocks]) - - if len(data) != fullBlocks { - var partialBlock [gcmBlockSize]byte - copy(partialBlock[:], data[fullBlocks:]) - g.updateBlocks(y, partialBlock[:]) - } +func gcmInc32(counterBlock *[gcmBlockSize]byte) { + ctr := counterBlock[len(counterBlock)-4:] + byteorder.BEPutUint32(ctr, byteorder.BEUint32(ctr)+1) } -// gcmInc32 treats the final four bytes of counterBlock as a big-endian value -// and increments it. -func gcmInc32(counterBlock *[16]byte) { - ctr := counterBlock[len(counterBlock)-4:] - byteorder.BePutUint32(ctr, byteorder.BeUint32(ctr)+1) +func gcmAuth(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) { + lenBlock := make([]byte, 16) + byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8) + byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8) + S := gcm.GHASH(H, additionalData, ciphertext, lenBlock) + subtle.XORBytes(out, S, tagMask[:]) } // sliceForAppend takes a slice and a requested number of bytes. It returns a @@ -363,64 +376,3 @@ func sliceForAppend(in []byte, n int) (head, tail []byte) { tail = head[len(in):] return } - -// counterCrypt crypts in to out using g.cipher in counter mode. -func (g *gcm) counterCrypt(out, in []byte, counter *[gcmBlockSize]byte) { - var mask [gcmBlockSize]byte - - for len(in) >= gcmBlockSize { - g.cipher.Encrypt(mask[:], counter[:]) - gcmInc32(counter) - - subtle.XORBytes(out, in, mask[:]) - out = out[gcmBlockSize:] - in = in[gcmBlockSize:] - } - - if len(in) > 0 { - g.cipher.Encrypt(mask[:], counter[:]) - gcmInc32(counter) - subtle.XORBytes(out, in, mask[:]) - } -} - -// deriveCounter computes the initial GCM counter state from the given nonce. -// See NIST SP 800-38D, section 7.1. This assumes that counter is filled with -// zeros on entry. -func (g *gcm) deriveCounter(counter *[gcmBlockSize]byte, nonce []byte) { - // GCM has two modes of operation with respect to the initial counter - // state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path" - // for nonces of other lengths. For a 96-bit nonce, the nonce, along - // with a four-byte big-endian counter starting at one, is used - // directly as the starting counter. For other nonce sizes, the counter - // is computed by passing it through the GHASH function. - if len(nonce) == gcmStandardNonceSize { - copy(counter[:], nonce) - counter[gcmBlockSize-1] = 1 - } else { - var y gcmFieldElement - g.update(&y, nonce) - y.high ^= uint64(len(nonce)) * 8 - g.mul(&y) - byteorder.BePutUint64(counter[:8], y.low) - byteorder.BePutUint64(counter[8:], y.high) - } -} - -// auth calculates GHASH(ciphertext, additionalData), masks the result with -// tagMask and writes the result to out. -func (g *gcm) auth(out, ciphertext, additionalData []byte, tagMask *[gcmTagSize]byte) { - var y gcmFieldElement - g.update(&y, additionalData) - g.update(&y, ciphertext) - - y.low ^= uint64(len(additionalData)) * 8 - y.high ^= uint64(len(ciphertext)) * 8 - - g.mul(&y) - - byteorder.BePutUint64(out, y.low) - byteorder.BePutUint64(out[8:], y.high) - - subtle.XORBytes(out, out, tagMask[:]) -} diff --git a/crypto/cipher/gcm_test.go b/crypto/cipher/gcm_test.go index acde1a81ca9..b862dc91590 100644 --- a/crypto/cipher/gcm_test.go +++ b/crypto/cipher/gcm_test.go @@ -6,6 +6,7 @@ package cipher_test import ( "bytes" + fipsaes "crypto/internal/fips140/aes" "encoding/hex" "errors" "fmt" @@ -17,9 +18,48 @@ import ( "github.com/runZeroInc/excrypto/crypto/aes" "github.com/runZeroInc/excrypto/crypto/cipher" + "github.com/runZeroInc/excrypto/crypto/internal/boring" "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" ) +var _ cipher.Block = (*wrapper)(nil) + +type wrapper struct { + block cipher.Block +} + +func (w *wrapper) BlockSize() int { return w.block.BlockSize() } +func (w *wrapper) Encrypt(dst, src []byte) { w.block.Encrypt(dst, src) } +func (w *wrapper) Decrypt(dst, src []byte) { w.block.Decrypt(dst, src) } + +// wrap wraps the Block so that it does not type-asserts to *aes.Block. +func wrap(b cipher.Block) cipher.Block { + return &wrapper{b} +} + +func testAllImplementations(t *testing.T, f func(*testing.T, func([]byte) cipher.Block)) { + cryptotest.TestAllImplementations(t, "gcm", func(t *testing.T) { + f(t, func(b []byte) cipher.Block { + c, err := aes.NewCipher(b) + if err != nil { + t.Fatal(err) + } + return c + }) + }) + t.Run("Fallback", func(t *testing.T) { + f(t, func(b []byte) cipher.Block { + c, err := aes.NewCipher(b) + if err != nil { + t.Fatal(err) + } + return wrap(c) + }) + }) +} + var aesGCMTests = []struct { key, nonce, plaintext, ad, result string }{ @@ -376,18 +416,20 @@ var aesGCMTests = []struct { } func TestAESGCM(t *testing.T) { + testAllImplementations(t, testAESGCM) +} + +func testAESGCM(t *testing.T, newCipher func(key []byte) cipher.Block) { for i, test := range aesGCMTests { key, _ := hex.DecodeString(test.key) - aes, err := aes.NewCipher(key) - if err != nil { - t.Fatal(err) - } + aes := newCipher(key) nonce, _ := hex.DecodeString(test.nonce) plaintext, _ := hex.DecodeString(test.plaintext) ad, _ := hex.DecodeString(test.ad) tagSize := (len(test.result) - len(test.plaintext)) / 2 + var err error var aesgcm cipher.AEAD switch { // Handle non-standard tag sizes @@ -459,19 +501,26 @@ func TestAESGCM(t *testing.T) { } func TestGCMInvalidTagSize(t *testing.T) { - key, _ := hex.DecodeString("ab72c77b97cb5fe9a382d9fe81ffdbed") + testAllImplementations(t, testGCMInvalidTagSize) +} - aes, _ := aes.NewCipher(key) +func testGCMInvalidTagSize(t *testing.T, newCipher func(key []byte) cipher.Block) { + key, _ := hex.DecodeString("ab72c77b97cb5fe9a382d9fe81ffdbed") + aes := newCipher(key) for _, tagSize := range []int{0, 1, aes.BlockSize() + 1} { aesgcm, err := cipher.NewGCMWithTagSize(aes, tagSize) if aesgcm != nil || err == nil { - t.Fatalf("NewGCMWithNonceAndTagSize was successful with an invalid %d-byte tag size", tagSize) + t.Fatalf("NewGCMWithTagSize was successful with an invalid %d-byte tag size", tagSize) } } } func TestTagFailureOverwrite(t *testing.T) { + testAllImplementations(t, testTagFailureOverwrite) +} + +func testTagFailureOverwrite(t *testing.T, newCipher func(key []byte) cipher.Block) { // The AESNI GCM code decrypts and authenticates concurrently and so // overwrites the output buffer before checking the authentication tag. // In order to be consistent across platforms, all implementations @@ -481,7 +530,7 @@ func TestTagFailureOverwrite(t *testing.T) { nonce, _ := hex.DecodeString("54cc7dc2c37ec006bcc6d1db") ciphertext, _ := hex.DecodeString("0e1bde206a07a9c2c1b65300f8c649972b4401346697138c7a4891ee59867d0c") - aes, _ := aes.NewCipher(key) + aes := newCipher(key) aesgcm, _ := cipher.NewGCM(aes) dst := make([]byte, len(ciphertext)-16) @@ -506,6 +555,10 @@ func TestTagFailureOverwrite(t *testing.T) { } func TestGCMCounterWrap(t *testing.T) { + testAllImplementations(t, testGCMCounterWrap) +} + +func testGCMCounterWrap(t *testing.T, newCipher func(key []byte) cipher.Block) { // Test that the last 32-bits of the counter wrap correctly. tests := []struct { nonce, tag string @@ -518,10 +571,7 @@ func TestGCMCounterWrap(t *testing.T) { {"010ae3d486", "5405bb490b1f95d01e2ba735687154bc"}, // counter: e36c18e69406c49722808104fffffff8 {"01b1107a9d", "939a585f342e01e17844627492d44dbf"}, // counter: e6d56eaf9127912b6d62c6dcffffffff } - key, err := aes.NewCipher(make([]byte, 16)) - if err != nil { - t.Fatal(err) - } + key := newCipher(make([]byte, 16)) plaintext := make([]byte, 16*17+1) for i, test := range tests { nonce, _ := hex.DecodeString(test.nonce) @@ -541,22 +591,6 @@ func TestGCMCounterWrap(t *testing.T) { } } -var _ cipher.Block = (*wrapper)(nil) - -type wrapper struct { - block cipher.Block -} - -func (w *wrapper) BlockSize() int { return w.block.BlockSize() } -func (w *wrapper) Encrypt(dst, src []byte) { w.block.Encrypt(dst, src) } -func (w *wrapper) Decrypt(dst, src []byte) { w.block.Decrypt(dst, src) } - -// wrap wraps the Block interface so that it does not fulfill -// any optimizing interfaces such as gcmAble. -func wrap(b cipher.Block) cipher.Block { - return &wrapper{b} -} - func TestGCMAsm(t *testing.T) { // Create a new pair of AEADs, one using the assembly implementation // and one using the generic Go implementation. @@ -661,6 +695,10 @@ func TestGCMAsm(t *testing.T) { // Test GCM against the general cipher.AEAD interface tester. func TestGCMAEAD(t *testing.T) { + testAllImplementations(t, testGCMAEAD) +} + +func testGCMAEAD(t *testing.T, newCipher func(key []byte) cipher.Block) { minTagSize := 12 for _, keySize := range []int{128, 192, 256} { @@ -671,10 +709,7 @@ func TestGCMAEAD(t *testing.T) { key := make([]byte, keySize/8) rng.Read(key) - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } + block := newCipher(key) // Test GCM with the current AES block with the standard nonce and tag // sizes. @@ -688,10 +723,159 @@ func TestGCMAEAD(t *testing.T) { // Test non-standard nonce sizes. for _, nonceSize := range []int{1, 16, 100} { t.Run(fmt.Sprintf("NonceSize-%d", nonceSize), func(t *testing.T) { - cryptotest.TestAEAD(t, func() (cipher.AEAD, error) { return cipher.NewGCMWithNonceSize(block, nonceSize) }) }) } + + // Test NewGCMWithRandomNonce. + t.Run("GCMWithRandomNonce", func(t *testing.T) { + if _, ok := block.(*wrapper); ok || boring.Enabled { + t.Skip("NewGCMWithRandomNonce requires an AES block cipher") + } + cryptotest.TestAEAD(t, func() (cipher.AEAD, error) { return cipher.NewGCMWithRandomNonce(block) }) + }) }) } } + +func TestFIPSServiceIndicator(t *testing.T) { + newGCM := func() cipher.AEAD { + key := make([]byte, 16) + block, _ := fipsaes.New(key) + aead, _ := gcm.NewGCMWithCounterNonce(block) + return aead + } + tryNonce := func(aead cipher.AEAD, nonce []byte) bool { + fips140.ResetServiceIndicator() + aead.Seal(nil, nonce, []byte("x"), nil) + return fips140.ServiceIndicator() + } + expectTrue := func(t *testing.T, aead cipher.AEAD, nonce []byte) { + t.Helper() + if !tryNonce(aead, nonce) { + t.Errorf("expected service indicator true for %x", nonce) + } + } + expectPanic := func(t *testing.T, aead cipher.AEAD, nonce []byte) { + t.Helper() + defer func() { + t.Helper() + if recover() == nil { + t.Errorf("expected panic for %x", nonce) + } + }() + tryNonce(aead, nonce) + } + + g := newGCM() + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}) + expectTrue(t, g, []byte{0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0}) + // Changed name. + expectPanic(t, g, []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}) + + g = newGCM() + expectTrue(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + // Went down. + expectPanic(t, g, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) + + g = newGCM() + expectTrue(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}) + expectTrue(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13}) + // Did not increment. + expectPanic(t, g, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13}) + + g = newGCM() + expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}) + expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + // Wrap is ok as long as we don't run out of values. + expectTrue(t, g, []byte{1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0}) + expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe}) + // Run out of counters. + expectPanic(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff}) + + g = newGCM() + expectTrue(t, g, []byte{1, 2, 3, 4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}) + // Wrap with overflow. + expectPanic(t, g, []byte{1, 2, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0}) +} + +func TestGCMForSSH(t *testing.T) { + // incIV from x/crypto/ssh/cipher.go. + incIV := func(iv []byte) { + for i := 4 + 7; i >= 4; i-- { + iv[i]++ + if iv[i] != 0 { + break + } + } + } + + expectOK := func(aead cipher.AEAD, iv []byte) { + aead.Seal(nil, iv, []byte("hello, world"), nil) + } + + expectPanic := func(aead cipher.AEAD, iv []byte) { + defer func() { + if recover() == nil { + t.Errorf("expected panic") + } + }() + aead.Seal(nil, iv, []byte("hello, world"), nil) + } + + key := make([]byte, 16) + block, _ := fipsaes.New(key) + aead, err := gcm.NewGCMForSSH(block) + if err != nil { + t.Fatal(err) + } + iv := decodeHex(t, "11223344"+"0000000000000000") + expectOK(aead, iv) + incIV(iv) + expectOK(aead, iv) + iv = decodeHex(t, "11223344"+"fffffffffffffffe") + expectOK(aead, iv) + incIV(iv) + expectPanic(aead, iv) + + aead, _ = gcm.NewGCMForSSH(block) + iv = decodeHex(t, "11223344"+"fffffffffffffffe") + expectOK(aead, iv) + incIV(iv) + expectOK(aead, iv) + incIV(iv) + expectOK(aead, iv) + incIV(iv) + expectOK(aead, iv) + + aead, _ = gcm.NewGCMForSSH(block) + iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaaa") + expectOK(aead, iv) + iv = decodeHex(t, "11223344"+"ffffffffffffffff") + expectOK(aead, iv) + incIV(iv) + expectOK(aead, iv) + iv = decodeHex(t, "11223344"+"aaaaaaaaaaaaaaa8") + expectOK(aead, iv) + incIV(iv) + expectPanic(aead, iv) + iv = decodeHex(t, "11223344"+"bbbbbbbbbbbbbbbb") + expectPanic(aead, iv) +} + +func decodeHex(t *testing.T, s string) []byte { + t.Helper() + b, err := hex.DecodeString(s) + if err != nil { + t.Fatal(err) + } + return b +} diff --git a/crypto/cipher/modes_test.go b/crypto/cipher/modes_test.go new file mode 100644 index 00000000000..3f431b9b134 --- /dev/null +++ b/crypto/cipher/modes_test.go @@ -0,0 +1,126 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cipher_test + +import ( + . "crypto/cipher" + "reflect" + "testing" +) + +// Historically, crypto/aes's Block would implement some undocumented +// methods for crypto/cipher to use from NewCTR, NewCBCEncrypter, etc. +// This is no longer the case, but for now test that the mechanism is +// still working until we explicitly decide to remove it. + +type block struct { + Block +} + +func (block) BlockSize() int { + return 16 +} + +type specialCTR struct { + Stream +} + +func (block) NewCTR(iv []byte) Stream { + return specialCTR{} +} + +func TestCTRAble(t *testing.T) { + b := block{} + s := NewCTR(b, make([]byte, 16)) + if _, ok := s.(specialCTR); !ok { + t.Errorf("NewCTR did not return specialCTR") + } +} + +type specialCBC struct { + BlockMode +} + +func (block) NewCBCEncrypter(iv []byte) BlockMode { + return specialCBC{} +} + +func (block) NewCBCDecrypter(iv []byte) BlockMode { + return specialCBC{} +} + +func TestCBCAble(t *testing.T) { + b := block{} + s := NewCBCEncrypter(b, make([]byte, 16)) + if _, ok := s.(specialCBC); !ok { + t.Errorf("NewCBCEncrypter did not return specialCBC") + } + s = NewCBCDecrypter(b, make([]byte, 16)) + if _, ok := s.(specialCBC); !ok { + t.Errorf("NewCBCDecrypter did not return specialCBC") + } +} + +type specialGCM struct { + AEAD +} + +func (block) NewGCM(nonceSize, tagSize int) (AEAD, error) { + return specialGCM{}, nil +} + +func TestGCM(t *testing.T) { + b := block{} + s, err := NewGCM(b) + if err != nil { + t.Errorf("NewGCM failed: %v", err) + } + if _, ok := s.(specialGCM); !ok { + t.Errorf("NewGCM did not return specialGCM") + } +} + +// TestNoExtraMethods makes sure we don't accidentally expose methods on the +// underlying implementations of modes. +func TestNoExtraMethods(t *testing.T) { + testAllImplementations(t, testNoExtraMethods) +} + +func testNoExtraMethods(t *testing.T, newBlock func([]byte) Block) { + b := newBlock(make([]byte, 16)) + + ctr := NewCTR(b, make([]byte, 16)) + ctrExpected := []string{"XORKeyStream"} + if got := exportedMethods(ctr); !reflect.DeepEqual(got, ctrExpected) { + t.Errorf("CTR: got %v, want %v", got, ctrExpected) + } + + cbc := NewCBCEncrypter(b, make([]byte, 16)) + cbcExpected := []string{"BlockSize", "CryptBlocks", "SetIV"} + if got := exportedMethods(cbc); !reflect.DeepEqual(got, cbcExpected) { + t.Errorf("CBC: got %v, want %v", got, cbcExpected) + } + cbc = NewCBCDecrypter(b, make([]byte, 16)) + if got := exportedMethods(cbc); !reflect.DeepEqual(got, cbcExpected) { + t.Errorf("CBC: got %v, want %v", got, cbcExpected) + } + + gcm, _ := NewGCM(b) + gcmExpected := []string{"NonceSize", "Open", "Overhead", "Seal"} + if got := exportedMethods(gcm); !reflect.DeepEqual(got, gcmExpected) { + t.Errorf("GCM: got %v, want %v", got, gcmExpected) + } +} + +func exportedMethods(x any) []string { + var methods []string + v := reflect.ValueOf(x) + for i := 0; i < v.NumMethod(); i++ { + if v.Type().Method(i).IsExported() { + methods = append(methods, v.Type().Method(i).Name) + } + } + return methods +} diff --git a/crypto/cipher/ofb.go b/crypto/cipher/ofb.go index 962e38f57ea..b0db48f28d6 100644 --- a/crypto/cipher/ofb.go +++ b/crypto/cipher/ofb.go @@ -7,7 +7,8 @@ package cipher import ( - "github.com/runZeroInc/excrypto/crypto/internal/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -21,7 +22,17 @@ type ofb struct { // NewOFB returns a [Stream] that encrypts or decrypts using the block cipher b // in output feedback mode. The initialization vector iv's length must be equal // to b's block size. +// +// Deprecated: OFB mode is not authenticated, which generally enables active +// attacks to manipulate and recover the plaintext. It is recommended that +// applications use [AEAD] modes instead. The standard library implementation of +// OFB is also unoptimized and not validated as part of the FIPS 140-3 module. +// If an unauthenticated [Stream] mode is required, use [NewCTR] instead. func NewOFB(b Block, iv []byte) Stream { + if fips140only.Enabled { + panic("crypto/cipher: use of OFB is not allowed in FIPS 140-only mode") + } + blockSize := b.BlockSize() if len(iv) != blockSize { panic("cipher.NewOFB: IV length must equal block size") diff --git a/crypto/des/block.go b/crypto/des/block.go index 7267a04294d..4715b52d767 100644 --- a/crypto/des/block.go +++ b/crypto/des/block.go @@ -10,7 +10,7 @@ import ( ) func cryptBlock(subkeys []uint64, dst, src []byte, decrypt bool) { - b := byteorder.BeUint64(src) + b := byteorder.BEUint64(src) b = permuteInitialBlock(b) left, right := uint32(b>>32), uint32(b) @@ -32,7 +32,7 @@ func cryptBlock(subkeys []uint64, dst, src []byte, decrypt bool) { // switch left & right and perform final permutation preOutput := (uint64(right) << 32) | uint64(left) - byteorder.BePutUint64(dst, permuteFinalBlock(preOutput)) + byteorder.BEPutUint64(dst, permuteFinalBlock(preOutput)) } // DES Feistel function. feistelBox must be initialized via @@ -218,7 +218,7 @@ func (c *desCipher) generateSubkeys(keyBytes []byte) { feistelBoxOnce.Do(initFeistelBox) // apply PC1 permutation to key - key := byteorder.BeUint64(keyBytes) + key := byteorder.BEUint64(keyBytes) permutedKey := permuteBlock(key, permutedChoice1[:]) // rotate halves of permuted key according to the rotation schedule diff --git a/crypto/des/cipher.go b/crypto/des/cipher.go index 2cdacd9c73d..427eaed3153 100644 --- a/crypto/des/cipher.go +++ b/crypto/des/cipher.go @@ -5,11 +5,13 @@ package des import ( + "errors" + "internal/byteorder" "strconv" "github.com/runZeroInc/excrypto/crypto/cipher" - "github.com/runZeroInc/excrypto/crypto/internal/alias" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" ) // The DES block size in bytes. @@ -28,6 +30,10 @@ type desCipher struct { // NewCipher creates and returns a new [cipher.Block]. func NewCipher(key []byte) (cipher.Block, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/des: use of DES is not allowed in FIPS 140-only mode") + } + if len(key) != 8 { return nil, KeySizeError(len(key)) } @@ -72,6 +78,10 @@ type tripleDESCipher struct { // NewTripleDESCipher creates and returns a new [cipher.Block]. func NewTripleDESCipher(key []byte) (cipher.Block, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/des: use of TripleDES is not allowed in FIPS 140-only mode") + } + if len(key) != 24 { return nil, KeySizeError(len(key)) } @@ -96,7 +106,7 @@ func (c *tripleDESCipher) Encrypt(dst, src []byte) { panic("crypto/des: invalid buffer overlap") } - b := byteorder.BeUint64(src) + b := byteorder.BEUint64(src) b = permuteInitialBlock(b) left, right := uint32(b>>32), uint32(b) @@ -117,7 +127,7 @@ func (c *tripleDESCipher) Encrypt(dst, src []byte) { right = (right << 31) | (right >> 1) preOutput := (uint64(right) << 32) | uint64(left) - byteorder.BePutUint64(dst, permuteFinalBlock(preOutput)) + byteorder.BEPutUint64(dst, permuteFinalBlock(preOutput)) } func (c *tripleDESCipher) Decrypt(dst, src []byte) { @@ -131,7 +141,7 @@ func (c *tripleDESCipher) Decrypt(dst, src []byte) { panic("crypto/des: invalid buffer overlap") } - b := byteorder.BeUint64(src) + b := byteorder.BEUint64(src) b = permuteInitialBlock(b) left, right := uint32(b>>32), uint32(b) @@ -152,5 +162,5 @@ func (c *tripleDESCipher) Decrypt(dst, src []byte) { right = (right << 31) | (right >> 1) preOutput := (uint64(right) << 32) | uint64(left) - byteorder.BePutUint64(dst, permuteFinalBlock(preOutput)) + byteorder.BEPutUint64(dst, permuteFinalBlock(preOutput)) } diff --git a/crypto/dsa/dsa.go b/crypto/dsa/dsa.go index 489547d0bd5..1602f66f5e7 100644 --- a/crypto/dsa/dsa.go +++ b/crypto/dsa/dsa.go @@ -18,6 +18,7 @@ import ( "io" "math/big" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/internal/randutil" ) @@ -63,6 +64,10 @@ const numMRTests = 64 // GenerateParameters puts a random, valid set of DSA parameters into params. // This function can take many seconds, even on fast machines. func GenerateParameters(params *Parameters, rand io.Reader, sizes ParameterSizes) error { + if fips140only.Enabled { + return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") + } + // This function doesn't follow FIPS 186-3 exactly in that it doesn't // use a verification seed to generate the primes. The verification // seed doesn't appear to be exported or used by other code and @@ -157,6 +162,10 @@ GeneratePrimes: // GenerateKey generates a public&private key pair. The Parameters of the // [PrivateKey] must already be valid (see [GenerateParameters]). func GenerateKey(priv *PrivateKey, rand io.Reader) error { + if fips140only.Enabled { + return errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") + } + if priv.P == nil || priv.Q == nil || priv.G == nil { return errors.New("crypto/dsa: parameters not set up before generating key") } @@ -203,6 +212,10 @@ func fermatInverse(k, P *big.Int) *big.Int { // Be aware that calling Sign with an attacker-controlled [PrivateKey] may // require an arbitrary amount of CPU. func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) { + if fips140only.Enabled { + return nil, nil, errors.New("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") + } + randutil.MaybeReadByte(rand) // FIPS 186-3, section 4.6 @@ -271,6 +284,10 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err // to the byte-length of the subgroup. This function does not perform that // truncation itself. func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { + if fips140only.Enabled { + panic("crypto/dsa: use of DSA is not allowed in FIPS 140-only mode") + } + // FIPS 186-3, section 4.7 if pub.P.Sign() == 0 { diff --git a/crypto/ecdh/ecdh.go b/crypto/ecdh/ecdh.go index f7d93f52771..f09753c8978 100644 --- a/crypto/ecdh/ecdh.go +++ b/crypto/ecdh/ecdh.go @@ -7,12 +7,12 @@ package ecdh import ( + "crypto" "errors" "io" - "sync" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdh" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -51,14 +51,6 @@ type Curve interface { // The private method also allow us to expand the ECDH interface with more // methods in the future without breaking backwards compatibility. ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) - - // privateKeyToPublicKey converts a PrivateKey to a PublicKey. It's exposed - // as the PrivateKey.PublicKey method. - // - // This method always succeeds: for X25519, the zero key can't be - // constructed due to clamping; for NIST curves, it is rejected by - // NewPrivateKey. - privateKeyToPublicKey(*PrivateKey) *PublicKey } // PublicKey is an ECDH public key, usually a peer's ECDH share sent over the wire. @@ -70,6 +62,7 @@ type PublicKey struct { curve Curve publicKey []byte boring *boring.PublicKeyECDH + fips *ecdh.PublicKey } // Bytes returns a copy of the encoding of the public key. @@ -108,11 +101,9 @@ func (k *PublicKey) Curve() Curve { type PrivateKey struct { curve Curve privateKey []byte + publicKey *PublicKey boring *boring.PrivateKeyECDH - // publicKey is set under publicKeyOnce, to allow loading private keys with - // NewPrivateKey without having to perform a scalar multiplication. - publicKey *PublicKey - publicKeyOnce sync.Once + fips *ecdh.PrivateKey } // ECDH performs an ECDH exchange and returns the shared secret. The [PrivateKey] @@ -121,6 +112,8 @@ type PrivateKey struct { // For NIST curves, this performs ECDH as specified in SEC 1, Version 2.0, // Section 3.3.1, and returns the x-coordinate encoded according to SEC 1, // Version 2.0, Section 2.3.5. The result is never the point at infinity. +// This is also known as the Shared Secret Computation of the Ephemeral Unified +// Model scheme specified in NIST SP 800-56A Rev. 3, Section 6.1.2.2. // // For [X25519], this performs ECDH as specified in RFC 7748, Section 6.1. If // the result is the all-zero value, ECDH returns an error. @@ -161,25 +154,6 @@ func (k *PrivateKey) Curve() Curve { } func (k *PrivateKey) PublicKey() *PublicKey { - k.publicKeyOnce.Do(func() { - if k.boring != nil { - // Because we already checked in NewPrivateKey that the key is valid, - // there should not be any possible errors from BoringCrypto, - // so we turn the error into a panic. - // (We can't return it anyhow.) - kpub, err := k.boring.PublicKey() - if err != nil { - panic("boringcrypto: " + err.Error()) - } - k.publicKey = &PublicKey{ - curve: k.curve, - publicKey: kpub.Bytes(), - boring: kpub, - } - } else { - k.publicKey = k.curve.privateKeyToPublicKey(k) - } - }) return k.publicKey } diff --git a/crypto/ecdh/ecdh_test.go b/crypto/ecdh/ecdh_test.go index 826704a1eab..62d8f190d01 100644 --- a/crypto/ecdh/ecdh_test.go +++ b/crypto/ecdh/ecdh_test.go @@ -9,11 +9,17 @@ import ( "encoding/hex" "fmt" "io" + "os" + "os/exec" + "path/filepath" + "regexp" "strings" "testing" "crypto/rand" + "github.com/runZeroInc/excrypto/internal/testenv" + "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/cipher" "github.com/runZeroInc/excrypto/crypto/ecdh" @@ -420,7 +426,8 @@ package main import "github.com/runZeroInc/excrypto/crypto/ecdh" import "crypto/rand" func main() { - curve := ecdh.P384() + // Use P-256, since that's what the always-enabled CAST uses. + curve := ecdh.P256() key, err := curve.GenerateKey(rand.Reader) if err != nil { panic(err) } _, err = curve.NewPublicKey(key.PublicKey().Bytes()) @@ -433,6 +440,56 @@ func main() { } ` +// TestLinker ensures that using one curve does not bring all other +// implementations into the binary. This also guarantees that govulncheck can +// avoid warning about a curve-specific vulnerability if that curve is not used. +func TestLinker(t *testing.T) { + if testing.Short() { + t.Skip("test requires running 'go build'") + } + testenv.MustHaveGoBuild(t) + + dir := t.TempDir() + hello := filepath.Join(dir, "hello.go") + err := os.WriteFile(hello, []byte(linkerTestProgram), 0664) + if err != nil { + t.Fatal(err) + } + + run := func(args ...string) string { + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("%v: %v\n%s", args, err, string(out)) + } + return string(out) + } + + goBin := testenv.GoToolPath(t) + run(goBin, "build", "-o", "hello.exe", "hello.go") + if out := run("./hello.exe"); out != "OK\n" { + t.Error("unexpected output:", out) + } + + // List all text symbols under crypto/... and make sure there are some for + // P256, but none for the other curves. + var consistent bool + nm := run(goBin, "tool", "nm", "hello.exe") + for _, match := range regexp.MustCompile(`(?m)T (crypto/.*)$`).FindAllStringSubmatch(nm, -1) { + symbol := strings.ToLower(match[1]) + if strings.Contains(symbol, "p256") { + consistent = true + } + if strings.Contains(symbol, "p224") || strings.Contains(symbol, "p384") || strings.Contains(symbol, "p521") { + t.Errorf("unexpected symbol in program using only ecdh.P256: %s", match[1]) + } + } + if !consistent { + t.Error("no P256 symbols found in program using ecdh.P256, test is broken") + } +} + func TestMismatchedCurves(t *testing.T) { curves := []struct { name string diff --git a/crypto/ecdh/nist.go b/crypto/ecdh/nist.go index 3d1aaad4d99..341d630fb19 100644 --- a/crypto/ecdh/nist.go +++ b/crypto/ecdh/nist.go @@ -5,191 +5,143 @@ package ecdh import ( + "bytes" "errors" "io" - "math/bits" "github.com/runZeroInc/excrypto/crypto/internal/boring" - "github.com/runZeroInc/excrypto/crypto/internal/nistec" - "github.com/runZeroInc/excrypto/crypto/internal/randutil" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdh" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" ) -type nistCurve[Point nistPoint[Point]] struct { - name string - newPoint func() Point - scalarOrder []byte +type nistCurve struct { + name string + generate func(io.Reader) (*ecdh.PrivateKey, error) + newPrivateKey func([]byte) (*ecdh.PrivateKey, error) + newPublicKey func(publicKey []byte) (*ecdh.PublicKey, error) + sharedSecret func(*ecdh.PrivateKey, *ecdh.PublicKey) (sharedSecret []byte, err error) } -// nistPoint is a generic constraint for the nistec Point types. -type nistPoint[T any] interface { - Bytes() []byte - BytesX() ([]byte, error) - SetBytes([]byte) (T, error) - ScalarMult(T, []byte) (T, error) - ScalarBaseMult([]byte) (T, error) -} - -func (c *nistCurve[Point]) String() string { +func (c *nistCurve) String() string { return c.name } -var errInvalidPrivateKey = errors.New("crypto/ecdh: invalid private key") - -func (c *nistCurve[Point]) GenerateKey(rand io.Reader) (*PrivateKey, error) { +func (c *nistCurve) GenerateKey(rand io.Reader) (*PrivateKey, error) { if boring.Enabled && rand == boring.RandReader { key, bytes, err := boring.GenerateKeyECDH(c.name) if err != nil { return nil, err } - return newBoringPrivateKey(c, key, bytes) - } - - key := make([]byte, len(c.scalarOrder)) - randutil.MaybeReadByte(rand) - for { - if _, err := io.ReadFull(rand, key); err != nil { + pub, err := key.PublicKey() + if err != nil { return nil, err } - - // Mask off any excess bits if the size of the underlying field is not a - // whole number of bytes, which is only the case for P-521. We use a - // pointer to the scalarOrder field because comparing generic and - // instantiated types is not supported. - if &c.scalarOrder[0] == &p521Order[0] { - key[0] &= 0b0000_0001 + k := &PrivateKey{ + curve: c, + privateKey: bytes, + publicKey: &PublicKey{curve: c, publicKey: pub.Bytes(), boring: pub}, + boring: key, } + return k, nil + } - // In tests, rand will return all zeros and NewPrivateKey will reject - // the zero key as it generates the identity as a public key. This also - // makes this function consistent with crypto/elliptic.GenerateKey. - key[1] ^= 0x42 - - k, err := c.NewPrivateKey(key) - if err == errInvalidPrivateKey { - continue - } - return k, err + if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + return nil, errors.New("crypto/ecdh: only crypto/rand.Reader is allowed in FIPS 140-only mode") } -} -func (c *nistCurve[Point]) NewPrivateKey(key []byte) (*PrivateKey, error) { - if len(key) != len(c.scalarOrder) { - return nil, errors.New("crypto/ecdh: invalid private key size") + privateKey, err := c.generate(rand) + if err != nil { + return nil, err } - if isZero(key) || !isLess(key, c.scalarOrder) { - return nil, errInvalidPrivateKey + + k := &PrivateKey{ + curve: c, + privateKey: privateKey.Bytes(), + fips: privateKey, + publicKey: &PublicKey{ + curve: c, + publicKey: privateKey.PublicKey().Bytes(), + fips: privateKey.PublicKey(), + }, } if boring.Enabled { - bk, err := boring.NewPrivateKeyECDH(c.name, key) + bk, err := boring.NewPrivateKeyECDH(c.name, k.privateKey) if err != nil { return nil, err } - return newBoringPrivateKey(c, bk, key) - } - k := &PrivateKey{ - curve: c, - privateKey: append([]byte{}, key...), + pub, err := bk.PublicKey() + if err != nil { + return nil, err + } + k.boring = bk + k.publicKey.boring = pub } return k, nil } -func newBoringPrivateKey(c Curve, bk *boring.PrivateKeyECDH, privateKey []byte) (*PrivateKey, error) { - k := &PrivateKey{ - curve: c, - boring: bk, - privateKey: append([]byte(nil), privateKey...), +func (c *nistCurve) NewPrivateKey(key []byte) (*PrivateKey, error) { + if boring.Enabled { + bk, err := boring.NewPrivateKeyECDH(c.name, key) + if err != nil { + return nil, errors.New("crypto/ecdh: invalid private key") + } + pub, err := bk.PublicKey() + if err != nil { + return nil, errors.New("crypto/ecdh: invalid private key") + } + k := &PrivateKey{ + curve: c, + privateKey: bytes.Clone(key), + publicKey: &PublicKey{curve: c, publicKey: pub.Bytes(), boring: pub}, + boring: bk, + } + return k, nil } - return k, nil -} -func (c *nistCurve[Point]) privateKeyToPublicKey(key *PrivateKey) *PublicKey { - boring.Unreachable() - if key.curve != c { - panic("crypto/ecdh: internal error: converting the wrong key type") - } - p, err := c.newPoint().ScalarBaseMult(key.privateKey) + fk, err := c.newPrivateKey(key) if err != nil { - // This is unreachable because the only error condition of - // ScalarBaseMult is if the input is not the right size. - panic("crypto/ecdh: internal error: nistec ScalarBaseMult failed for a fixed-size input") - } - publicKey := p.Bytes() - if len(publicKey) == 1 { - // The encoding of the identity is a single 0x00 byte. This is - // unreachable because the only scalar that generates the identity is - // zero, which is rejected by NewPrivateKey. - panic("crypto/ecdh: internal error: nistec ScalarBaseMult returned the identity") - } - return &PublicKey{ - curve: key.curve, - publicKey: publicKey, - } -} - -// isZero returns whether a is all zeroes in constant time. -func isZero(a []byte) bool { - var acc byte - for _, b := range a { - acc |= b - } - return acc == 0 -} - -// isLess returns whether a < b, where a and b are big-endian buffers of the -// same length and shorter than 72 bytes. -func isLess(a, b []byte) bool { - if len(a) != len(b) { - panic("crypto/ecdh: internal error: mismatched isLess inputs") - } - - // Copy the values into a fixed-size preallocated little-endian buffer. - // 72 bytes is enough for every scalar in this package, and having a fixed - // size lets us avoid heap allocations. - if len(a) > 72 { - panic("crypto/ecdh: internal error: isLess input too large") - } - bufA, bufB := make([]byte, 72), make([]byte, 72) - for i := range a { - bufA[i], bufB[i] = a[len(a)-i-1], b[len(b)-i-1] + return nil, err } - - // Perform a subtraction with borrow. - var borrow uint64 - for i := 0; i < len(bufA); i += 8 { - limbA, limbB := byteorder.LeUint64(bufA[i:]), byteorder.LeUint64(bufB[i:]) - _, borrow = bits.Sub64(limbA, limbB, borrow) + k := &PrivateKey{ + curve: c, + privateKey: bytes.Clone(key), + fips: fk, + publicKey: &PublicKey{ + curve: c, + publicKey: fk.PublicKey().Bytes(), + fips: fk.PublicKey(), + }, } - - // If there is a borrow at the end of the operation, then a < b. - return borrow == 1 + return k, nil } -func (c *nistCurve[Point]) NewPublicKey(key []byte) (*PublicKey, error) { +func (c *nistCurve) NewPublicKey(key []byte) (*PublicKey, error) { // Reject the point at infinity and compressed encodings. + // Note that boring.NewPublicKeyECDH would accept them. if len(key) == 0 || key[0] != 4 { return nil, errors.New("crypto/ecdh: invalid public key") } k := &PublicKey{ curve: c, - publicKey: append([]byte{}, key...), + publicKey: bytes.Clone(key), } if boring.Enabled { bk, err := boring.NewPublicKeyECDH(c.name, k.publicKey) if err != nil { - return nil, err + return nil, errors.New("crypto/ecdh: invalid public key") } k.boring = bk } else { - // SetBytes also checks that the point is on the curve. - if _, err := c.newPoint().SetBytes(key); err != nil { + fk, err := c.newPublicKey(key) + if err != nil { return nil, err } + k.fips = fk } return k, nil } -func (c *nistCurve[Point]) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) { +func (c *nistCurve) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, error) { // Note that this function can't return an error, as NewPublicKey rejects // invalid points and the point at infinity, and NewPrivateKey rejects // invalid scalars and the zero value. BytesX returns an error for the point @@ -200,16 +152,7 @@ func (c *nistCurve[Point]) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, e if boring.Enabled { return boring.ECDH(local.boring, remote.boring) } - - boring.Unreachable() - p, err := c.newPoint().SetBytes(remote.publicKey) - if err != nil { - return nil, err - } - if _, err := p.ScalarMult(p, local.privateKey); err != nil { - return nil, err - } - return p.BytesX() + return c.sharedSecret(local.fips, remote.fips) } // P256 returns a [Curve] which implements NIST P-256 (FIPS 186-3, section D.2.3), @@ -219,18 +162,22 @@ func (c *nistCurve[Point]) ecdh(local *PrivateKey, remote *PublicKey) ([]byte, e // be used for equality checks and switch statements. func P256() Curve { return p256 } -var p256 = &nistCurve[*nistec.P256Point]{ - name: "P-256", - newPoint: nistec.NewP256Point, - scalarOrder: p256Order, +var p256 = &nistCurve{ + name: "P-256", + generate: func(r io.Reader) (*ecdh.PrivateKey, error) { + return ecdh.GenerateKey(ecdh.P256(), r) + }, + newPrivateKey: func(b []byte) (*ecdh.PrivateKey, error) { + return ecdh.NewPrivateKey(ecdh.P256(), b) + }, + newPublicKey: func(publicKey []byte) (*ecdh.PublicKey, error) { + return ecdh.NewPublicKey(ecdh.P256(), publicKey) + }, + sharedSecret: func(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) (sharedSecret []byte, err error) { + return ecdh.ECDH(ecdh.P256(), priv, pub) + }, } -var p256Order = []byte{ - 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, - 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51} - // P384 returns a [Curve] which implements NIST P-384 (FIPS 186-3, section D.2.4), // also known as secp384r1. // @@ -238,20 +185,22 @@ var p256Order = []byte{ // be used for equality checks and switch statements. func P384() Curve { return p384 } -var p384 = &nistCurve[*nistec.P384Point]{ - name: "P-384", - newPoint: nistec.NewP384Point, - scalarOrder: p384Order, +var p384 = &nistCurve{ + name: "P-384", + generate: func(r io.Reader) (*ecdh.PrivateKey, error) { + return ecdh.GenerateKey(ecdh.P384(), r) + }, + newPrivateKey: func(b []byte) (*ecdh.PrivateKey, error) { + return ecdh.NewPrivateKey(ecdh.P384(), b) + }, + newPublicKey: func(publicKey []byte) (*ecdh.PublicKey, error) { + return ecdh.NewPublicKey(ecdh.P384(), publicKey) + }, + sharedSecret: func(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) (sharedSecret []byte, err error) { + return ecdh.ECDH(ecdh.P384(), priv, pub) + }, } -var p384Order = []byte{ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf, - 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a, - 0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73} - // P521 returns a [Curve] which implements NIST P-521 (FIPS 186-3, section D.2.5), // also known as secp521r1. // @@ -259,18 +208,18 @@ var p384Order = []byte{ // be used for equality checks and switch statements. func P521() Curve { return p521 } -var p521 = &nistCurve[*nistec.P521Point]{ - name: "P-521", - newPoint: nistec.NewP521Point, - scalarOrder: p521Order, +var p521 = &nistCurve{ + name: "P-521", + generate: func(r io.Reader) (*ecdh.PrivateKey, error) { + return ecdh.GenerateKey(ecdh.P521(), r) + }, + newPrivateKey: func(b []byte) (*ecdh.PrivateKey, error) { + return ecdh.NewPrivateKey(ecdh.P521(), b) + }, + newPublicKey: func(publicKey []byte) (*ecdh.PublicKey, error) { + return ecdh.NewPublicKey(ecdh.P521(), publicKey) + }, + sharedSecret: func(priv *ecdh.PrivateKey, pub *ecdh.PublicKey) (sharedSecret []byte, err error) { + return ecdh.ECDH(ecdh.P521(), priv, pub) + }, } - -var p521Order = []byte{0x01, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, - 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, 0x96, 0x6b, - 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, 0xa5, 0xd0, - 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae, - 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09} diff --git a/crypto/ecdh/x25519.go b/crypto/ecdh/x25519.go index 2f746ae4493..ebea50efc65 100644 --- a/crypto/ecdh/x25519.go +++ b/crypto/ecdh/x25519.go @@ -5,10 +5,12 @@ package ecdh import ( + "bytes" "errors" "io" - "github.com/runZeroInc/excrypto/crypto/internal/edwards25519/field" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/edwards25519/field" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/internal/randutil" ) @@ -34,6 +36,9 @@ func (c *x25519Curve) String() string { } func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") + } key := make([]byte, x25519PrivateKeySize) randutil.MaybeReadByte(rand) if _, err := io.ReadFull(rand, key); err != nil { @@ -43,35 +48,35 @@ func (c *x25519Curve) GenerateKey(rand io.Reader) (*PrivateKey, error) { } func (c *x25519Curve) NewPrivateKey(key []byte) (*PrivateKey, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") + } if len(key) != x25519PrivateKeySize { return nil, errors.New("crypto/ecdh: invalid private key size") } + publicKey := make([]byte, x25519PublicKeySize) + x25519Basepoint := [32]byte{9} + x25519ScalarMult(publicKey, key, x25519Basepoint[:]) + // We don't check for the all-zero public key here because the scalar is + // never zero because of clamping, and the basepoint is not the identity in + // the prime-order subgroup(s). return &PrivateKey{ curve: c, - privateKey: append([]byte{}, key...), + privateKey: bytes.Clone(key), + publicKey: &PublicKey{curve: c, publicKey: publicKey}, }, nil } -func (c *x25519Curve) privateKeyToPublicKey(key *PrivateKey) *PublicKey { - if key.curve != c { - panic("crypto/ecdh: internal error: converting the wrong key type") - } - k := &PublicKey{ - curve: key.curve, - publicKey: make([]byte, x25519PublicKeySize), - } - x25519Basepoint := [32]byte{9} - x25519ScalarMult(k.publicKey, key.privateKey, x25519Basepoint[:]) - return k -} - func (c *x25519Curve) NewPublicKey(key []byte) (*PublicKey, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/ecdh: use of X25519 is not allowed in FIPS 140-only mode") + } if len(key) != x25519PublicKeySize { return nil, errors.New("crypto/ecdh: invalid public key") } return &PublicKey{ curve: c, - publicKey: append([]byte{}, key...), + publicKey: bytes.Clone(key), }, nil } @@ -135,3 +140,12 @@ func x25519ScalarMult(dst, scalar, point []byte) { x2.Multiply(&x2, &z2) copy(dst[:], x2.Bytes()) } + +// isZero reports whether x is all zeroes in constant time. +func isZero(x []byte) bool { + var acc byte + for _, b := range x { + acc |= b + } + return acc == 0 +} diff --git a/crypto/aes/_asm/gcm/go.mod b/crypto/ecdsa/boring.go similarity index 100% rename from crypto/aes/_asm/gcm/go.mod rename to crypto/ecdsa/boring.go diff --git a/crypto/ecdsa/ecdsa.go b/crypto/ecdsa/ecdsa.go index f02dfb7a08f..ec6deb78723 100644 --- a/crypto/ecdsa/ecdsa.go +++ b/crypto/ecdsa/ecdsa.go @@ -3,7 +3,7 @@ // license that can be found in the LICENSE file. // Package ecdsa implements the Elliptic Curve Digital Signature Algorithm, as -// defined in FIPS 186-4 and SEC 1, Version 2.0. +// defined in [FIPS 186-5]. // // Signatures generated by this package are not deterministic, but entropy is // mixed with the private key and the message, achieving the same level of @@ -12,38 +12,29 @@ // Operations involving private keys are implemented using constant-time // algorithms, as long as an [elliptic.Curve] returned by [elliptic.P224], // [elliptic.P256], [elliptic.P384], or [elliptic.P521] is used. -package ecdsa - -// [FIPS 186-4] references ANSI X9.62-2005 for the bulk of the ECDSA algorithm. -// That standard is not freely available, which is a problem in an open source -// implementation, because not only the implementer, but also any maintainer, -// contributor, reviewer, auditor, and learner needs access to it. Instead, this -// package references and follows the equivalent [SEC 1, Version 2.0]. // -// [FIPS 186-4]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf -// [SEC 1, Version 2.0]: https://www.secg.org/sec1-v2.pdf +// [FIPS 186-5]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf +package ecdsa import ( - "bytes" + "crypto" "errors" - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/crypto/aes" - "github.com/runZeroInc/excrypto/crypto/cipher" + "io" + "math/big" + "github.com/runZeroInc/excrypto/crypto/ecdh" "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/crypto/internal/bigmod" "github.com/runZeroInc/excrypto/crypto/internal/boring" "github.com/runZeroInc/excrypto/crypto/internal/boring/bbig" - "github.com/runZeroInc/excrypto/crypto/internal/nistec" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140hash" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/internal/randutil" "github.com/runZeroInc/excrypto/crypto/sha512" "github.com/runZeroInc/excrypto/crypto/subtle" - "io" - "math/big" - "sync" - "github.com/runZeroInc/excrypto/x/crypto/cryptobyte" - "github.com/runZeroInc/excrypto/x/crypto/cryptobyte/asn1" + "golang.org/x/crypto/cryptobyte" + "golang.org/x/crypto/cryptobyte/asn1" ) // PublicKey represents an ECDSA public key. @@ -143,14 +134,24 @@ func bigIntEqual(a, b *big.Int) bool { return subtle.ConstantTimeCompare(a.Bytes(), b.Bytes()) == 1 } -// Sign signs digest with priv, reading randomness from rand. The opts argument -// is not currently used but, in keeping with the crypto.Signer interface, -// should be the hash function used to digest the message. +// Sign signs a hash (which should be the result of hashing a larger message +// with opts.HashFunc()) using the private key, priv. If the hash is longer than +// the bit-length of the private key's curve order, the hash will be truncated +// to that length. It returns the ASN.1 encoded signature, like [SignASN1]. +// +// If rand is not nil, the signature is randomized. Most applications should use +// [crypto/rand.Reader] as rand. Note that the returned signature does not +// depend deterministically on the bytes read from rand, and may change between +// calls and/or between versions. // -// This method implements crypto.Signer, which is an interface to support keys -// where the private part is kept in, for example, a hardware module. Common -// uses can use the [SignASN1] function in this package directly. +// If rand is nil, Sign will produce a deterministic signature according to RFC +// 6979. When producing a deterministic signature, opts.HashFunc() must be the +// function used to produce digest and priv.Curve must be one of +// [elliptic.P224], [elliptic.P256], [elliptic.P384], or [elliptic.P521]. func (priv *PrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { + if rand == nil { + return signRFC6979(priv, digest, opts) + } return SignASN1(rand, priv, digest) } @@ -173,78 +174,29 @@ func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { switch c.Params() { case elliptic.P224().Params(): - return generateNISTEC(p224(), rand) + return generateFIPS(c, ecdsa.P224(), rand) case elliptic.P256().Params(): - return generateNISTEC(p256(), rand) + return generateFIPS(c, ecdsa.P256(), rand) case elliptic.P384().Params(): - return generateNISTEC(p384(), rand) + return generateFIPS(c, ecdsa.P384(), rand) case elliptic.P521().Params(): - return generateNISTEC(p521(), rand) + return generateFIPS(c, ecdsa.P521(), rand) default: return generateLegacy(c, rand) } } -func generateNISTEC[Point nistPoint[Point]](c *nistCurve[Point], rand io.Reader) (*PrivateKey, error) { - k, Q, err := randomPoint(c, rand) - if err != nil { - return nil, err +func generateFIPS[P ecdsa.Point[P]](curve elliptic.Curve, c *ecdsa.Curve[P], rand io.Reader) (*PrivateKey, error) { + if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } - - priv := new(PrivateKey) - priv.PublicKey.Curve = c.curve - priv.D = new(big.Int).SetBytes(k.Bytes(c.N)) - priv.PublicKey.X, priv.PublicKey.Y, err = c.pointToAffine(Q) + privateKey, err := ecdsa.GenerateKey(c, rand) if err != nil { return nil, err } - return priv, nil -} - -// randomPoint returns a random scalar and the corresponding point using the -// procedure given in FIPS 186-4, Appendix B.5.2 (rejection sampling). -func randomPoint[Point nistPoint[Point]](c *nistCurve[Point], rand io.Reader) (k *bigmod.Nat, p Point, err error) { - k = bigmod.NewNat() - for { - b := make([]byte, c.N.Size()) - if _, err = io.ReadFull(rand, b); err != nil { - return - } - - // Mask off any excess bits to increase the chance of hitting a value in - // (0, N). These are the most dangerous lines in the package and maybe in - // the library: a single bit of bias in the selection of nonces would likely - // lead to key recovery, but no tests would fail. Look but DO NOT TOUCH. - if excess := len(b)*8 - c.N.BitLen(); excess > 0 { - // Just to be safe, assert that this only happens for the one curve that - // doesn't have a round number of bits. - if excess != 0 && c.curve.Params().Name != "P-521" { - panic("ecdsa: internal error: unexpectedly masking off bits") - } - b[0] >>= excess - } - - // FIPS 186-4 makes us check k <= N - 2 and then add one. - // Checking 0 < k <= N - 1 is strictly equivalent. - // None of this matters anyway because the chance of selecting - // zero is cryptographically negligible. - if _, err = k.SetBytes(b, c.N); err == nil && k.IsZero() == 0 { - break - } - - if testingOnlyRejectionSamplingLooped != nil { - testingOnlyRejectionSamplingLooped() - } - } - - p, err = c.newPoint().ScalarBaseMult(k.Bytes(c.N)) - return + return privateKeyFromFIPS(curve, privateKey) } -// testingOnlyRejectionSamplingLooped is called when rejection sampling in -// randomPoint rejects a candidate for being higher than the modulus. -var testingOnlyRejectionSamplingLooped func() - // errNoAsm is returned by signAsm and verifyAsm when the assembly // implementation is not available. var errNoAsm = errors.New("no assembly implementation available") @@ -269,74 +221,77 @@ func SignASN1(rand io.Reader, priv *PrivateKey, hash []byte) ([]byte, error) { } boring.UnreachableExceptTests() - csprng, err := mixedCSPRNG(rand, priv, hash) - if err != nil { - return nil, err - } - - if sig, err := signAsm(priv, csprng, hash); err != errNoAsm { - return sig, err - } - switch priv.Curve.Params() { case elliptic.P224().Params(): - return signNISTEC(p224(), priv, csprng, hash) + return signFIPS(ecdsa.P224(), priv, rand, hash) case elliptic.P256().Params(): - return signNISTEC(p256(), priv, csprng, hash) + return signFIPS(ecdsa.P256(), priv, rand, hash) case elliptic.P384().Params(): - return signNISTEC(p384(), priv, csprng, hash) + return signFIPS(ecdsa.P384(), priv, rand, hash) case elliptic.P521().Params(): - return signNISTEC(p521(), priv, csprng, hash) + return signFIPS(ecdsa.P521(), priv, rand, hash) default: - return signLegacy(priv, csprng, hash) + return signLegacy(priv, rand, hash) } } -func signNISTEC[Point nistPoint[Point]](c *nistCurve[Point], priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { - // SEC 1, Version 2.0, Section 4.1.3 - - k, R, err := randomPoint(c, csprng) - if err != nil { - return nil, err +func signFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey, rand io.Reader, hash []byte) ([]byte, error) { + if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + return nil, errors.New("crypto/ecdsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") } - - // kInv = k⁻¹ - kInv := bigmod.NewNat() - inverse(c, kInv, k) - - Rx, err := R.BytesX() + // privateKeyToFIPS is very slow in FIPS mode because it performs a + // Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache + // it or attach it to the PrivateKey. + k, err := privateKeyToFIPS(c, priv) if err != nil { return nil, err } - r, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N) + // Always using SHA-512 instead of the hash that computed hash is + // technically a violation of draft-irtf-cfrg-det-sigs-with-noise-04 but in + // our API we don't get to know what it was, and this has no security impact. + sig, err := ecdsa.Sign(c, sha512.New, k, rand, hash) if err != nil { return nil, err } + return encodeSignature(sig.R, sig.S) +} - // The spec wants us to retry here, but the chance of hitting this condition - // on a large prime-order group like the NIST curves we support is - // cryptographically negligible. If we hit it, something is awfully wrong. - if r.IsZero() == 1 { - return nil, errors.New("ecdsa: internal error: r is zero") +func signRFC6979(priv *PrivateKey, hash []byte, opts crypto.SignerOpts) ([]byte, error) { + if opts == nil { + return nil, errors.New("ecdsa: Sign called with nil opts") } + h := opts.HashFunc() + if h.Size() != len(hash) { + return nil, errors.New("ecdsa: hash length does not match hash function") + } + switch priv.Curve.Params() { + case elliptic.P224().Params(): + return signFIPSDeterministic(ecdsa.P224(), h, priv, hash) + case elliptic.P256().Params(): + return signFIPSDeterministic(ecdsa.P256(), h, priv, hash) + case elliptic.P384().Params(): + return signFIPSDeterministic(ecdsa.P384(), h, priv, hash) + case elliptic.P521().Params(): + return signFIPSDeterministic(ecdsa.P521(), h, priv, hash) + default: + return nil, errors.New("ecdsa: curve not supported by deterministic signatures") + } +} - e := bigmod.NewNat() - hashToNat(c, e, hash) - - s, err := bigmod.NewNat().SetBytes(priv.D.Bytes(), c.N) +func signFIPSDeterministic[P ecdsa.Point[P]](c *ecdsa.Curve[P], hashFunc crypto.Hash, priv *PrivateKey, hash []byte) ([]byte, error) { + k, err := privateKeyToFIPS(c, priv) if err != nil { return nil, err } - s.Mul(r, c.N) - s.Add(e, c.N) - s.Mul(kInv, c.N) - - // Again, the chance of this happening is cryptographically negligible. - if s.IsZero() == 1 { - return nil, errors.New("ecdsa: internal error: s is zero") + h := fips140hash.UnwrapNew(hashFunc.New) + if fips140only.Enabled && !fips140only.ApprovedHash(h()) { + return nil, errors.New("crypto/ecdsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } - - return encodeSignature(r.Bytes(c.N), s.Bytes(c.N)) + sig, err := ecdsa.SignDeterministic(c, h, k, hash) + if err != nil { + return nil, err + } + return encodeSignature(sig.R, sig.S) } func encodeSignature(r, s []byte) ([]byte, error) { @@ -366,105 +321,6 @@ func addASN1IntBytes(b *cryptobyte.Builder, bytes []byte) { }) } -// inverse sets kInv to the inverse of k modulo the order of the curve. -func inverse[Point nistPoint[Point]](c *nistCurve[Point], kInv, k *bigmod.Nat) { - if c.curve.Params().Name == "P-256" { - kBytes, err := nistec.P256OrdInverse(k.Bytes(c.N)) - // Some platforms don't implement P256OrdInverse, and always return an error. - if err == nil { - _, err := kInv.SetBytes(kBytes, c.N) - if err != nil { - panic("ecdsa: internal error: P256OrdInverse produced an invalid value") - } - return - } - } - - // Calculate the inverse of s in GF(N) using Fermat's method - // (exponentiation modulo P - 2, per Euler's theorem) - kInv.Exp(k, c.nMinus2, c.N) -} - -// hashToNat sets e to the left-most bits of hash, according to -// SEC 1, Section 4.1.3, point 5 and Section 4.1.4, point 3. -func hashToNat[Point nistPoint[Point]](c *nistCurve[Point], e *bigmod.Nat, hash []byte) { - // ECDSA asks us to take the left-most log2(N) bits of hash, and use them as - // an integer modulo N. This is the absolute worst of all worlds: we still - // have to reduce, because the result might still overflow N, but to take - // the left-most bits for P-521 we have to do a right shift. - if size := c.N.Size(); len(hash) >= size { - hash = hash[:size] - if excess := len(hash)*8 - c.N.BitLen(); excess > 0 { - hash = bytes.Clone(hash) - for i := len(hash) - 1; i >= 0; i-- { - hash[i] >>= excess - if i > 0 { - hash[i] |= hash[i-1] << (8 - excess) - } - } - } - } - _, err := e.SetOverflowingBytes(hash, c.N) - if err != nil { - panic("ecdsa: internal error: truncated hash is too long") - } -} - -// mixedCSPRNG returns a CSPRNG that mixes entropy from rand with the message -// and the private key, to protect the key in case rand fails. This is -// equivalent in security to RFC 6979 deterministic nonce generation, but still -// produces randomized signatures. -func mixedCSPRNG(rand io.Reader, priv *PrivateKey, hash []byte) (io.Reader, error) { - // This implementation derives the nonce from an AES-CTR CSPRNG keyed by: - // - // SHA2-512(priv.D || entropy || hash)[:32] - // - // The CSPRNG key is indifferentiable from a random oracle as shown in - // [Coron], the AES-CTR stream is indifferentiable from a random oracle - // under standard cryptographic assumptions (see [Larsson] for examples). - // - // [Coron]: https://cs.nyu.edu/~dodis/ps/merkle.pdf - // [Larsson]: https://web.archive.org/web/20040719170906/https://www.nada.kth.se/kurser/kth/2D1441/semteo03/lecturenotes/assump.pdf - - // Get 256 bits of entropy from rand. - entropy := make([]byte, 32) - if _, err := io.ReadFull(rand, entropy); err != nil { - return nil, err - } - - // Initialize an SHA-512 hash context; digest... - md := sha512.New() - md.Write(priv.D.Bytes()) // the private key, - md.Write(entropy) // the entropy, - md.Write(hash) // and the input hash; - key := md.Sum(nil)[:32] // and compute ChopMD-256(SHA-512), - // which is an indifferentiable MAC. - - // Create an AES-CTR instance to use as a CSPRNG. - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - // Create a CSPRNG that xors a stream of zeros with - // the output of the AES-CTR instance. - const aesIV = "IV for ECDSA CTR" - return &cipher.StreamReader{ - R: zeroReader, - S: cipher.NewCTR(block, []byte(aesIV)), - }, nil -} - -type zr struct{} - -var zeroReader = zr{} - -// Read replaces the contents of dst with zeros. It is safe for concurrent use. -func (zr) Read(dst []byte) (n int, err error) { - clear(dst) - return len(dst), nil -} - // VerifyASN1 verifies the ASN.1 encoded signature, sig, of hash using the // public key, pub. Its return value records whether the signature is valid. // @@ -480,75 +336,33 @@ func VerifyASN1(pub *PublicKey, hash, sig []byte) bool { } boring.UnreachableExceptTests() - if err := verifyAsm(pub, hash, sig); err != errNoAsm { - return err == nil - } - switch pub.Curve.Params() { case elliptic.P224().Params(): - return verifyNISTEC(p224(), pub, hash, sig) + return verifyFIPS(ecdsa.P224(), pub, hash, sig) case elliptic.P256().Params(): - return verifyNISTEC(p256(), pub, hash, sig) + return verifyFIPS(ecdsa.P256(), pub, hash, sig) case elliptic.P384().Params(): - return verifyNISTEC(p384(), pub, hash, sig) + return verifyFIPS(ecdsa.P384(), pub, hash, sig) case elliptic.P521().Params(): - return verifyNISTEC(p521(), pub, hash, sig) + return verifyFIPS(ecdsa.P521(), pub, hash, sig) default: return verifyLegacy(pub, hash, sig) } } -func verifyNISTEC[Point nistPoint[Point]](c *nistCurve[Point], pub *PublicKey, hash, sig []byte) bool { - rBytes, sBytes, err := parseSignature(sig) +func verifyFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], pub *PublicKey, hash, sig []byte) bool { + r, s, err := parseSignature(sig) if err != nil { return false } - - Q, err := c.pointFromAffine(pub.X, pub.Y) + k, err := publicKeyToFIPS(c, pub) if err != nil { return false } - - // SEC 1, Version 2.0, Section 4.1.4 - - r, err := bigmod.NewNat().SetBytes(rBytes, c.N) - if err != nil || r.IsZero() == 1 { + if err := ecdsa.Verify(c, k, hash, &ecdsa.Signature{R: r, S: s}); err != nil { return false } - s, err := bigmod.NewNat().SetBytes(sBytes, c.N) - if err != nil || s.IsZero() == 1 { - return false - } - - e := bigmod.NewNat() - hashToNat(c, e, hash) - - // w = s⁻¹ - w := bigmod.NewNat() - inverse(c, w, s) - - // p₁ = [e * s⁻¹]G - p1, err := c.newPoint().ScalarBaseMult(e.Mul(w, c.N).Bytes(c.N)) - if err != nil { - return false - } - // p₂ = [r * s⁻¹]Q - p2, err := Q.ScalarMult(Q, w.Mul(r, c.N).Bytes(c.N)) - if err != nil { - return false - } - // BytesX returns an error for the point at infinity. - Rx, err := p1.Add(p1, p2).BytesX() - if err != nil { - return false - } - - v, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N) - if err != nil { - return false - } - - return v.Equal(r) == 1 + return true } func parseSignature(sig []byte) (r, s []byte, err error) { @@ -564,32 +378,47 @@ func parseSignature(sig []byte) (r, s []byte, err error) { return r, s, nil } -type nistCurve[Point nistPoint[Point]] struct { - newPoint func() Point - curve elliptic.Curve - N *bigmod.Modulus - nMinus2 []byte +func publicKeyFromFIPS(curve elliptic.Curve, pub *ecdsa.PublicKey) (*PublicKey, error) { + x, y, err := pointToAffine(curve, pub.Bytes()) + if err != nil { + return nil, err + } + return &PublicKey{Curve: curve, X: x, Y: y}, nil +} + +func privateKeyFromFIPS(curve elliptic.Curve, priv *ecdsa.PrivateKey) (*PrivateKey, error) { + pub, err := publicKeyFromFIPS(curve, priv.PublicKey()) + if err != nil { + return nil, err + } + return &PrivateKey{PublicKey: *pub, D: new(big.Int).SetBytes(priv.Bytes())}, nil } -// nistPoint is a generic constraint for the nistec Point types. -type nistPoint[T any] interface { - Bytes() []byte - BytesX() ([]byte, error) - SetBytes([]byte) (T, error) - Add(T, T) T - ScalarMult(T, []byte) (T, error) - ScalarBaseMult([]byte) (T, error) +func publicKeyToFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], pub *PublicKey) (*ecdsa.PublicKey, error) { + Q, err := pointFromAffine(pub.Curve, pub.X, pub.Y) + if err != nil { + return nil, err + } + return ecdsa.NewPublicKey(c, Q) +} + +func privateKeyToFIPS[P ecdsa.Point[P]](c *ecdsa.Curve[P], priv *PrivateKey) (*ecdsa.PrivateKey, error) { + Q, err := pointFromAffine(priv.Curve, priv.X, priv.Y) + if err != nil { + return nil, err + } + return ecdsa.NewPrivateKey(c, priv.D.Bytes(), Q) } -// pointFromAffine is used to convert the PublicKey to a nistec Point. -func (curve *nistCurve[Point]) pointFromAffine(x, y *big.Int) (p Point, err error) { - bitSize := curve.curve.Params().BitSize +// pointFromAffine is used to convert the PublicKey to a nistec SetBytes input. +func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) { + bitSize := curve.Params().BitSize // Reject values that would not get correctly encoded. if x.Sign() < 0 || y.Sign() < 0 { - return p, errors.New("negative coordinate") + return nil, errors.New("negative coordinate") } if x.BitLen() > bitSize || y.BitLen() > bitSize { - return p, errors.New("overflowing coordinate") + return nil, errors.New("overflowing coordinate") } // Encode the coordinates and let SetBytes reject invalid points. byteLen := (bitSize + 7) / 8 @@ -597,81 +426,17 @@ func (curve *nistCurve[Point]) pointFromAffine(x, y *big.Int) (p Point, err erro buf[0] = 4 // uncompressed point x.FillBytes(buf[1 : 1+byteLen]) y.FillBytes(buf[1+byteLen : 1+2*byteLen]) - return curve.newPoint().SetBytes(buf) + return buf, nil } -// pointToAffine is used to convert a nistec Point to a PublicKey. -func (curve *nistCurve[Point]) pointToAffine(p Point) (x, y *big.Int, err error) { - out := p.Bytes() - if len(out) == 1 && out[0] == 0 { +// pointToAffine is used to convert a nistec Bytes encoding to a PublicKey. +func pointToAffine(curve elliptic.Curve, p []byte) (x, y *big.Int, err error) { + if len(p) == 1 && p[0] == 0 { // This is the encoding of the point at infinity. return nil, nil, errors.New("ecdsa: public key point is the infinity") } - byteLen := (curve.curve.Params().BitSize + 7) / 8 - x = new(big.Int).SetBytes(out[1 : 1+byteLen]) - y = new(big.Int).SetBytes(out[1+byteLen:]) + byteLen := (curve.Params().BitSize + 7) / 8 + x = new(big.Int).SetBytes(p[1 : 1+byteLen]) + y = new(big.Int).SetBytes(p[1+byteLen:]) return x, y, nil } - -var p224Once sync.Once -var _p224 *nistCurve[*nistec.P224Point] - -func p224() *nistCurve[*nistec.P224Point] { - p224Once.Do(func() { - _p224 = &nistCurve[*nistec.P224Point]{ - newPoint: func() *nistec.P224Point { return nistec.NewP224Point() }, - } - precomputeParams(_p224, elliptic.P224()) - }) - return _p224 -} - -var p256Once sync.Once -var _p256 *nistCurve[*nistec.P256Point] - -func p256() *nistCurve[*nistec.P256Point] { - p256Once.Do(func() { - _p256 = &nistCurve[*nistec.P256Point]{ - newPoint: func() *nistec.P256Point { return nistec.NewP256Point() }, - } - precomputeParams(_p256, elliptic.P256()) - }) - return _p256 -} - -var p384Once sync.Once -var _p384 *nistCurve[*nistec.P384Point] - -func p384() *nistCurve[*nistec.P384Point] { - p384Once.Do(func() { - _p384 = &nistCurve[*nistec.P384Point]{ - newPoint: func() *nistec.P384Point { return nistec.NewP384Point() }, - } - precomputeParams(_p384, elliptic.P384()) - }) - return _p384 -} - -var p521Once sync.Once -var _p521 *nistCurve[*nistec.P521Point] - -func p521() *nistCurve[*nistec.P521Point] { - p521Once.Do(func() { - _p521 = &nistCurve[*nistec.P521Point]{ - newPoint: func() *nistec.P521Point { return nistec.NewP521Point() }, - } - precomputeParams(_p521, elliptic.P521()) - }) - return _p521 -} - -func precomputeParams[Point nistPoint[Point]](c *nistCurve[Point], curve elliptic.Curve) { - params := curve.Params() - c.curve = curve - var err error - c.N, err = bigmod.NewModulusFromBig(params.N) - if err != nil { - panic(err) - } - c.nMinus2 = new(big.Int).Sub(params.N, big.NewInt(2)).Bytes() -} diff --git a/crypto/ecdsa/ecdsa_legacy.go b/crypto/ecdsa/ecdsa_legacy.go index d6c872c6f37..fe697ae7f2c 100644 --- a/crypto/ecdsa/ecdsa_legacy.go +++ b/crypto/ecdsa/ecdsa_legacy.go @@ -6,18 +6,25 @@ package ecdsa import ( "errors" - "github.com/runZeroInc/excrypto/crypto/elliptic" "io" "math/big" + "math/rand/v2" + + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" - "github.com/runZeroInc/excrypto/x/crypto/cryptobyte" - "github.com/runZeroInc/excrypto/x/crypto/cryptobyte/asn1" + "golang.org/x/crypto/cryptobyte" + "golang.org/x/crypto/cryptobyte/asn1" ) // This file contains a math/big implementation of ECDSA that is only used for // deprecated custom curves. func generateLegacy(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") + } + k, err := randFieldElement(c, rand) if err != nil { return nil, err @@ -75,8 +82,25 @@ func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err err } func signLegacy(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { + if fips140only.Enabled { + return nil, errors.New("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") + } + c := priv.Curve + // A cheap version of hedged signatures, for the deprecated path. + var seed [32]byte + if _, err := io.ReadFull(csprng, seed[:]); err != nil { + return nil, err + } + for i, b := range priv.D.Bytes() { + seed[i%32] ^= b + } + for i, b := range hash { + seed[i%32] ^= b + } + csprng = rand.NewChaCha8(seed) + // SEC 1, Version 2.0, Section 4.1.3 N := c.Params().N if N.Sign() == 0 { @@ -130,6 +154,10 @@ func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool { } func verifyLegacy(pub *PublicKey, hash []byte, sig []byte) bool { + if fips140only.Enabled { + panic("crypto/ecdsa: use of custom curves is not allowed in FIPS 140-only mode") + } + rBytes, sBytes, err := parseSignature(sig) if err != nil { return false @@ -171,9 +199,6 @@ var one = new(big.Int).SetInt64(1) // randFieldElement returns a random element of the order of the given // curve using the procedure given in FIPS 186-4, Appendix B.5.2. func randFieldElement(c elliptic.Curve, rand io.Reader) (k *big.Int, err error) { - // See randomPoint for notes on the algorithm. This has to match, or s390x - // signatures will come out different from other architectures, which will - // break TLS recorded tests. for { N := c.Params().N b := make([]byte, (N.BitLen()+7)/8) diff --git a/crypto/ecdsa/ecdsa_noasm.go b/crypto/ecdsa/ecdsa_noasm.go deleted file mode 100644 index e2fa8082f68..00000000000 --- a/crypto/ecdsa/ecdsa_noasm.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !s390x || purego - -package ecdsa - -import "io" - -func verifyAsm(pub *PublicKey, hash []byte, sig []byte) error { - return errNoAsm -} - -func signAsm(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { - return nil, errNoAsm -} diff --git a/crypto/ecdsa/ecdsa_s390x_test.go b/crypto/ecdsa/ecdsa_s390x_test.go deleted file mode 100644 index f8fbff152ae..00000000000 --- a/crypto/ecdsa/ecdsa_s390x_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build s390x && !purego - -package ecdsa - -import ( - "github.com/runZeroInc/excrypto/crypto/elliptic" - "testing" -) - -func TestNoAsm(t *testing.T) { - testingDisableKDSA = true - defer func() { testingDisableKDSA = false }() - - curves := [...]elliptic.Curve{ - elliptic.P256(), - elliptic.P384(), - elliptic.P521(), - } - - for _, curve := range curves { - name := curve.Params().Name - t.Run(name, func(t *testing.T) { testKeyGeneration(t, curve) }) - t.Run(name, func(t *testing.T) { testSignAndVerify(t, curve) }) - t.Run(name, func(t *testing.T) { testNonceSafety(t, curve) }) - t.Run(name, func(t *testing.T) { testINDCCA(t, curve) }) - t.Run(name, func(t *testing.T) { testNegativeInputs(t, curve) }) - } -} diff --git a/crypto/ecdsa/ecdsa_test.go b/crypto/ecdsa/ecdsa_test.go index 25a20668fb8..ec6ad3ac161 100644 --- a/crypto/ecdsa/ecdsa_test.go +++ b/crypto/ecdsa/ecdsa_test.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "compress/bzip2" + "crypto" "encoding/hex" "hash" "io" @@ -19,7 +20,7 @@ import ( "crypto/rand" "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/crypto/internal/bigmod" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" "github.com/runZeroInc/excrypto/crypto/sha1" "github.com/runZeroInc/excrypto/crypto/sha256" "github.com/runZeroInc/excrypto/crypto/sha512" @@ -41,9 +42,11 @@ func testAllCurves(t *testing.T, f func(*testing.T, elliptic.Curve)) { } for _, test := range tests { curve := test.curve - t.Run(test.name, func(t *testing.T) { - t.Parallel() - f(t, curve) + cryptotest.TestAllImplementations(t, "ecdsa", func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + f(t, curve) + }) }) } } @@ -150,6 +153,15 @@ func testNonceSafety(t *testing.T, c elliptic.Curve) { } } +type readerFunc func([]byte) (int, error) + +func (f readerFunc) Read(b []byte) (int, error) { return f(b) } + +var zeroReader = readerFunc(func(b []byte) (int, error) { + clear(b) + return len(b), nil +}) + func TestINDCCA(t *testing.T) { testAllCurves(t, testINDCCA) } @@ -188,6 +200,10 @@ func fromHex(s string) *big.Int { } func TestVectors(t *testing.T) { + cryptotest.TestAllImplementations(t, "ecdsa", testVectors) +} + +func testVectors(t *testing.T) { // This test runs the full set of NIST test vectors from // https://csrc.nist.gov/groups/STM/cavp/documents/dss/186-3ecdsatestvectors.zip // @@ -341,80 +357,6 @@ func testZeroHashSignature(t *testing.T, curve elliptic.Curve) { } } -func TestRandomPoint(t *testing.T) { - t.Run("P-224", func(t *testing.T) { testRandomPoint(t, p224()) }) - t.Run("P-256", func(t *testing.T) { testRandomPoint(t, p256()) }) - t.Run("P-384", func(t *testing.T) { testRandomPoint(t, p384()) }) - t.Run("P-521", func(t *testing.T) { testRandomPoint(t, p521()) }) -} - -func testRandomPoint[Point nistPoint[Point]](t *testing.T, c *nistCurve[Point]) { - t.Cleanup(func() { testingOnlyRejectionSamplingLooped = nil }) - var loopCount int - testingOnlyRejectionSamplingLooped = func() { loopCount++ } - - // A sequence of all ones will generate 2^N-1, which should be rejected. - // (Unless, for example, we are masking too many bits.) - r := io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0xff}, 100)), rand.Reader) - if k, p, err := randomPoint(c, r); err != nil { - t.Fatal(err) - } else if k.IsZero() == 1 { - t.Error("k is zero") - } else if p.Bytes()[0] != 4 { - t.Error("p is infinity") - } - if loopCount == 0 { - t.Error("overflow was not rejected") - } - loopCount = 0 - - // A sequence of all zeroes will generate zero, which should be rejected. - r = io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0}, 100)), rand.Reader) - if k, p, err := randomPoint(c, r); err != nil { - t.Fatal(err) - } else if k.IsZero() == 1 { - t.Error("k is zero") - } else if p.Bytes()[0] != 4 { - t.Error("p is infinity") - } - if loopCount == 0 { - t.Error("zero was not rejected") - } - loopCount = 0 - - // P-256 has a 2⁻³² chance or randomly hitting a rejection. For P-224 it's - // 2⁻¹¹², for P-384 it's 2⁻¹⁹⁴, and for P-521 it's 2⁻²⁶², so if we hit in - // tests, something is horribly wrong. (For example, we are masking the - // wrong bits.) - if c.curve == elliptic.P256() { - return - } - if k, p, err := randomPoint(c, rand.Reader); err != nil { - t.Fatal(err) - } else if k.IsZero() == 1 { - t.Error("k is zero") - } else if p.Bytes()[0] != 4 { - t.Error("p is infinity") - } - if loopCount > 0 { - t.Error("unexpected rejection") - } -} - -func TestHashToNat(t *testing.T) { - t.Run("P-224", func(t *testing.T) { testHashToNat(t, p224()) }) - t.Run("P-256", func(t *testing.T) { testHashToNat(t, p256()) }) - t.Run("P-384", func(t *testing.T) { testHashToNat(t, p384()) }) - t.Run("P-521", func(t *testing.T) { testHashToNat(t, p521()) }) -} - -func testHashToNat[Point nistPoint[Point]](t *testing.T, c *nistCurve[Point]) { - for l := 0; l < 600; l++ { - h := bytes.Repeat([]byte{0xff}, l) - hashToNat(c, bigmod.NewNat(), h) - } -} - func TestZeroSignature(t *testing.T) { testAllCurves(t, testZeroSignature) } @@ -496,22 +438,113 @@ func testRMinusNSignature(t *testing.T, curve elliptic.Curve) { } } -func randomPointForCurve(curve elliptic.Curve, rand io.Reader) error { - switch curve.Params() { - case elliptic.P224().Params(): - _, _, err := randomPoint(p224(), rand) - return err - case elliptic.P256().Params(): - _, _, err := randomPoint(p256(), rand) - return err - case elliptic.P384().Params(): - _, _, err := randomPoint(p384(), rand) - return err - case elliptic.P521().Params(): - _, _, err := randomPoint(p521(), rand) - return err - default: - panic("unknown curve") +func TestRFC6979(t *testing.T) { + t.Run("P-224", func(t *testing.T) { + testRFC6979(t, elliptic.P224(), + "F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1", + "00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C", + "EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A", + "sample", + "61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA", + "BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101") + testRFC6979(t, elliptic.P224(), + "F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1", + "00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C", + "EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A", + "test", + "AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6", + "178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD") + }) + t.Run("P-256", func(t *testing.T) { + // This vector was bruteforced to find a message that causes the + // generation of k to loop. It was checked against + // github.com/codahale/rfc6979 (https://go.dev/play/p/FK5-fmKf7eK), + // OpenSSL 3.2.0 (https://github.com/openssl/openssl/pull/23130), + // and python-ecdsa: + // + // ecdsa.keys.SigningKey.from_secret_exponent( + // 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721, + // ecdsa.curves.curve_by_name("NIST256p"), hashlib.sha256).sign_deterministic( + // b"wv[vnX", hashlib.sha256, lambda r, s, order: print(hex(r), hex(s))) + // + testRFC6979(t, elliptic.P256(), + "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721", + "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6", + "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299", + "wv[vnX", + "EFD9073B652E76DA1B5A019C0E4A2E3FA529B035A6ABB91EF67F0ED7A1F21234", + "3DB4706C9D9F4A4FE13BB5E08EF0FAB53A57DBAB2061C83A35FA411C68D2BA33") + + // The remaining vectors are from RFC 6979. + testRFC6979(t, elliptic.P256(), + "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721", + "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6", + "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299", + "sample", + "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716", + "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8") + testRFC6979(t, elliptic.P256(), + "C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721", + "60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6", + "7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299", + "test", + "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367", + "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083") + }) + t.Run("P-384", func(t *testing.T) { + testRFC6979(t, elliptic.P384(), + "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5", + "EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13", + "8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720", + "sample", + "21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD", + "F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0") + testRFC6979(t, elliptic.P384(), + "6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5", + "EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13", + "8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720", + "test", + "6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B", + "2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265") + }) + t.Run("P-521", func(t *testing.T) { + testRFC6979(t, elliptic.P521(), + "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", + "1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4", + "0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5", + "sample", + "1511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7", + "04A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC") + testRFC6979(t, elliptic.P521(), + "0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538", + "1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4", + "0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5", + "test", + "00E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8", + "0CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86") + }) +} + +func testRFC6979(t *testing.T, curve elliptic.Curve, D, X, Y, msg, r, s string) { + priv := &PrivateKey{ + D: fromHex(D), + PublicKey: PublicKey{ + Curve: curve, + X: fromHex(X), + Y: fromHex(Y), + }, + } + h := sha256.Sum256([]byte(msg)) + sig, err := priv.Sign(nil, h[:], crypto.SHA256) + if err != nil { + t.Fatal(err) + } + expected, err := encodeSignature(fromHex(r).Bytes(), fromHex(s).Bytes()) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig, expected) { + t.Errorf("signature mismatch:\n got: %x\nwant: %x", sig, expected) } } diff --git a/crypto/ed25519/ed25519.go b/crypto/ed25519/ed25519.go index eaa8b3fb011..00fbb6b7f86 100644 --- a/crypto/ed25519/ed25519.go +++ b/crypto/ed25519/ed25519.go @@ -16,16 +16,14 @@ package ed25519 import ( - "bytes" + "crypto" + cryptorand "crypto/rand" "errors" "io" "strconv" - cryptorand "crypto/rand" - - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/crypto/internal/edwards25519" - "github.com/runZeroInc/excrypto/crypto/sha512" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ed25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -78,7 +76,7 @@ func (priv PrivateKey) Equal(x crypto.PrivateKey) bool { // interoperability with RFC 8032. RFC 8032's private keys correspond to seeds // in this package. func (priv PrivateKey) Seed() []byte { - return bytes.Clone(priv[:SeedSize]) + return append(make([]byte, 0, SeedSize), priv[:SeedSize]...) } // Sign signs the given message with priv. rand is ignored and can be nil. @@ -91,6 +89,13 @@ func (priv PrivateKey) Seed() []byte { // A value of type [Options] can be used as opts, or crypto.Hash(0) or // crypto.SHA512 directly to select plain Ed25519 or Ed25519ph, respectively. func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOpts) (signature []byte, err error) { + // NewPrivateKey is very slow in FIPS mode because it performs a + // Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache + // it or attach it to the PrivateKey. + k, err := ed25519.NewPrivateKey(priv) + if err != nil { + return nil, err + } hash := opts.HashFunc() context := "" if opts, ok := opts.(*Options); ok { @@ -98,24 +103,14 @@ func (priv PrivateKey) Sign(rand io.Reader, message []byte, opts crypto.SignerOp } switch { case hash == crypto.SHA512: // Ed25519ph - if l := len(message); l != sha512.Size { - return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) - } - if l := len(context); l > 255 { - return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) - } - signature := make([]byte, SignatureSize) - sign(signature, priv, message, domPrefixPh, context) - return signature, nil + return ed25519.SignPH(k, message, context) case hash == crypto.Hash(0) && context != "": // Ed25519ctx - if l := len(context); l > 255 { - return nil, errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l)) + if fips140only.Enabled { + return nil, errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode") } - signature := make([]byte, SignatureSize) - sign(signature, priv, message, domPrefixCtx, context) - return signature, nil + return ed25519.SignCtx(k, message, context) case hash == crypto.Hash(0): // Ed25519 - return Sign(priv, message), nil + return ed25519.Sign(k, message), nil default: return nil, errors.New("ed25519: expected opts.HashFunc() zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") } @@ -151,9 +146,7 @@ func GenerateKey(rand io.Reader) (PublicKey, PrivateKey, error) { } privateKey := NewKeyFromSeed(seed) - publicKey := make([]byte, PublicKeySize) - copy(publicKey, privateKey[32:]) - + publicKey := privateKey.Public().(PublicKey) return publicKey, privateKey, nil } @@ -169,21 +162,12 @@ func NewKeyFromSeed(seed []byte) PrivateKey { } func newKeyFromSeed(privateKey, seed []byte) { - if l := len(seed); l != SeedSize { - panic("ed25519: bad seed length: " + strconv.Itoa(l)) - } - - h := sha512.Sum512(seed) - s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32]) + k, err := ed25519.NewPrivateKeyFromSeed(seed) if err != nil { - panic("ed25519: internal error: setting scalar failed") + // NewPrivateKeyFromSeed only returns an error if the seed length is incorrect. + panic("ed25519: bad seed length: " + strconv.Itoa(len(seed))) } - A := (&edwards25519.Point{}).ScalarBaseMult(s) - - publicKey := A.Bytes() - - copy(privateKey, seed) - copy(privateKey[32:], publicKey) + copy(privateKey, k.Bytes()) } // Sign signs the message with privateKey and returns a signature. It will @@ -192,73 +176,20 @@ func Sign(privateKey PrivateKey, message []byte) []byte { // Outline the function body so that the returned signature can be // stack-allocated. signature := make([]byte, SignatureSize) - sign(signature, privateKey, message, domPrefixPure, "") + sign(signature, privateKey, message) return signature } -// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph/Ed25519ctx. -// See RFC 8032, Section 2 and Section 5.1. -const ( - // domPrefixPure is empty for pure Ed25519. - domPrefixPure = "" - // domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the - // uint8-length prefixed context. - domPrefixPh = "SigEd25519 no Ed25519 collisions\x01" - // domPrefixCtx is dom2(phflag=0) for Ed25519ctx. It must be followed by the - // uint8-length prefixed context. - domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00" -) - -func sign(signature, privateKey, message []byte, domPrefix, context string) { - if l := len(privateKey); l != PrivateKeySize { - panic("ed25519: bad private key length: " + strconv.Itoa(l)) - } - seed, publicKey := privateKey[:SeedSize], privateKey[SeedSize:] - - h := sha512.Sum512(seed) - s, err := edwards25519.NewScalar().SetBytesWithClamping(h[:32]) - if err != nil { - panic("ed25519: internal error: setting scalar failed") - } - prefix := h[32:] - - mh := sha512.New() - if domPrefix != domPrefixPure { - mh.Write([]byte(domPrefix)) - mh.Write([]byte{byte(len(context))}) - mh.Write([]byte(context)) - } - mh.Write(prefix) - mh.Write(message) - messageDigest := make([]byte, 0, sha512.Size) - messageDigest = mh.Sum(messageDigest) - r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest) +func sign(signature []byte, privateKey PrivateKey, message []byte) { + // NewPrivateKey is very slow in FIPS mode because it performs a + // Sign+Verify cycle per FIPS 140-3 IG 10.3.A. We should find a way to cache + // it or attach it to the PrivateKey. + k, err := ed25519.NewPrivateKey(privateKey) if err != nil { - panic("ed25519: internal error: setting scalar failed") + panic("ed25519: bad private key: " + err.Error()) } - - R := (&edwards25519.Point{}).ScalarBaseMult(r) - - kh := sha512.New() - if domPrefix != domPrefixPure { - kh.Write([]byte(domPrefix)) - kh.Write([]byte{byte(len(context))}) - kh.Write([]byte(context)) - } - kh.Write(R.Bytes()) - kh.Write(publicKey) - kh.Write(message) - hramDigest := make([]byte, 0, sha512.Size) - hramDigest = kh.Sum(hramDigest) - k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) - if err != nil { - panic("ed25519: internal error: setting scalar failed") - } - - S := edwards25519.NewScalar().MultiplyAdd(k, s, r) - - copy(signature[:32], R.Bytes()) - copy(signature[32:], S.Bytes()) + sig := ed25519.Sign(k, message) + copy(signature, sig) } // Verify reports whether sig is a valid signature of message by publicKey. It @@ -267,7 +198,7 @@ func sign(signature, privateKey, message []byte, domPrefix, context string) { // The inputs are not considered confidential, and may leak through timing side // channels, or if an attacker has control of part of the inputs. func Verify(publicKey PublicKey, message, sig []byte) bool { - return verify(publicKey, message, sig, domPrefixPure, "") + return VerifyWithOptions(publicKey, message, sig, &Options{Hash: crypto.Hash(0)}) == nil } // VerifyWithOptions reports whether sig is a valid signature of message by @@ -282,74 +213,24 @@ func Verify(publicKey PublicKey, message, sig []byte) bool { // The inputs are not considered confidential, and may leak through timing side // channels, or if an attacker has control of part of the inputs. func VerifyWithOptions(publicKey PublicKey, message, sig []byte, opts *Options) error { + if l := len(publicKey); l != PublicKeySize { + panic("ed25519: bad public key length: " + strconv.Itoa(l)) + } + k, err := ed25519.NewPublicKey(publicKey) + if err != nil { + return err + } switch { case opts.Hash == crypto.SHA512: // Ed25519ph - if l := len(message); l != sha512.Size { - return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) - } - if l := len(opts.Context); l > 255 { - return errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) - } - if !verify(publicKey, message, sig, domPrefixPh, opts.Context) { - return errors.New("ed25519: invalid signature") - } - return nil + return ed25519.VerifyPH(k, message, sig, opts.Context) case opts.Hash == crypto.Hash(0) && opts.Context != "": // Ed25519ctx - if l := len(opts.Context); l > 255 { - return errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l)) + if fips140only.Enabled { + return errors.New("crypto/ed25519: use of Ed25519ctx is not allowed in FIPS 140-only mode") } - if !verify(publicKey, message, sig, domPrefixCtx, opts.Context) { - return errors.New("ed25519: invalid signature") - } - return nil + return ed25519.VerifyCtx(k, message, sig, opts.Context) case opts.Hash == crypto.Hash(0): // Ed25519 - if !verify(publicKey, message, sig, domPrefixPure, "") { - return errors.New("ed25519: invalid signature") - } - return nil + return ed25519.Verify(k, message, sig) default: return errors.New("ed25519: expected opts.Hash zero (unhashed message, for standard Ed25519) or SHA-512 (for Ed25519ph)") } } - -func verify(publicKey PublicKey, message, sig []byte, domPrefix, context string) bool { - if l := len(publicKey); l != PublicKeySize { - panic("ed25519: bad public key length: " + strconv.Itoa(l)) - } - - if len(sig) != SignatureSize || sig[63]&224 != 0 { - return false - } - - A, err := (&edwards25519.Point{}).SetBytes(publicKey) - if err != nil { - return false - } - - kh := sha512.New() - if domPrefix != domPrefixPure { - kh.Write([]byte(domPrefix)) - kh.Write([]byte{byte(len(context))}) - kh.Write([]byte(context)) - } - kh.Write(sig[:32]) - kh.Write(publicKey) - kh.Write(message) - hramDigest := make([]byte, 0, sha512.Size) - hramDigest = kh.Sum(hramDigest) - k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) - if err != nil { - panic("ed25519: internal error: setting scalar failed") - } - - S, err := edwards25519.NewScalar().SetCanonicalBytes(sig[32:]) - if err != nil { - return false - } - - // [S]B = R + [k]A --> [k](-A) + [S]B = R - minusA := (&edwards25519.Point{}).Negate(A) - R := (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(k, minusA, S) - - return bytes.Equal(sig[:32], R.Bytes()) -} diff --git a/crypto/ed25519/ed25519_test.go b/crypto/ed25519/ed25519_test.go index b47e39cdb57..559e729f265 100644 --- a/crypto/ed25519/ed25519_test.go +++ b/crypto/ed25519/ed25519_test.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "compress/gzip" + "crypto" "encoding/hex" "log" "os" @@ -16,10 +17,8 @@ import ( "crypto/rand" - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" "github.com/runZeroInc/excrypto/crypto/sha512" - "github.com/runZeroInc/excrypto/internal/testenv" ) func Example_ed25519ctx() { @@ -44,6 +43,56 @@ func Example_ed25519ctx() { } } +func TestGenerateKey(t *testing.T) { + // nil is like using crypto/rand.Reader. + public, private, err := GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + + if len(public) != PublicKeySize { + t.Errorf("public key has the wrong size: %d", len(public)) + } + if len(private) != PrivateKeySize { + t.Errorf("private key has the wrong size: %d", len(private)) + } + if !bytes.Equal(private.Public().(PublicKey), public) { + t.Errorf("public key doesn't match private key") + } + fromSeed := NewKeyFromSeed(private.Seed()) + if !bytes.Equal(private, fromSeed) { + t.Errorf("recreating key pair from seed gave different private key") + } + + _, k2, err := GenerateKey(nil) + if err != nil { + t.Fatal(err) + } + if bytes.Equal(private, k2) { + t.Errorf("GenerateKey returned the same private key twice") + } + + _, k3, err := GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + if bytes.Equal(private, k3) { + t.Errorf("GenerateKey returned the same private key twice") + } + + // GenerateKey is documented to be the same as NewKeyFromSeed. + seed := make([]byte, SeedSize) + rand.Read(seed) + _, k4, err := GenerateKey(bytes.NewReader(seed)) + if err != nil { + t.Fatal(err) + } + k4n := NewKeyFromSeed(seed) + if !bytes.Equal(k4, k4n) { + t.Errorf("GenerateKey with seed gave different private key") + } +} + type zeroReader struct{} func (zeroReader) Read(buf []byte) (int, error) { @@ -321,11 +370,7 @@ func TestMalleability(t *testing.T) { } func TestAllocations(t *testing.T) { - if boring.Enabled { - t.Skip("skipping allocations test with BoringCrypto") - } - testenv.SkipIfOptimizationOff(t) - + cryptotest.SkipTestAllocations(t) if allocs := testing.AllocsPerRun(100, func() { seed := make([]byte, SeedSize) message := []byte("Hello, world!") diff --git a/crypto/ed25519/ed25519vectors_test.go b/crypto/ed25519/ed25519vectors_test.go index ea07118dc8d..f41f9a0697b 100644 --- a/crypto/ed25519/ed25519vectors_test.go +++ b/crypto/ed25519/ed25519vectors_test.go @@ -7,12 +7,12 @@ package ed25519_test import ( "encoding/hex" "encoding/json" - "github.com/runZeroInc/excrypto/crypto/ed25519" - "github.com/runZeroInc/excrypto/internal/testenv" "os" - "os/exec" "path/filepath" "testing" + + "github.com/runZeroInc/excrypto/crypto/ed25519" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" ) // TestEd25519Vectors runs a very large set of test vectors that exercise all @@ -72,38 +72,13 @@ func TestEd25519Vectors(t *testing.T) { } func downloadEd25519Vectors(t *testing.T) []byte { - testenv.MustHaveExternalNetwork(t) - - // Create a temp dir and modcache subdir. - d := t.TempDir() - // Create a spot for the modcache. - modcache := filepath.Join(d, "modcache") - if err := os.Mkdir(modcache, 0777); err != nil { - t.Fatal(err) - } - - t.Setenv("GO111MODULE", "on") - t.Setenv("GOMODCACHE", modcache) - // Download the JSON test file from the GOPROXY with `go mod download`, // pinning the version so test and module caching works as expected. - goTool := testenv.GoToolPath(t) - path := "filippo.io/mostly-harmless/ed25519vectors@v0.0.0-20210322192420-30a2d7243a94" - cmd := exec.Command(goTool, "mod", "download", "-modcacherw", "-json", path) - // TODO: enable the sumdb once the TryBots proxy supports it. - cmd.Env = append(os.Environ(), "GONOSUMDB=*") - output, err := cmd.Output() - if err != nil { - t.Fatalf("failed to run `go mod download -json %s`, output: %s", path, output) - } - var dm struct { - Dir string // absolute path to cached source root directory - } - if err := json.Unmarshal(output, &dm); err != nil { - t.Fatal(err) - } + path := "filippo.io/mostly-harmless/ed25519vectors" + version := "v0.0.0-20210322192420-30a2d7243a94" + dir := cryptotest.FetchModule(t, path, version) - jsonVectors, err := os.ReadFile(filepath.Join(dm.Dir, "ed25519vectors.json")) + jsonVectors, err := os.ReadFile(filepath.Join(dir, "ed25519vectors.json")) if err != nil { t.Fatalf("failed to read ed25519vectors.json: %v", err) } diff --git a/crypto/elliptic/nistec.go b/crypto/elliptic/nistec.go index c9aee374597..c3735997f1a 100644 --- a/crypto/elliptic/nistec.go +++ b/crypto/elliptic/nistec.go @@ -8,7 +8,7 @@ import ( "errors" "math/big" - "github.com/runZeroInc/excrypto/crypto/internal/nistec" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" ) var p224 = &nistCurve[*nistec.P224Point]{ @@ -19,7 +19,7 @@ func initP224() { p224.params = &CurveParams{ Name: "P-224", BitSize: 224, - // FIPS 186-4, section D.1.2.2 + // SP 800-186, Section 3.2.1.2 P: bigFromDecimal("26959946667150639794667015087019630673557916260026308143510066298881"), N: bigFromDecimal("26959946667150639794667015087019625940457807714424391721682722368061"), B: bigFromHex("b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4"), @@ -40,7 +40,7 @@ func initP256() { p256.params = &CurveParams{ Name: "P-256", BitSize: 256, - // FIPS 186-4, section D.1.2.3 + // SP 800-186, Section 3.2.1.3 P: bigFromDecimal("115792089210356248762697446949407573530086143415290314195533631308867097853951"), N: bigFromDecimal("115792089210356248762697446949407573529996955224135760342422259061068512044369"), B: bigFromHex("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b"), @@ -57,7 +57,7 @@ func initP384() { p384.params = &CurveParams{ Name: "P-384", BitSize: 384, - // FIPS 186-4, section D.1.2.4 + // SP 800-186, Section 3.2.1.4 P: bigFromDecimal("394020061963944792122790401001436138050797392704654" + "46667948293404245721771496870329047266088258938001861606973112319"), N: bigFromDecimal("394020061963944792122790401001436138050797392704654" + @@ -79,7 +79,7 @@ func initP521() { p521.params = &CurveParams{ Name: "P-521", BitSize: 521, - // FIPS 186-4, section D.1.2.5 + // SP 800-186, Section 3.2.1.5 P: bigFromDecimal("68647976601306097149819007990813932172694353001433" + "0540939446345918554318339765605212255964066145455497729631139148" + "0858037121987999716643812574028291115057151"), diff --git a/crypto/elliptic/nistec_p256.go b/crypto/elliptic/nistec_p256.go index 6a505987117..d0fe6d809fc 100644 --- a/crypto/elliptic/nistec_p256.go +++ b/crypto/elliptic/nistec_p256.go @@ -9,7 +9,7 @@ package elliptic import ( "math/big" - "github.com/runZeroInc/excrypto/crypto/internal/nistec" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" ) func (c p256Curve) Inverse(k *big.Int) *big.Int { diff --git a/crypto/fips140/fips140.go b/crypto/fips140/fips140.go new file mode 100644 index 00000000000..b2dbd92e6ec --- /dev/null +++ b/crypto/fips140/fips140.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140 + +import ( + "internal/godebug" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" +) + +var fips140GODEBUG = godebug.New("fips140") + +// Enabled reports whether the cryptography libraries are operating in FIPS +// 140-3 mode. +// +// It can be controlled at runtime using the GODEBUG setting "fips140". If set +// to "on", FIPS 140-3 mode is enabled. If set to "only", non-approved +// cryptography functions will additionally return errors or panic. +// +// This can't be changed after the program has started. +func Enabled() bool { + godebug := fips140GODEBUG.Value() + currentlyEnabled := godebug == "on" || godebug == "only" || godebug == "debug" + if currentlyEnabled != fips140.Enabled { + panic("crypto/fips140: GODEBUG setting changed after program start") + } + if fips140.Enabled && !check.Verified { + panic("crypto/fips140: FIPS 140-3 mode enabled, but integrity check didn't pass") + } + return fips140.Enabled +} diff --git a/crypto/hkdf/example_test.go b/crypto/hkdf/example_test.go new file mode 100644 index 00000000000..0c6c54c0fa4 --- /dev/null +++ b/crypto/hkdf/example_test.go @@ -0,0 +1,55 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hkdf_test + +import ( + "bytes" + "fmt" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/hkdf" + "github.com/runZeroInc/excrypto/crypto/sha256" +) + +// Usage example that expands one master secret into three other +// cryptographically secure keys. +func Example_usage() { + // Underlying hash function for HMAC. + hash := sha256.New + keyLen := hash().Size() + + // Cryptographically secure master secret. + secret := []byte{0x00, 0x01, 0x02, 0x03} // i.e. NOT this. + + // Non-secret salt, optional (can be nil). + // Recommended: hash-length random value. + salt := make([]byte, hash().Size()) + if _, err := rand.Read(salt); err != nil { + panic(err) + } + + // Non-secret context info, optional (can be nil). + info := "hkdf example" + + // Generate three 128-bit derived keys. + var keys [][]byte + for i := 0; i < 3; i++ { + key, err := hkdf.Key(hash, secret, salt, info, keyLen) + if err != nil { + panic(err) + } + keys = append(keys, key) + } + + for i := range keys { + fmt.Printf("Key #%d: %v\n", i+1, !bytes.Equal(keys[i], make([]byte, 16))) + } + + // Output: + // Key #1: true + // Key #2: true + // Key #3: true +} diff --git a/crypto/hkdf/hkdf.go b/crypto/hkdf/hkdf.go new file mode 100644 index 00000000000..cb7bf95c66f --- /dev/null +++ b/crypto/hkdf/hkdf.go @@ -0,0 +1,85 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hkdf implements the HMAC-based Extract-and-Expand Key Derivation +// Function (HKDF) as defined in RFC 5869. +// +// HKDF is a cryptographic key derivation function (KDF) with the goal of +// expanding limited input keying material into one or more cryptographically +// strong secret keys. +package hkdf + +import ( + "errors" + "hash" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + "github.com/runZeroInc/excrypto/crypto/internal/fips140hash" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" +) + +// Extract generates a pseudorandom key for use with [Expand] from an input +// secret and an optional independent salt. +// +// Only use this function if you need to reuse the extracted key with multiple +// Expand invocations and different context values. Most common scenarios, +// including the generation of multiple keys, should use [Key] instead. +func Extract[H hash.Hash](h func() H, secret, salt []byte) ([]byte, error) { + fh := fips140hash.UnwrapNew(h) + if err := checkFIPS140Only(fh, secret); err != nil { + return nil, err + } + return hkdf.Extract(fh, secret, salt), nil +} + +// Expand derives a key from the given hash, key, and optional context info, +// returning a []byte of length keyLength that can be used as cryptographic key. +// The extraction step is skipped. +// +// The key should have been generated by [Extract], or be a uniformly +// random or pseudorandom cryptographically strong key. See RFC 5869, Section +// 3.3. Most common scenarios will want to use [Key] instead. +func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error) { + fh := fips140hash.UnwrapNew(h) + if err := checkFIPS140Only(fh, pseudorandomKey); err != nil { + return nil, err + } + + limit := fh().Size() * 255 + if keyLength > limit { + return nil, errors.New("hkdf: requested key length too large") + } + + return hkdf.Expand(fh, pseudorandomKey, info, keyLength), nil +} + +// Key derives a key from the given hash, secret, salt and context info, +// returning a []byte of length keyLength that can be used as cryptographic key. +// Salt and info can be nil. +func Key[Hash hash.Hash](h func() Hash, secret, salt []byte, info string, keyLength int) ([]byte, error) { + fh := fips140hash.UnwrapNew(h) + if err := checkFIPS140Only(fh, secret); err != nil { + return nil, err + } + + limit := fh().Size() * 255 + if keyLength > limit { + return nil, errors.New("hkdf: requested key length too large") + } + + return hkdf.Key(fh, secret, salt, info, keyLength), nil +} + +func checkFIPS140Only[Hash hash.Hash](h func() Hash, key []byte) error { + if !fips140only.Enabled { + return nil + } + if len(key) < 112/8 { + return errors.New("crypto/hkdf: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") + } + if !fips140only.ApprovedHash(h()) { + return errors.New("crypto/hkdf: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + return nil +} diff --git a/crypto/hkdf/hkdf_test.go b/crypto/hkdf/hkdf_test.go new file mode 100644 index 00000000000..16f4e73973c --- /dev/null +++ b/crypto/hkdf/hkdf_test.go @@ -0,0 +1,414 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hkdf + +import ( + "bytes" + "hash" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/md5" + "github.com/runZeroInc/excrypto/crypto/sha1" + "github.com/runZeroInc/excrypto/crypto/sha256" + "github.com/runZeroInc/excrypto/crypto/sha512" +) + +type hkdfTest struct { + hash func() hash.Hash + master []byte + salt []byte + prk []byte + info []byte + out []byte +} + +var hkdfTests = []hkdfTest{ + // Tests from RFC 5869 + { + sha256.New, + []byte{ + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, + }, + []byte{ + 0x07, 0x77, 0x09, 0x36, 0x2c, 0x2e, 0x32, 0xdf, + 0x0d, 0xdc, 0x3f, 0x0d, 0xc4, 0x7b, 0xba, 0x63, + 0x90, 0xb6, 0xc7, 0x3b, 0xb5, 0x0f, 0x9c, 0x31, + 0x22, 0xec, 0x84, 0x4a, 0xd7, 0xc2, 0xb3, 0xe5, + }, + []byte{ + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, + }, + []byte{ + 0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, + 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36, 0x2f, 0x2a, + 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, + 0x5d, 0xb0, 0x2d, 0x56, 0xec, 0xc4, 0xc5, 0xbf, + 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, + 0x58, 0x65, + }, + }, + { + sha256.New, + []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + }, + []byte{ + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + }, + []byte{ + 0x06, 0xa6, 0xb8, 0x8c, 0x58, 0x53, 0x36, 0x1a, + 0x06, 0x10, 0x4c, 0x9c, 0xeb, 0x35, 0xb4, 0x5c, + 0xef, 0x76, 0x00, 0x14, 0x90, 0x46, 0x71, 0x01, + 0x4a, 0x19, 0x3f, 0x40, 0xc1, 0x5f, 0xc2, 0x44, + }, + []byte{ + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + }, + []byte{ + 0xb1, 0x1e, 0x39, 0x8d, 0xc8, 0x03, 0x27, 0xa1, + 0xc8, 0xe7, 0xf7, 0x8c, 0x59, 0x6a, 0x49, 0x34, + 0x4f, 0x01, 0x2e, 0xda, 0x2d, 0x4e, 0xfa, 0xd8, + 0xa0, 0x50, 0xcc, 0x4c, 0x19, 0xaf, 0xa9, 0x7c, + 0x59, 0x04, 0x5a, 0x99, 0xca, 0xc7, 0x82, 0x72, + 0x71, 0xcb, 0x41, 0xc6, 0x5e, 0x59, 0x0e, 0x09, + 0xda, 0x32, 0x75, 0x60, 0x0c, 0x2f, 0x09, 0xb8, + 0x36, 0x77, 0x93, 0xa9, 0xac, 0xa3, 0xdb, 0x71, + 0xcc, 0x30, 0xc5, 0x81, 0x79, 0xec, 0x3e, 0x87, + 0xc1, 0x4c, 0x01, 0xd5, 0xc1, 0xf3, 0x43, 0x4f, + 0x1d, 0x87, + }, + }, + { + sha256.New, + []byte{ + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + []byte{}, + []byte{ + 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, + 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf, + 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, + 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04, + }, + []byte{}, + []byte{ + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, + 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, + 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, + 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, + 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, + 0x96, 0xc8, + }, + }, + { + sha256.New, + []byte{ + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + nil, + []byte{ + 0x19, 0xef, 0x24, 0xa3, 0x2c, 0x71, 0x7b, 0x16, + 0x7f, 0x33, 0xa9, 0x1d, 0x6f, 0x64, 0x8b, 0xdf, + 0x96, 0x59, 0x67, 0x76, 0xaf, 0xdb, 0x63, 0x77, + 0xac, 0x43, 0x4c, 0x1c, 0x29, 0x3c, 0xcb, 0x04, + }, + nil, + []byte{ + 0x8d, 0xa4, 0xe7, 0x75, 0xa5, 0x63, 0xc1, 0x8f, + 0x71, 0x5f, 0x80, 0x2a, 0x06, 0x3c, 0x5a, 0x31, + 0xb8, 0xa1, 0x1f, 0x5c, 0x5e, 0xe1, 0x87, 0x9e, + 0xc3, 0x45, 0x4e, 0x5f, 0x3c, 0x73, 0x8d, 0x2d, + 0x9d, 0x20, 0x13, 0x95, 0xfa, 0xa4, 0xb6, 0x1a, + 0x96, 0xc8, + }, + }, + { + sha1.New, + []byte{ + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, + }, + []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, + }, + []byte{ + 0x9b, 0x6c, 0x18, 0xc4, 0x32, 0xa7, 0xbf, 0x8f, + 0x0e, 0x71, 0xc8, 0xeb, 0x88, 0xf4, 0xb3, 0x0b, + 0xaa, 0x2b, 0xa2, 0x43, + }, + []byte{ + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, + }, + []byte{ + 0x08, 0x5a, 0x01, 0xea, 0x1b, 0x10, 0xf3, 0x69, + 0x33, 0x06, 0x8b, 0x56, 0xef, 0xa5, 0xad, 0x81, + 0xa4, 0xf1, 0x4b, 0x82, 0x2f, 0x5b, 0x09, 0x15, + 0x68, 0xa9, 0xcd, 0xd4, 0xf1, 0x55, 0xfd, 0xa2, + 0xc2, 0x2e, 0x42, 0x24, 0x78, 0xd3, 0x05, 0xf3, + 0xf8, 0x96, + }, + }, + { + sha1.New, + []byte{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + }, + []byte{ + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + }, + []byte{ + 0x8a, 0xda, 0xe0, 0x9a, 0x2a, 0x30, 0x70, 0x59, + 0x47, 0x8d, 0x30, 0x9b, 0x26, 0xc4, 0x11, 0x5a, + 0x22, 0x4c, 0xfa, 0xf6, + }, + []byte{ + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, + }, + []byte{ + 0x0b, 0xd7, 0x70, 0xa7, 0x4d, 0x11, 0x60, 0xf7, + 0xc9, 0xf1, 0x2c, 0xd5, 0x91, 0x2a, 0x06, 0xeb, + 0xff, 0x6a, 0xdc, 0xae, 0x89, 0x9d, 0x92, 0x19, + 0x1f, 0xe4, 0x30, 0x56, 0x73, 0xba, 0x2f, 0xfe, + 0x8f, 0xa3, 0xf1, 0xa4, 0xe5, 0xad, 0x79, 0xf3, + 0xf3, 0x34, 0xb3, 0xb2, 0x02, 0xb2, 0x17, 0x3c, + 0x48, 0x6e, 0xa3, 0x7c, 0xe3, 0xd3, 0x97, 0xed, + 0x03, 0x4c, 0x7f, 0x9d, 0xfe, 0xb1, 0x5c, 0x5e, + 0x92, 0x73, 0x36, 0xd0, 0x44, 0x1f, 0x4c, 0x43, + 0x00, 0xe2, 0xcf, 0xf0, 0xd0, 0x90, 0x0b, 0x52, + 0xd3, 0xb4, + }, + }, + { + sha1.New, + []byte{ + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + }, + []byte{}, + []byte{ + 0xda, 0x8c, 0x8a, 0x73, 0xc7, 0xfa, 0x77, 0x28, + 0x8e, 0xc6, 0xf5, 0xe7, 0xc2, 0x97, 0x78, 0x6a, + 0xa0, 0xd3, 0x2d, 0x01, + }, + []byte{}, + []byte{ + 0x0a, 0xc1, 0xaf, 0x70, 0x02, 0xb3, 0xd7, 0x61, + 0xd1, 0xe5, 0x52, 0x98, 0xda, 0x9d, 0x05, 0x06, + 0xb9, 0xae, 0x52, 0x05, 0x72, 0x20, 0xa3, 0x06, + 0xe0, 0x7b, 0x6b, 0x87, 0xe8, 0xdf, 0x21, 0xd0, + 0xea, 0x00, 0x03, 0x3d, 0xe0, 0x39, 0x84, 0xd3, + 0x49, 0x18, + }, + }, + { + sha1.New, + []byte{ + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + }, + nil, + []byte{ + 0x2a, 0xdc, 0xca, 0xda, 0x18, 0x77, 0x9e, 0x7c, + 0x20, 0x77, 0xad, 0x2e, 0xb1, 0x9d, 0x3f, 0x3e, + 0x73, 0x13, 0x85, 0xdd, + }, + nil, + []byte{ + 0x2c, 0x91, 0x11, 0x72, 0x04, 0xd7, 0x45, 0xf3, + 0x50, 0x0d, 0x63, 0x6a, 0x62, 0xf6, 0x4f, 0x0a, + 0xb3, 0xba, 0xe5, 0x48, 0xaa, 0x53, 0xd4, 0x23, + 0xb0, 0xd1, 0xf2, 0x7e, 0xbb, 0xa6, 0xf5, 0xe5, + 0x67, 0x3a, 0x08, 0x1d, 0x70, 0xcc, 0xe7, 0xac, + 0xfc, 0x48, + }, + }, +} + +func TestHKDF(t *testing.T) { + for i, tt := range hkdfTests { + prk, err := Extract(tt.hash, tt.master, tt.salt) + if err != nil { + t.Errorf("test %d: PRK extraction failed: %v", i, err) + } + if !bytes.Equal(prk, tt.prk) { + t.Errorf("test %d: incorrect PRK: have %v, need %v.", i, prk, tt.prk) + } + + key, err := Key(tt.hash, tt.master, tt.salt, string(tt.info), len(tt.out)) + if err != nil { + t.Errorf("test %d: key derivation failed: %v", i, err) + } + + if !bytes.Equal(key, tt.out) { + t.Errorf("test %d: incorrect output: have %v, need %v.", i, key, tt.out) + } + + expanded, err := Expand(tt.hash, prk, string(tt.info), len(tt.out)) + if err != nil { + t.Errorf("test %d: key expansion failed: %v", i, err) + } + + if !bytes.Equal(expanded, tt.out) { + t.Errorf("test %d: incorrect output from Expand: have %v, need %v.", i, expanded, tt.out) + } + } +} + +func TestHKDFLimit(t *testing.T) { + hash := sha1.New + master := []byte{0x00, 0x01, 0x02, 0x03} + info := "" + limit := hash().Size() * 255 + + // The maximum output bytes should be extractable + out, err := Key(hash, master, nil, info, limit) + if err != nil || len(out) != limit { + t.Errorf("key derivation failed: %v", err) + } + + // Reading one more should return an error + _, err = Key(hash, master, nil, info, limit+1) + if err == nil { + t.Error("expected key derivation to fail, but it succeeded") + } +} + +func Benchmark16ByteMD5Single(b *testing.B) { + benchmarkHKDF(md5.New, 16, b) +} + +func Benchmark20ByteSHA1Single(b *testing.B) { + benchmarkHKDF(sha1.New, 20, b) +} + +func Benchmark32ByteSHA256Single(b *testing.B) { + benchmarkHKDF(sha256.New, 32, b) +} + +func Benchmark64ByteSHA512Single(b *testing.B) { + benchmarkHKDF(sha512.New, 64, b) +} + +func benchmarkHKDF(hasher func() hash.Hash, block int, b *testing.B) { + master := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07} + salt := []byte{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17} + info := string([]byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}) + + b.SetBytes(int64(block)) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := Key(hasher, master, salt, info, hasher().Size()) + if err != nil { + b.Errorf("failed to derive key: %v", err) + } + } +} + +func TestFIPSServiceIndicator(t *testing.T) { + if boring.Enabled { + t.Skip("in BoringCrypto mode HMAC is not from the Go FIPS module") + } + + fips140.ResetServiceIndicator() + _, err := Key(sha256.New, []byte("YELLOW SUBMARINE"), nil, "", 32) + if err != nil { + panic(err) + } + if !fips140.ServiceIndicator() { + t.Error("FIPS service indicator should be set") + } + + // Key too short. + fips140.ResetServiceIndicator() + _, err = Key(sha256.New, []byte("key"), nil, "", 32) + if err != nil { + panic(err) + } + if fips140.ServiceIndicator() { + t.Error("FIPS service indicator should not be set") + } + + // Salt and info are short, which is ok, but translates to a short HMAC key. + fips140.ResetServiceIndicator() + _, err = Key(sha256.New, []byte("YELLOW SUBMARINE"), []byte("salt"), "info", 32) + if err != nil { + panic(err) + } + if !fips140.ServiceIndicator() { + t.Error("FIPS service indicator should be set") + } +} diff --git a/crypto/hmac/hmac.go b/crypto/hmac/hmac.go index 55433fcab22..83f4d2b08ae 100644 --- a/crypto/hmac/hmac.go +++ b/crypto/hmac/hmac.go @@ -25,104 +25,14 @@ import ( "hash" "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140hash" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/subtle" ) -// FIPS 198-1: -// https://csrc.nist.gov/publications/fips/fips198-1/FIPS-198-1_final.pdf - -// key is zero padded to the block size of the hash function -// ipad = 0x36 byte repeated for key length -// opad = 0x5c byte repeated for key length -// hmac = H([key ^ opad] H([key ^ ipad] text)) - -// marshalable is the combination of encoding.BinaryMarshaler and -// encoding.BinaryUnmarshaler. Their method definitions are repeated here to -// avoid a dependency on the encoding package. -type marshalable interface { - MarshalBinary() ([]byte, error) - UnmarshalBinary([]byte) error -} - -type hmac struct { - opad, ipad []byte - outer, inner hash.Hash - - // If marshaled is true, then opad and ipad do not contain a padded - // copy of the key, but rather the marshaled state of outer/inner after - // opad/ipad has been fed into it. - marshaled bool -} - -func (h *hmac) Sum(in []byte) []byte { - origLen := len(in) - in = h.inner.Sum(in) - - if h.marshaled { - if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil { - panic(err) - } - } else { - h.outer.Reset() - h.outer.Write(h.opad) - } - h.outer.Write(in[origLen:]) - return h.outer.Sum(in[:origLen]) -} - -func (h *hmac) Write(p []byte) (n int, err error) { - return h.inner.Write(p) -} - -func (h *hmac) Size() int { return h.outer.Size() } -func (h *hmac) BlockSize() int { return h.inner.BlockSize() } - -func (h *hmac) Reset() { - if h.marshaled { - if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil { - panic(err) - } - return - } - - h.inner.Reset() - h.inner.Write(h.ipad) - - // If the underlying hash is marshalable, we can save some time by - // saving a copy of the hash state now, and restoring it on future - // calls to Reset and Sum instead of writing ipad/opad every time. - // - // If either hash is unmarshalable for whatever reason, - // it's safe to bail out here. - marshalableInner, innerOK := h.inner.(marshalable) - if !innerOK { - return - } - marshalableOuter, outerOK := h.outer.(marshalable) - if !outerOK { - return - } - - imarshal, err := marshalableInner.MarshalBinary() - if err != nil { - return - } - - h.outer.Reset() - h.outer.Write(h.opad) - omarshal, err := marshalableOuter.MarshalBinary() - if err != nil { - return - } - - // Marshaling succeeded; save the marshaled state for later - h.ipad = imarshal - h.opad = omarshal - h.marshaled = true -} - // New returns a new HMAC hash using the given [hash.Hash] type and key. -// New functions like sha256.New from [crypto/sha256] can be used as h. +// New functions like [crypto/sha256.New] can be used as h. // h must return a new Hash every time it is called. // Note that unlike other hash implementations in the standard library, // the returned Hash does not implement [encoding.BinaryMarshaler] @@ -135,41 +45,16 @@ func New(h func() hash.Hash, key []byte) hash.Hash { } // BoringCrypto did not recognize h, so fall through to standard Go code. } - hm := new(hmac) - hm.outer = h() - hm.inner = h() - unique := true - func() { - defer func() { - // The comparison might panic if the underlying types are not comparable. - _ = recover() - }() - if hm.outer == hm.inner { - unique = false + h = fips140hash.UnwrapNew(h) + if fips140only.Enabled { + if len(key) < 112/8 { + panic("crypto/hmac: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") + } + if !fips140only.ApprovedHash(h()) { + panic("crypto/hmac: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") } - }() - if !unique { - panic("crypto/hmac: hash generation function does not produce unique values") - } - blocksize := hm.inner.BlockSize() - hm.ipad = make([]byte, blocksize) - hm.opad = make([]byte, blocksize) - if len(key) > blocksize { - // If key is too big, hash it. - hm.outer.Write(key) - key = hm.outer.Sum(nil) - } - copy(hm.ipad, key) - copy(hm.opad, key) - for i := range hm.ipad { - hm.ipad[i] ^= 0x36 - } - for i := range hm.opad { - hm.opad[i] ^= 0x5c } - hm.inner.Write(hm.ipad) - - return hm + return hmac.New(h, key) } // Equal compares two MACs for equality without leaking timing information. diff --git a/crypto/internal/bigmod/_asm/go.mod b/crypto/internal/bigmod/_asm/go.mod deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/internal/bigmod/_asm/go.sum b/crypto/internal/bigmod/_asm/go.sum deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/internal/cryptotest/aead.go b/crypto/internal/cryptotest/aead.go index 2a9d4ec0a65..722a5c78d0a 100644 --- a/crypto/internal/cryptotest/aead.go +++ b/crypto/internal/cryptotest/aead.go @@ -208,10 +208,12 @@ func TestAEAD(t *testing.T, mAEAD MakeAEAD) { t.Errorf("Seal alters dst instead of appending; got %s, want %s", truncateHex(out[:len(prefix)]), truncateHex(prefix)) } - ciphertext := out[len(prefix):] - // Check that the appended ciphertext wasn't affected by the prefix - if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) { - t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT)) + if isDeterministic(aead) { + ciphertext := out[len(prefix):] + // Check that the appended ciphertext wasn't affected by the prefix + if expectedCT := sealMsg(t, aead, nil, nonce, plaintext, addData); !bytes.Equal(ciphertext, expectedCT) { + t.Errorf("Seal behavior affected by pre-existing data in dst; got %s, want %s", truncateHex(ciphertext), truncateHex(expectedCT)) + } } } }) @@ -254,7 +256,9 @@ func TestAEAD(t *testing.T, mAEAD MakeAEAD) { }) t.Run("WrongNonce", func(t *testing.T) { - + if aead.NonceSize() == 0 { + t.Skip("AEAD does not use a nonce") + } // Test all combinations of plaintext and additional data lengths. for _, ptLen := range lengths { for _, adLen := range lengths { @@ -372,6 +376,18 @@ func sealMsg(t *testing.T, aead cipher.AEAD, ciphertext, nonce, plaintext, addDa return ciphertext } +func isDeterministic(aead cipher.AEAD) bool { + // Check if the AEAD is deterministic by checking if the same plaintext + // encrypted with the same nonce and additional data produces the same + // ciphertext. + nonce := make([]byte, aead.NonceSize()) + addData := []byte("additional data") + plaintext := []byte("plaintext") + ciphertext1 := aead.Seal(nil, nonce, plaintext, addData) + ciphertext2 := aead.Seal(nil, nonce, plaintext, addData) + return bytes.Equal(ciphertext1, ciphertext2) +} + // Helper function to Open and authenticate ciphertext. Checks that Open // doesn't error (assuming ciphertext was well-formed with corresponding nonce // and additional data). diff --git a/crypto/internal/cryptotest/allocations.go b/crypto/internal/cryptotest/allocations.go new file mode 100644 index 00000000000..90891872aa5 --- /dev/null +++ b/crypto/internal/cryptotest/allocations.go @@ -0,0 +1,44 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cryptotest + +import ( + "internal/asan" + "internal/msan" + "internal/race" + "internal/testenv" + "runtime" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/boring" +) + +// SkipTestAllocations skips the test if there are any factors that interfere +// with allocation optimizations. +func SkipTestAllocations(t *testing.T) { + // Go+BoringCrypto uses cgo. + if boring.Enabled { + t.Skip("skipping allocations test with BoringCrypto") + } + + // The sanitizers sometimes cause allocations. + if race.Enabled || msan.Enabled || asan.Enabled { + t.Skip("skipping allocations test with sanitizers") + } + + // The plan9 crypto/rand allocates. + if runtime.GOOS == "plan9" { + t.Skip("skipping allocations test on plan9") + } + + // s390x deviates from other assembly implementations and is very hard to + // test due to the lack of LUCI builders. See #67307. + if runtime.GOARCH == "s390x" { + t.Skip("skipping allocations test on s390x") + } + + // Some APIs rely on inliner and devirtualization to allocate on the stack. + testenv.SkipIfOptimizationOff(t) +} diff --git a/crypto/internal/cryptotest/blockmode.go b/crypto/internal/cryptotest/blockmode.go index 6f0bd1ef8a5..ccae0f4d8fa 100644 --- a/crypto/internal/cryptotest/blockmode.go +++ b/crypto/internal/cryptotest/blockmode.go @@ -60,6 +60,19 @@ func testBlockMode(t *testing.T, bm MakeBlockMode, b cipher.Block, iv []byte) { mustPanic(t, "IV length must equal block size", func() { bm(b, iv) }) }) + t.Run("EmptyInput", func(t *testing.T) { + rng := newRandReader(t) + + src, dst := make([]byte, blockSize), make([]byte, blockSize) + rng.Read(dst) + before := bytes.Clone(dst) + + bm(b, iv).CryptBlocks(dst, src[:0]) + if !bytes.Equal(dst, before) { + t.Errorf("CryptBlocks modified dst on empty input; got %x, want %x", dst, before) + } + }) + t.Run("AlterInput", func(t *testing.T) { rng := newRandReader(t) diff --git a/crypto/internal/cryptotest/fetchmodule.go b/crypto/internal/cryptotest/fetchmodule.go new file mode 100644 index 00000000000..37f2a094972 --- /dev/null +++ b/crypto/internal/cryptotest/fetchmodule.go @@ -0,0 +1,59 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cryptotest + +import ( + "bytes" + "encoding/json" + "internal/testenv" + "os" + "os/exec" + "testing" +) + +// FetchModule fetches the module at the given version and returns the directory +// containing its source tree. It skips the test if fetching modules is not +// possible in this environment. +func FetchModule(t *testing.T, module, version string) string { + testenv.MustHaveExternalNetwork(t) + goTool := testenv.GoToolPath(t) + + // If the default GOMODCACHE doesn't exist, use a temporary directory + // instead. (For example, run.bash sets GOPATH=/nonexist-gopath.) + out, err := testenv.Command(t, goTool, "env", "GOMODCACHE").Output() + if err != nil { + t.Errorf("%s env GOMODCACHE: %v\n%s", goTool, err, out) + if ee, ok := err.(*exec.ExitError); ok { + t.Logf("%s", ee.Stderr) + } + t.FailNow() + } + modcacheOk := false + if gomodcache := string(bytes.TrimSpace(out)); gomodcache != "" { + if _, err := os.Stat(gomodcache); err == nil { + modcacheOk = true + } + } + if !modcacheOk { + t.Setenv("GOMODCACHE", t.TempDir()) + // Allow t.TempDir() to clean up subdirectories. + t.Setenv("GOFLAGS", os.Getenv("GOFLAGS")+" -modcacherw") + } + + t.Logf("fetching %s@%s\n", module, version) + + output, err := testenv.Command(t, goTool, "mod", "download", "-json", module+"@"+version).CombinedOutput() + if err != nil { + t.Fatalf("failed to download %s@%s: %s\n%s\n", module, version, err, output) + } + var j struct { + Dir string + } + if err := json.Unmarshal(output, &j); err != nil { + t.Fatalf("failed to parse 'go mod download': %s\n%s\n", err, output) + } + + return j.Dir +} diff --git a/crypto/internal/cryptotest/implementations.go b/crypto/internal/cryptotest/implementations.go new file mode 100644 index 00000000000..766b47bfe8c --- /dev/null +++ b/crypto/internal/cryptotest/implementations.go @@ -0,0 +1,60 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cryptotest + +import ( + "internal/goos" + "internal/testenv" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// TestAllImplementations runs the provided test function with each available +// implementation of the package registered with crypto/internal/impl. If there +// are no alternative implementations for pkg, f is invoked directly once. +func TestAllImplementations(t *testing.T, pkg string, f func(t *testing.T)) { + // BoringCrypto bypasses the multiple Go implementations. + if boring.Enabled { + f(t) + return + } + + impls := impl.List(pkg) + if len(impls) == 0 { + f(t) + return + } + + t.Cleanup(func() { impl.Reset(pkg) }) + + for _, name := range impls { + if available := impl.Select(pkg, name); available { + t.Run(name, f) + } else { + t.Run(name, func(t *testing.T) { + // Report an error if we're on Linux CI (assumed to be the most + // consistent) and the builder can't test this implementation. + if testenv.Builder() != "" && goos.GOOS == "linux" { + if name == "SHA-NI" { + t.Skip("known issue, see golang.org/issue/69592") + } + if name == "Armv8.2" { + t.Skip("known issue, see golang.org/issue/69593") + } + t.Error("builder doesn't support CPU features needed to test this implementation") + } else { + t.Skip("implementation not supported") + } + }) + } + + } + + // Test the generic implementation. + impl.Select(pkg, "") + t.Run("Base", f) +} diff --git a/crypto/internal/cryptotest/stream.go b/crypto/internal/cryptotest/stream.go index 6bd2a69f919..846a6f69d6b 100644 --- a/crypto/internal/cryptotest/stream.go +++ b/crypto/internal/cryptotest/stream.go @@ -86,6 +86,19 @@ func TestStream(t *testing.T, ms MakeStream) { }) }) + t.Run("EmptyInput", func(t *testing.T) { + rng := newRandReader(t) + + src, dst := make([]byte, 100), make([]byte, 100) + rng.Read(dst) + before := bytes.Clone(dst) + + ms().XORKeyStream(dst, src[:0]) + if !bytes.Equal(dst, before) { + t.Errorf("XORKeyStream modified dst on empty input; got %s, want %s", truncateHex(dst), truncateHex(before)) + } + }) + t.Run("AlterInput", func(t *testing.T) { rng := newRandReader(t) src, dst, before := make([]byte, bufCap), make([]byte, bufCap), make([]byte, bufCap) diff --git a/crypto/internal/edwards25519/field/_asm/go.mod b/crypto/internal/edwards25519/field/_asm/go.mod deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/internal/edwards25519/field/_asm/go.sum b/crypto/internal/edwards25519/field/_asm/go.sum deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/internal/entropy/entropy.go b/crypto/internal/entropy/entropy.go new file mode 100644 index 00000000000..11d22f0a97a --- /dev/null +++ b/crypto/internal/entropy/entropy.go @@ -0,0 +1,28 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package entropy provides the passive entropy source for the FIPS 140-3 +// module. It is only used in FIPS mode by [crypto/internal/fips140/drbg.Read]. +// +// This complies with IG 9.3.A, Additional Comment 12, which until January 1, +// 2026 allows new modules to meet an [earlier version] of Resolution 2(b): +// "A software module that contains an approved DRBG that receives a LOAD +// command (or its logical equivalent) with entropy obtained from [...] inside +// the physical perimeter of the operational environment of the module [...]." +// +// Distributions that have their own SP 800-90B entropy source should replace +// this package with their own implementation. +// +// [earlier version]: https://csrc.nist.gov/CSRC/media/Projects/cryptographic-module-validation-program/documents/IG%209.3.A%20Resolution%202b%5BMarch%2026%202024%5D.pdf +package entropy + +import "github.com/runZeroInc/excrypto/crypto/internal/sysrand" + +// Depleted notifies the entropy source that the entropy in the module is +// "depleted" and provides the callback for the LOAD command. +func Depleted(LOAD func(*[48]byte)) { + var entropy [48]byte + sysrand.Read(entropy[:]) + LOAD(&entropy) +} diff --git a/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go b/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go new file mode 100644 index 00000000000..35e1d8aeb62 --- /dev/null +++ b/crypto/internal/fips140/aes/_asm/ctr/ctr_amd64_asm.go @@ -0,0 +1,127 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "sync" + + . "github.com/mmcloughlin/avo/build" + . "github.com/mmcloughlin/avo/operand" + . "github.com/mmcloughlin/avo/reg" +) + +//go:generate go run . -out ../../ctr_amd64.s + +func main() { + Package("crypto/aes") + ConstraintExpr("!purego") + + ctrBlocks(1) + ctrBlocks(2) + ctrBlocks(4) + ctrBlocks(8) + + Generate() +} + +func ctrBlocks(numBlocks int) { + Implement(fmt.Sprintf("ctrBlocks%dAsm", numBlocks)) + + rounds := Load(Param("nr"), GP64()) + xk := Load(Param("xk"), GP64()) + dst := Load(Param("dst"), GP64()) + src := Load(Param("src"), GP64()) + ivlo := Load(Param("ivlo"), GP64()) + ivhi := Load(Param("ivhi"), GP64()) + + bswap := XMM() + MOVOU(bswapMask(), bswap) + + blocks := make([]VecVirtual, 0, numBlocks) + + // Lay out counter block plaintext. + for i := 0; i < numBlocks; i++ { + x := XMM() + blocks = append(blocks, x) + + MOVQ(ivlo, x) + PINSRQ(Imm(1), ivhi, x) + PSHUFB(bswap, x) + if i < numBlocks-1 { + ADDQ(Imm(1), ivlo) + ADCQ(Imm(0), ivhi) + } + } + + // Initial key add. + aesRoundStart(blocks, Mem{Base: xk}) + ADDQ(Imm(16), xk) + + // Branch based on the number of rounds. + SUBQ(Imm(12), rounds) + JE(LabelRef("enc192")) + JB(LabelRef("enc128")) + + // Two extra rounds for 256-bit keys. + aesRound(blocks, Mem{Base: xk}) + aesRound(blocks, Mem{Base: xk}.Offset(16)) + ADDQ(Imm(32), xk) + + // Two extra rounds for 192-bit keys. + Label("enc192") + aesRound(blocks, Mem{Base: xk}) + aesRound(blocks, Mem{Base: xk}.Offset(16)) + ADDQ(Imm(32), xk) + + // 10 rounds for 128-bit keys (with special handling for the final round). + Label("enc128") + for i := 0; i < 9; i++ { + aesRound(blocks, Mem{Base: xk}.Offset(16*i)) + } + aesRoundLast(blocks, Mem{Base: xk}.Offset(16*9)) + + // XOR state with src and write back to dst. + for i, b := range blocks { + x := XMM() + + MOVUPS(Mem{Base: src}.Offset(16*i), x) + PXOR(b, x) + MOVUPS(x, Mem{Base: dst}.Offset(16*i)) + } + + RET() +} + +func aesRoundStart(blocks []VecVirtual, k Mem) { + x := XMM() + MOVUPS(k, x) + for _, b := range blocks { + PXOR(x, b) + } +} + +func aesRound(blocks []VecVirtual, k Mem) { + x := XMM() + MOVUPS(k, x) + for _, b := range blocks { + AESENC(x, b) + } +} + +func aesRoundLast(blocks []VecVirtual, k Mem) { + x := XMM() + MOVUPS(k, x) + for _, b := range blocks { + AESENCLAST(x, b) + } +} + +var bswapMask = sync.OnceValue(func() Mem { + bswapMask := GLOBL("bswapMask", NOPTR|RODATA) + DATA(0x00, U64(0x08090a0b0c0d0e0f)) + DATA(0x08, U64(0x0001020304050607)) + return bswapMask +}) diff --git a/crypto/internal/fips140/aes/_asm/ctr/go.mod b/crypto/internal/fips140/aes/_asm/ctr/go.mod new file mode 100644 index 00000000000..5d97cd7f4e6 --- /dev/null +++ b/crypto/internal/fips140/aes/_asm/ctr/go.mod @@ -0,0 +1,11 @@ +module crypto/aes/_asm/ctr + +go 1.24 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) diff --git a/crypto/internal/fips140/aes/_asm/ctr/go.sum b/crypto/internal/fips140/aes/_asm/ctr/go.sum new file mode 100644 index 00000000000..76af484b2eb --- /dev/null +++ b/crypto/internal/fips140/aes/_asm/ctr/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/crypto/aes/_asm/standard/asm_amd64.go b/crypto/internal/fips140/aes/_asm/standard/aes_amd64.go similarity index 98% rename from crypto/aes/_asm/standard/asm_amd64.go rename to crypto/internal/fips140/aes/_asm/standard/aes_amd64.go index cd192af6d72..44e0a79289c 100644 --- a/crypto/aes/_asm/standard/asm_amd64.go +++ b/crypto/internal/fips140/aes/_asm/standard/aes_amd64.go @@ -14,10 +14,10 @@ import ( . "github.com/mmcloughlin/avo/reg" ) -//go:generate go run . -out ../../asm_amd64.s -pkg aes +//go:generate go run . -out ../../aes_amd64.s func main() { - Package("github.com/runZeroInc/excrypto/crypto/aes") + Package("crypto/aes") ConstraintExpr("!purego") encryptBlockAsm() decryptBlockAsm() diff --git a/crypto/internal/fips140/aes/_asm/standard/go.mod b/crypto/internal/fips140/aes/_asm/standard/go.mod new file mode 100644 index 00000000000..f1329b7290a --- /dev/null +++ b/crypto/internal/fips140/aes/_asm/standard/go.mod @@ -0,0 +1,11 @@ +module crypto/aes/_asm/standard + +go 1.24 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) diff --git a/crypto/internal/fips140/aes/_asm/standard/go.sum b/crypto/internal/fips140/aes/_asm/standard/go.sum new file mode 100644 index 00000000000..76af484b2eb --- /dev/null +++ b/crypto/internal/fips140/aes/_asm/standard/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/crypto/internal/fips140/aes/aes.go b/crypto/internal/fips140/aes/aes.go new file mode 100644 index 00000000000..54d82303a46 --- /dev/null +++ b/crypto/internal/fips140/aes/aes.go @@ -0,0 +1,132 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aes + +import ( + "strconv" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" +) + +// BlockSize is the AES block size in bytes. +const BlockSize = 16 + +// A Block is an instance of AES using a particular key. +// It is safe for concurrent use. +type Block struct { + block +} + +// blockExpanded is the block type used for all architectures except s390x, +// which feeds the raw key directly to its instructions. +type blockExpanded struct { + rounds int + // Round keys, where only the first (rounds + 1) × (128 ÷ 32) words are used. + enc [60]uint32 + dec [60]uint32 +} + +const ( + // AES-128 has 128-bit keys, 10 rounds, and uses 11 128-bit round keys + // (11×128÷32 = 44 32-bit words). + + // AES-192 has 192-bit keys, 12 rounds, and uses 13 128-bit round keys + // (13×128÷32 = 52 32-bit words). + + // AES-256 has 256-bit keys, 14 rounds, and uses 15 128-bit round keys + // (15×128÷32 = 60 32-bit words). + + aes128KeySize = 16 + aes192KeySize = 24 + aes256KeySize = 32 + + aes128Rounds = 10 + aes192Rounds = 12 + aes256Rounds = 14 +) + +// roundKeysSize returns the number of uint32 of c.end or c.dec that are used. +func (b *blockExpanded) roundKeysSize() int { + return (b.rounds + 1) * (128 / 32) +} + +type KeySizeError int + +func (k KeySizeError) Error() string { + return "crypto/aes: invalid key size " + strconv.Itoa(int(k)) +} + +// New creates and returns a new [cipher.Block] implementation. +// The key argument should be the AES key, either 16, 24, or 32 bytes to select +// AES-128, AES-192, or AES-256. +func New(key []byte) (*Block, error) { + // This call is outline to let the allocation happen on the parent stack. + return newOutlined(&Block{}, key) +} + +// newOutlined is marked go:noinline to avoid it inlining into New, and making New +// too complex to inline itself. +// +//go:noinline +func newOutlined(b *Block, key []byte) (*Block, error) { + switch len(key) { + case aes128KeySize, aes192KeySize, aes256KeySize: + default: + return nil, KeySizeError(len(key)) + } + return newBlock(b, key), nil +} + +func newBlockExpanded(c *blockExpanded, key []byte) { + switch len(key) { + case aes128KeySize: + c.rounds = aes128Rounds + case aes192KeySize: + c.rounds = aes192Rounds + case aes256KeySize: + c.rounds = aes256Rounds + } + expandKeyGeneric(c, key) +} + +func (c *Block) BlockSize() int { return BlockSize } + +func (c *Block) Encrypt(dst, src []byte) { + // AES-ECB is not approved in FIPS 140-3 mode. + fips140.RecordNonApproved() + if len(src) < BlockSize { + panic("crypto/aes: input not full block") + } + if len(dst) < BlockSize { + panic("crypto/aes: output not full block") + } + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("crypto/aes: invalid buffer overlap") + } + encryptBlock(c, dst, src) +} + +func (c *Block) Decrypt(dst, src []byte) { + // AES-ECB is not approved in FIPS 140-3 mode. + fips140.RecordNonApproved() + if len(src) < BlockSize { + panic("crypto/aes: input not full block") + } + if len(dst) < BlockSize { + panic("crypto/aes: output not full block") + } + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("crypto/aes: invalid buffer overlap") + } + decryptBlock(c, dst, src) +} + +// EncryptBlockInternal applies the AES encryption function to one block. +// +// It is an internal function meant only for the gcm package. +func EncryptBlockInternal(c *Block, dst, src []byte) { + encryptBlock(c, dst, src) +} diff --git a/crypto/aes/asm_amd64.s b/crypto/internal/fips140/aes/aes_amd64.s similarity index 100% rename from crypto/aes/asm_amd64.s rename to crypto/internal/fips140/aes/aes_amd64.s diff --git a/crypto/aes/asm_arm64.s b/crypto/internal/fips140/aes/aes_arm64.s similarity index 99% rename from crypto/aes/asm_arm64.s rename to crypto/internal/fips140/aes/aes_arm64.s index 2bf5bee2b59..192d0df8965 100644 --- a/crypto/aes/asm_arm64.s +++ b/crypto/internal/fips140/aes/aes_arm64.s @@ -22,14 +22,14 @@ TEXT ·encryptBlockAsm(SB),NOSPLIT,$0 CMP $12, R9 BLT enc128 - BEQ enc196 + BEQ enc192 enc256: VLD1.P 32(R10), [V1.B16, V2.B16] AESE V1.B16, V0.B16 AESMC V0.B16, V0.B16 AESE V2.B16, V0.B16 AESMC V0.B16, V0.B16 -enc196: +enc192: VLD1.P 32(R10), [V3.B16, V4.B16] AESE V3.B16, V0.B16 AESMC V0.B16, V0.B16 @@ -73,14 +73,14 @@ TEXT ·decryptBlockAsm(SB),NOSPLIT,$0 CMP $12, R9 BLT dec128 - BEQ dec196 + BEQ dec192 dec256: VLD1.P 32(R10), [V1.B16, V2.B16] AESD V1.B16, V0.B16 AESIMC V0.B16, V0.B16 AESD V2.B16, V0.B16 AESIMC V0.B16, V0.B16 -dec196: +dec192: VLD1.P 32(R10), [V3.B16, V4.B16] AESD V3.B16, V0.B16 AESIMC V0.B16, V0.B16 diff --git a/crypto/internal/fips140/aes/aes_asm.go b/crypto/internal/fips140/aes/aes_asm.go new file mode 100644 index 00000000000..03b1f3adf7c --- /dev/null +++ b/crypto/internal/fips140/aes/aes_asm.go @@ -0,0 +1,96 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (amd64 || arm64 || ppc64 || ppc64le) && !purego + +package aes + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +//go:noescape +func encryptBlockAsm(nr int, xk *uint32, dst, src *byte) + +//go:noescape +func decryptBlockAsm(nr int, xk *uint32, dst, src *byte) + +//go:noescape +func expandKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) + +var supportsAES = cpu.X86HasAES && cpu.X86HasSSE41 && cpu.X86HasSSSE3 || + cpu.ARM64HasAES || cpu.PPC64 || cpu.PPC64le + +func init() { + if cpu.AMD64 { + impl.Register("aes", "AES-NI", &supportsAES) + } + if cpu.ARM64 { + impl.Register("aes", "Armv8.0", &supportsAES) + } + if cpu.PPC64 || cpu.PPC64le { + // The POWER architecture doesn't have a way to turn off AES support + // at runtime with GODEBUG=cpu.something=off, so introduce a new GODEBUG + // knob for that. It's intentionally only checked at init() time, to + // avoid the performance overhead of checking it every time. + if godebug.Value("#ppc64aes") == "off" { + supportsAES = false + } + impl.Register("aes", "POWER8", &supportsAES) + } +} + +// checkGenericIsExpected is called by the variable-time implementation to make +// sure it is not used when hardware support is available. It shouldn't happen, +// but this way it's more evidently correct. +func checkGenericIsExpected() { + if supportsAES { + panic("crypto/aes: internal error: using generic implementation despite hardware support") + } +} + +type block struct { + blockExpanded +} + +func newBlock(c *Block, key []byte) *Block { + switch len(key) { + case aes128KeySize: + c.rounds = aes128Rounds + case aes192KeySize: + c.rounds = aes192Rounds + case aes256KeySize: + c.rounds = aes256Rounds + } + if supportsAES { + expandKeyAsm(c.rounds, &key[0], &c.enc[0], &c.dec[0]) + } else { + expandKeyGeneric(&c.blockExpanded, key) + } + return c +} + +// EncryptionKeySchedule is used from the GCM implementation to access the +// precomputed AES key schedule, to pass to the assembly implementation. +func EncryptionKeySchedule(c *Block) []uint32 { + return c.enc[:c.roundKeysSize()] +} + +func encryptBlock(c *Block, dst, src []byte) { + if supportsAES { + encryptBlockAsm(c.rounds, &c.enc[0], &dst[0], &src[0]) + } else { + encryptBlockGeneric(&c.blockExpanded, dst, src) + } +} + +func decryptBlock(c *Block, dst, src []byte) { + if supportsAES { + decryptBlockAsm(c.rounds, &c.dec[0], &dst[0], &src[0]) + } else { + decryptBlockGeneric(&c.blockExpanded, dst, src) + } +} diff --git a/crypto/aes/block.go b/crypto/internal/fips140/aes/aes_generic.go similarity index 80% rename from crypto/aes/block.go rename to crypto/internal/fips140/aes/aes_generic.go index 81824db9665..5ea5da3f2dd 100644 --- a/crypto/aes/block.go +++ b/crypto/internal/fips140/aes/aes_generic.go @@ -36,15 +36,18 @@ package aes -import "github.com/runZeroInc/excrypto/internal/byteorder" +import "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" // Encrypt one block from src into dst, using the expanded key xk. -func encryptBlockGo(xk []uint32, dst, src []byte) { +func encryptBlockGeneric(c *blockExpanded, dst, src []byte) { + checkGenericIsExpected() + xk := c.enc[:] + _ = src[15] // early bounds check - s0 := byteorder.BeUint32(src[0:4]) - s1 := byteorder.BeUint32(src[4:8]) - s2 := byteorder.BeUint32(src[8:12]) - s3 := byteorder.BeUint32(src[12:16]) + s0 := byteorder.BEUint32(src[0:4]) + s1 := byteorder.BEUint32(src[4:8]) + s2 := byteorder.BEUint32(src[8:12]) + s3 := byteorder.BEUint32(src[12:16]) // First round just XORs input with key. s0 ^= xk[0] @@ -53,11 +56,9 @@ func encryptBlockGo(xk []uint32, dst, src []byte) { s3 ^= xk[3] // Middle rounds shuffle using tables. - // Number of rounds is set by length of expanded key. - nr := len(xk)/4 - 2 // - 2: one above, one more below k := 4 var t0, t1, t2, t3 uint32 - for r := 0; r < nr; r++ { + for r := 0; r < c.rounds-1; r++ { t0 = xk[k+0] ^ te0[uint8(s0>>24)] ^ te1[uint8(s1>>16)] ^ te2[uint8(s2>>8)] ^ te3[uint8(s3)] t1 = xk[k+1] ^ te0[uint8(s1>>24)] ^ te1[uint8(s2>>16)] ^ te2[uint8(s3>>8)] ^ te3[uint8(s0)] t2 = xk[k+2] ^ te0[uint8(s2>>24)] ^ te1[uint8(s3>>16)] ^ te2[uint8(s0>>8)] ^ te3[uint8(s1)] @@ -78,19 +79,22 @@ func encryptBlockGo(xk []uint32, dst, src []byte) { s3 ^= xk[k+3] _ = dst[15] // early bounds check - byteorder.BePutUint32(dst[0:4], s0) - byteorder.BePutUint32(dst[4:8], s1) - byteorder.BePutUint32(dst[8:12], s2) - byteorder.BePutUint32(dst[12:16], s3) + byteorder.BEPutUint32(dst[0:4], s0) + byteorder.BEPutUint32(dst[4:8], s1) + byteorder.BEPutUint32(dst[8:12], s2) + byteorder.BEPutUint32(dst[12:16], s3) } // Decrypt one block from src into dst, using the expanded key xk. -func decryptBlockGo(xk []uint32, dst, src []byte) { +func decryptBlockGeneric(c *blockExpanded, dst, src []byte) { + checkGenericIsExpected() + xk := c.dec[:] + _ = src[15] // early bounds check - s0 := byteorder.BeUint32(src[0:4]) - s1 := byteorder.BeUint32(src[4:8]) - s2 := byteorder.BeUint32(src[8:12]) - s3 := byteorder.BeUint32(src[12:16]) + s0 := byteorder.BEUint32(src[0:4]) + s1 := byteorder.BEUint32(src[4:8]) + s2 := byteorder.BEUint32(src[8:12]) + s3 := byteorder.BEUint32(src[12:16]) // First round just XORs input with key. s0 ^= xk[0] @@ -99,11 +103,9 @@ func decryptBlockGo(xk []uint32, dst, src []byte) { s3 ^= xk[3] // Middle rounds shuffle using tables. - // Number of rounds is set by length of expanded key. - nr := len(xk)/4 - 2 // - 2: one above, one more below k := 4 var t0, t1, t2, t3 uint32 - for r := 0; r < nr; r++ { + for r := 0; r < c.rounds-1; r++ { t0 = xk[k+0] ^ td0[uint8(s0>>24)] ^ td1[uint8(s3>>16)] ^ td2[uint8(s2>>8)] ^ td3[uint8(s1)] t1 = xk[k+1] ^ td0[uint8(s1>>24)] ^ td1[uint8(s0>>16)] ^ td2[uint8(s3>>8)] ^ td3[uint8(s2)] t2 = xk[k+2] ^ td0[uint8(s2>>24)] ^ td1[uint8(s1>>16)] ^ td2[uint8(s0>>8)] ^ td3[uint8(s3)] @@ -124,10 +126,10 @@ func decryptBlockGo(xk []uint32, dst, src []byte) { s3 ^= xk[k+3] _ = dst[15] // early bounds check - byteorder.BePutUint32(dst[0:4], s0) - byteorder.BePutUint32(dst[4:8], s1) - byteorder.BePutUint32(dst[8:12], s2) - byteorder.BePutUint32(dst[12:16], s3) + byteorder.BEPutUint32(dst[0:4], s0) + byteorder.BEPutUint32(dst[4:8], s1) + byteorder.BEPutUint32(dst[8:12], s2) + byteorder.BEPutUint32(dst[12:16], s3) } // Apply sbox0 to each byte in w. @@ -143,38 +145,37 @@ func rotw(w uint32) uint32 { return w<<8 | w>>24 } // Key expansion algorithm. See FIPS-197, Figure 11. // Their rcon[i] is our powx[i-1] << 24. -func expandKeyGo(key []byte, enc, dec []uint32) { +func expandKeyGeneric(c *blockExpanded, key []byte) { + checkGenericIsExpected() + // Encryption key setup. var i int nk := len(key) / 4 for i = 0; i < nk; i++ { - enc[i] = byteorder.BeUint32(key[4*i:]) + c.enc[i] = byteorder.BEUint32(key[4*i:]) } - for ; i < len(enc); i++ { - t := enc[i-1] + for ; i < c.roundKeysSize(); i++ { + t := c.enc[i-1] if i%nk == 0 { t = subw(rotw(t)) ^ (uint32(powx[i/nk-1]) << 24) } else if nk > 6 && i%nk == 4 { t = subw(t) } - enc[i] = enc[i-nk] ^ t + c.enc[i] = c.enc[i-nk] ^ t } // Derive decryption key from encryption key. // Reverse the 4-word round key sets from enc to produce dec. // All sets but the first and last get the MixColumn transform applied. - if dec == nil { - return - } - n := len(enc) + n := c.roundKeysSize() for i := 0; i < n; i += 4 { ei := n - i - 4 for j := 0; j < 4; j++ { - x := enc[ei+j] + x := c.enc[ei+j] if i > 0 && i+4 < n { x = td0[sbox0[x>>24]] ^ td1[sbox0[x>>16&0xff]] ^ td2[sbox0[x>>8&0xff]] ^ td3[sbox0[x&0xff]] } - dec[i+j] = x + c.dec[i+j] = x } } } diff --git a/crypto/internal/fips140/aes/aes_noasm.go b/crypto/internal/fips140/aes/aes_noasm.go new file mode 100644 index 00000000000..8ba540273e3 --- /dev/null +++ b/crypto/internal/fips140/aes/aes_noasm.go @@ -0,0 +1,26 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !s390x && !ppc64 && !ppc64le && !arm64) || purego + +package aes + +type block struct { + blockExpanded +} + +func newBlock(c *Block, key []byte) *Block { + newBlockExpanded(&c.blockExpanded, key) + return c +} + +func encryptBlock(c *Block, dst, src []byte) { + encryptBlockGeneric(&c.blockExpanded, dst, src) +} + +func decryptBlock(c *Block, dst, src []byte) { + decryptBlockGeneric(&c.blockExpanded, dst, src) +} + +func checkGenericIsExpected() {} diff --git a/crypto/aes/asm_ppc64x.s b/crypto/internal/fips140/aes/aes_ppc64x.s similarity index 76% rename from crypto/aes/asm_ppc64x.s rename to crypto/internal/fips140/aes/aes_ppc64x.s index 5a2b210920e..4c95dd21527 100644 --- a/crypto/aes/asm_ppc64x.s +++ b/crypto/internal/fips140/aes/aes_ppc64x.s @@ -74,6 +74,7 @@ GLOBL ·rcon(SB), RODATA, $80 #define P8_LXVB16X(RA,RB,VT) LXVB16X (RA+RB), VT #define P8_STXVB16X(VS,RA,RB) STXVB16X VS, (RA+RB) #define XXBRD_ON_LE(VA,VT) XXBRD VA, VT +#define SETUP_ESPERM(rtmp) # else // On POWER8/ppc64le, emulate the POWER9 instructions by loading unaligned // doublewords and byte-swapping each doubleword to emulate BE load/stores. @@ -89,11 +90,17 @@ GLOBL ·rcon(SB), RODATA, $80 #define XXBRD_ON_LE(VA,VT) \ VPERM VA, VA, ESPERM, VT +// Setup byte-swapping permute value in ESPERM for POWER9 instruction +// emulation macros. +#define SETUP_ESPERM(rtmp) \ + MOVD $·rcon(SB), rtmp \ + LVX (rtmp), ESPERM # endif // defined(GOPPC64_power9) #else #define P8_LXVB16X(RA,RB,VT) LXVD2X (RA+RB), VT #define P8_STXVB16X(VS,RA,RB) STXVD2X VS, (RA+RB) #define XXBRD_ON_LE(VA, VT) +#define SETUP_ESPERM(rtmp) #endif // defined(GOARCH_ppc64le) // func setEncryptKeyAsm(nr int, key *byte, enc *uint32, dec *uint32) @@ -313,10 +320,7 @@ TEXT ·encryptBlockAsm(SB), NOSPLIT|NOFRAME, $0 MOVD xk+8(FP), R5 // Key pointer MOVD dst+16(FP), R3 // Dest pointer MOVD src+24(FP), R4 // Src pointer -#ifdef NEEDS_ESPERM - MOVD $·rcon(SB), R7 - LVX (R7), ESPERM // Permute value for P8_ macros. -#endif + SETUP_ESPERM(R7) // Set CR{1,2,3}EQ to hold the key size information. CMPU R6, $10, CR1 @@ -408,10 +412,7 @@ TEXT ·decryptBlockAsm(SB), NOSPLIT|NOFRAME, $0 MOVD xk+8(FP), R5 // Key pointer MOVD dst+16(FP), R3 // Dest pointer MOVD src+24(FP), R4 // Src pointer -#ifdef NEEDS_ESPERM - MOVD $·rcon(SB), R7 - LVX (R7), ESPERM // Permute value for P8_ macros. -#endif + SETUP_ESPERM(R7) // Set CR{1,2,3}EQ to hold the key size information. CMPU R6, $10, CR1 @@ -626,10 +627,7 @@ TEXT ·cryptBlocksChain(SB), NOSPLIT|NOFRAME, $0 MOVD enc+40(FP), ENC MOVD nr+48(FP), ROUNDS -#ifdef NEEDS_ESPERM - MOVD $·rcon(SB), R11 - LVX (R11), ESPERM // Permute value for P8_ macros. -#endif + SETUP_ESPERM(R11) // Assume len > 0 && len % blockSize == 0. CMPW ENC, $0 @@ -673,3 +671,221 @@ Lcbc_dec: P8_STXVB16X(IVEC, IVP, R0) CLEAR_KEYS() RET + + +#define DO1_CIPHER(iv0, keyv, key, op) \ + LXVD2X (key), keyv \ + ADD $16, key \ + op iv0, keyv, iv0 + +#define DO2_CIPHER(iv0, iv1, keyv, key, op) \ + DO1_CIPHER(iv0, keyv, key, op) \ + op iv1, keyv, iv1 + +#define DO4_CIPHER(iv0, iv1, iv2, iv3, keyv, key, op) \ + DO2_CIPHER(iv0, iv1, keyv, key, op) \ + op iv2, keyv, iv2 \ + op iv3, keyv, iv3 + +#define DO8_CIPHER(iv0, iv1, iv2, iv3, iv4, iv5, iv6, iv7, keyv, key, op) \ + DO4_CIPHER(iv0, iv1, iv2, iv3, keyv, key, op) \ + op iv4, keyv, iv4 \ + op iv5, keyv, iv5 \ + op iv6, keyv, iv6 \ + op iv7, keyv, iv7 + +#define XOR_STORE(src, iv, dstp, dstpoff) \ + XXLXOR src, iv, V8 \ + P8_STXVB16X(V8,dstp,dstpoff) + +//func ctrBlocks1Asm(nr int, xk *[60]uint32, dst, src *[1 * BlockSize]byte, ivlo, ivhi uint64) +TEXT ·ctrBlocks1Asm(SB), NOSPLIT|NOFRAME, $0 + +#define CTRBLOCK_PROLOGUE \ + MOVD nr+0(FP), R3 \ + MOVD xk+8(FP), R4 \ + MOVD dst+16(FP), R5 \ + MOVD src+24(FP), R6 \ + MOVD ivlo+32(FP), R8 \ + MOVD ivhi+40(FP), R9 \ + CMP R3, $12, CR1 \ + MTVSRD R8, V0 \ + MTVSRD R9, V1 \ + XXPERMDI V1, V0, $0, V0 \ + SETUP_ESPERM(R8) + + CTRBLOCK_PROLOGUE + + DO1_CIPHER(V0,V8,R4,VXOR) + + BEQ CR1, key_12 + BLT CR1, key_10 +key_14: + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) +key_12: + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) +key_10: + P8_LXVB16X(R6,R0,V9) + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) + + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHER) + + DO1_CIPHER(V0,V8,R4,VCIPHER) + DO1_CIPHER(V0,V8,R4,VCIPHERLAST) + + XOR_STORE(V9,V0,R5,R0) + RET + +//func ctrBlocks2Asm(nr int, xk *[60]uint32, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) +TEXT ·ctrBlocks2Asm(SB), NOSPLIT|NOFRAME, $0 + CTRBLOCK_PROLOGUE + + XXLEQV V8, V8, V8 // V0 is -1 + VSUBUQM V0, V8, V1 // Vi = IV + i (as IV - (-1)) + + DO2_CIPHER(V0,V1,V8,R4,VXOR) + + BEQ CR1, key_12 + BLT CR1, key_10 +key_14: + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) +key_12: + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) +key_10: + P8_LXVB16X(R6,R0,V9) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + MOVD $16, R8 + P8_LXVB16X(R6,R8,V10) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHER) + DO2_CIPHER(V0,V1,V8,R4,VCIPHERLAST) + + XOR_STORE(V9,V0,R5,R0) + XOR_STORE(V10,V1,R5,R8) + + RET + +//func ctrBlocks4Asm(nr int, xk *[60]uint32, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) +TEXT ·ctrBlocks4Asm(SB), NOSPLIT|NOFRAME, $0 + CTRBLOCK_PROLOGUE + + XXLEQV V8, V8, V8 // V0 is -1 + VSUBUQM V0, V8, V1 // Vi = IV + i (as IV - (-1)) + VSUBUQM V1, V8, V2 + VSUBUQM V2, V8, V3 + + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VXOR) + + BEQ CR1, key_12 + BLT CR1, key_10 +key_14: + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) +key_12: + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) +key_10: + P8_LXVB16X(R6,R0,V9) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + MOVD $16, R8 + P8_LXVB16X(R6,R8,V10) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + MOVD $32, R9 + P8_LXVB16X(R6,R9,V11) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + MOVD $48, R10 + P8_LXVB16X(R6,R10,V12) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHER) + DO4_CIPHER(V0,V1,V2,V3,V8,R4,VCIPHERLAST) + + XOR_STORE(V9,V0,R5,R0) + XOR_STORE(V10,V1,R5,R8) + XOR_STORE(V11,V2,R5,R9) + XOR_STORE(V12,V3,R5,R10) + + RET + +//func ctrBlocks8Asm(nr int, xk *[60]uint32, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) +TEXT ·ctrBlocks8Asm(SB), NOSPLIT|NOFRAME, $0 + CTRBLOCK_PROLOGUE + + XXLEQV V8, V8, V8 // V8 is -1 + VSUBUQM V0, V8, V1 // Vi = IV + i (as IV - (-1)) + VADDUQM V8, V8, V9 // V9 is -2 + + VSUBUQM V0, V9, V2 + VSUBUQM V1, V9, V3 + VSUBUQM V2, V9, V4 + VSUBUQM V3, V9, V5 + VSUBUQM V4, V9, V6 + VSUBUQM V5, V9, V7 + + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VXOR) + + BEQ CR1, key_12 + BLT CR1, key_10 +key_14: + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) +key_12: + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) +key_10: + P8_LXVB16X(R6,R0,V9) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $16, R8 + P8_LXVB16X(R6,R8,V10) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $32, R9 + P8_LXVB16X(R6,R9,V11) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $48, R10 + P8_LXVB16X(R6,R10,V12) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $64, R11 + P8_LXVB16X(R6,R11,V13) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $80, R12 + P8_LXVB16X(R6,R12,V14) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $96, R14 + P8_LXVB16X(R6,R14,V15) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + MOVD $112, R15 + P8_LXVB16X(R6,R15,V16) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHER) + DO8_CIPHER(V0,V1,V2,V3,V4,V5,V6,V7,V8,R4,VCIPHERLAST) + + XOR_STORE(V9,V0,R5,R0) + XOR_STORE(V10,V1,R5,R8) + XOR_STORE(V11,V2,R5,R9) + XOR_STORE(V12,V3,R5,R10) + XOR_STORE(V13,V4,R5,R11) + XOR_STORE(V14,V5,R5,R12) + XOR_STORE(V15,V6,R5,R14) + XOR_STORE(V16,V7,R5,R15) + + RET + diff --git a/crypto/internal/fips140/aes/aes_s390x.go b/crypto/internal/fips140/aes/aes_s390x.go new file mode 100644 index 00000000000..409b89d3f42 --- /dev/null +++ b/crypto/internal/fips140/aes/aes_s390x.go @@ -0,0 +1,99 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package aes + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +type code int + +// Function codes for the cipher message family of instructions. +const ( + aes128 code = 18 + aes192 code = 19 + aes256 code = 20 +) + +type block struct { + function code // code for cipher message instruction + key []byte // key (128, 192 or 256 bits) + storage [32]byte // array backing key slice + + fallback *blockExpanded +} + +// cryptBlocks invokes the cipher message (KM) instruction with +// the given function code. This is equivalent to AES in ECB +// mode. The length must be a multiple of BlockSize (16). +// +//go:noescape +func cryptBlocks(c code, key, dst, src *byte, length int) + +var supportsAES = cpu.S390XHasAES && cpu.S390XHasAESCBC + +func init() { + // CP Assist for Cryptographic Functions (CPACF) + // https://www.ibm.com/docs/en/zos/3.1.0?topic=icsf-cp-assist-cryptographic-functions-cpacf + impl.Register("aes", "CPACF", &supportsAES) +} + +func checkGenericIsExpected() { + if supportsAES { + panic("crypto/aes: internal error: using generic implementation despite hardware support") + } +} + +func newBlock(c *Block, key []byte) *Block { + if !supportsAES { + c.fallback = &blockExpanded{} + newBlockExpanded(c.fallback, key) + return c + } + + switch len(key) { + case aes128KeySize: + c.function = aes128 + case aes192KeySize: + c.function = aes192 + case aes256KeySize: + c.function = aes256 + } + c.key = c.storage[:len(key)] + copy(c.key, key) + return c +} + +// BlockFunction returns the function code for the block cipher. +// It is used by the GCM implementation to invoke the KMA instruction. +func BlockFunction(c *Block) int { + return int(c.function) +} + +// BlockKey returns the key for the block cipher. +// It is used by the GCM implementation to invoke the KMA instruction. +func BlockKey(c *Block) []byte { + return c.key +} + +func encryptBlock(c *Block, dst, src []byte) { + if c.fallback != nil { + encryptBlockGeneric(c.fallback, dst, src) + } else { + cryptBlocks(c.function, &c.key[0], &dst[0], &src[0], BlockSize) + } +} + +func decryptBlock(c *Block, dst, src []byte) { + if c.fallback != nil { + decryptBlockGeneric(c.fallback, dst, src) + } else { + // The decrypt function code is equal to the function code + 128. + cryptBlocks(c.function+128, &c.key[0], &dst[0], &src[0], BlockSize) + } +} diff --git a/crypto/internal/fips140/aes/aes_s390x.s b/crypto/internal/fips140/aes/aes_s390x.s new file mode 100644 index 00000000000..5a60dd03b16 --- /dev/null +++ b/crypto/internal/fips140/aes/aes_s390x.s @@ -0,0 +1,39 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +// func cryptBlocks(c code, key, dst, src *byte, length int) +TEXT ·cryptBlocks(SB),NOSPLIT,$0-40 + MOVD key+8(FP), R1 + MOVD dst+16(FP), R2 + MOVD src+24(FP), R4 + MOVD length+32(FP), R5 + MOVD c+0(FP), R0 +loop: + KM R2, R4 // cipher message (KM) + BVS loop // branch back if interrupted + XOR R0, R0 + RET + +// func cryptBlocksChain(c code, iv, key, dst, src *byte, length int) +TEXT ·cryptBlocksChain(SB),NOSPLIT,$48-48 + LA params-48(SP), R1 + MOVD iv+8(FP), R8 + MOVD key+16(FP), R9 + MVC $16, 0(R8), 0(R1) // move iv into params + MVC $32, 0(R9), 16(R1) // move key into params + MOVD dst+24(FP), R2 + MOVD src+32(FP), R4 + MOVD length+40(FP), R5 + MOVD c+0(FP), R0 +loop: + KMC R2, R4 // cipher message with chaining (KMC) + BVS loop // branch back if interrupted + XOR R0, R0 + MVC $16, 0(R1), 0(R8) // update iv + RET + diff --git a/crypto/internal/fips140/aes/aes_test.go b/crypto/internal/fips140/aes/aes_test.go new file mode 100644 index 00000000000..35046389135 --- /dev/null +++ b/crypto/internal/fips140/aes/aes_test.go @@ -0,0 +1,120 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aes + +import "testing" + +// See const.go for overview of math here. + +// Test that powx is initialized correctly. +// (Can adapt this code to generate it too.) +func TestPowx(t *testing.T) { + p := 1 + for i := 0; i < len(powx); i++ { + if powx[i] != byte(p) { + t.Errorf("powx[%d] = %#x, want %#x", i, powx[i], p) + } + p <<= 1 + if p&0x100 != 0 { + p ^= poly + } + } +} + +// Multiply b and c as GF(2) polynomials modulo poly +func mul(b, c uint32) uint32 { + i := b + j := c + s := uint32(0) + for k := uint32(1); k < 0x100 && j != 0; k <<= 1 { + // Invariant: k == 1<>8 + } + } +} + +// Test that decryption tables are correct. +// (Can adapt this code to generate them too.) +func TestTd(t *testing.T) { + for i := 0; i < 256; i++ { + s := uint32(sbox1[i]) + s9 := mul(s, 0x9) + sb := mul(s, 0xb) + sd := mul(s, 0xd) + se := mul(s, 0xe) + w := se<<24 | s9<<16 | sd<<8 | sb + td := [][256]uint32{td0, td1, td2, td3} + for j := 0; j < 4; j++ { + if x := td[j][i]; x != w { + t.Fatalf("td[%d][%d] = %#x, want %#x", j, i, x, w) + } + w = w<<24 | w>>8 + } + } +} diff --git a/crypto/internal/fips140/aes/cast.go b/crypto/internal/fips140/aes/cast.go new file mode 100644 index 00000000000..8b82c804278 --- /dev/null +++ b/crypto/internal/fips140/aes/cast.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aes + +import ( + "bytes" + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func init() { + fips140.CAST("AES-CBC", func() error { + key := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + iv := [16]byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + plaintext := []byte{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + } + ciphertext := []byte{ + 0xdf, 0x76, 0x26, 0x4b, 0xd3, 0xb2, 0xc4, 0x8d, + 0x40, 0xa2, 0x6e, 0x7a, 0xc4, 0xff, 0xbd, 0x35, + } + b, err := New(key) + if err != nil { + return err + } + buf := make([]byte, 16) + NewCBCEncrypter(b, iv).CryptBlocks(buf, plaintext) + if !bytes.Equal(buf, ciphertext) { + return errors.New("unexpected result") + } + NewCBCDecrypter(b, iv).CryptBlocks(buf, ciphertext) + if !bytes.Equal(buf, plaintext) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/aes/cbc.go b/crypto/internal/fips140/aes/cbc.go new file mode 100644 index 00000000000..b3e38cf72a2 --- /dev/null +++ b/crypto/internal/fips140/aes/cbc.go @@ -0,0 +1,130 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aes + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" +) + +type CBCEncrypter struct { + b Block + iv [BlockSize]byte +} + +// NewCBCEncrypter returns a [cipher.BlockMode] which encrypts in cipher block +// chaining mode, using the given Block. +func NewCBCEncrypter(b *Block, iv [BlockSize]byte) *CBCEncrypter { + return &CBCEncrypter{b: *b, iv: iv} +} + +func (c *CBCEncrypter) BlockSize() int { return BlockSize } + +func (c *CBCEncrypter) CryptBlocks(dst, src []byte) { + if len(src)%BlockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("crypto/cipher: invalid buffer overlap") + } + fips140.RecordApproved() + if len(src) == 0 { + return + } + cryptBlocksEnc(&c.b, &c.iv, dst, src) +} + +func (x *CBCEncrypter) SetIV(iv []byte) { + if len(iv) != len(x.iv) { + panic("cipher: incorrect length IV") + } + copy(x.iv[:], iv) +} + +func cryptBlocksEncGeneric(b *Block, civ *[BlockSize]byte, dst, src []byte) { + iv := civ[:] + for len(src) > 0 { + // Write the xor to dst, then encrypt in place. + subtle.XORBytes(dst[:BlockSize], src[:BlockSize], iv) + encryptBlock(b, dst[:BlockSize], dst[:BlockSize]) + + // Move to the next block with this block as the next iv. + iv = dst[:BlockSize] + src = src[BlockSize:] + dst = dst[BlockSize:] + } + + // Save the iv for the next CryptBlocks call. + copy(civ[:], iv) +} + +type CBCDecrypter struct { + b Block + iv [BlockSize]byte +} + +// NewCBCDecrypter returns a [cipher.BlockMode] which decrypts in cipher block +// chaining mode, using the given Block. +func NewCBCDecrypter(b *Block, iv [BlockSize]byte) *CBCDecrypter { + return &CBCDecrypter{b: *b, iv: iv} +} + +func (c *CBCDecrypter) BlockSize() int { return BlockSize } + +func (c *CBCDecrypter) CryptBlocks(dst, src []byte) { + if len(src)%BlockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + if alias.InexactOverlap(dst[:len(src)], src) { + panic("crypto/cipher: invalid buffer overlap") + } + fips140.RecordApproved() + if len(src) == 0 { + return + } + cryptBlocksDec(&c.b, &c.iv, dst, src) +} + +func (x *CBCDecrypter) SetIV(iv []byte) { + if len(iv) != len(x.iv) { + panic("cipher: incorrect length IV") + } + copy(x.iv[:], iv) +} + +func cryptBlocksDecGeneric(b *Block, civ *[BlockSize]byte, dst, src []byte) { + // For each block, we need to xor the decrypted data with the previous + // block's ciphertext (the iv). To avoid making a copy each time, we loop + // over the blocks backwards. + end := len(src) + start := end - BlockSize + prev := start - BlockSize + + // Copy the last block of ciphertext as the IV of the next call. + iv := *civ + copy(civ[:], src[start:end]) + + for start >= 0 { + decryptBlock(b, dst[start:end], src[start:end]) + + if start > 0 { + subtle.XORBytes(dst[start:end], dst[start:end], src[prev:start]) + } else { + // The first block is special because it uses the saved iv. + subtle.XORBytes(dst[start:end], dst[start:end], iv[:]) + } + + end -= BlockSize + start -= BlockSize + prev -= BlockSize + } +} diff --git a/crypto/internal/fips140/aes/cbc_noasm.go b/crypto/internal/fips140/aes/cbc_noasm.go new file mode 100644 index 00000000000..fd10c2e99fe --- /dev/null +++ b/crypto/internal/fips140/aes/cbc_noasm.go @@ -0,0 +1,15 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!s390x && !ppc64 && !ppc64le) || purego + +package aes + +func cryptBlocksEnc(b *Block, civ *[BlockSize]byte, dst, src []byte) { + cryptBlocksEncGeneric(b, civ, dst, src) +} + +func cryptBlocksDec(b *Block, civ *[BlockSize]byte, dst, src []byte) { + cryptBlocksDecGeneric(b, civ, dst, src) +} diff --git a/crypto/internal/fips140/aes/cbc_ppc64x.go b/crypto/internal/fips140/aes/cbc_ppc64x.go new file mode 100644 index 00000000000..460bae3d497 --- /dev/null +++ b/crypto/internal/fips140/aes/cbc_ppc64x.go @@ -0,0 +1,31 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (ppc64 || ppc64le) && !purego + +package aes + +// cryptBlocksChain invokes the cipher message identifying encrypt or decrypt. +// +//go:noescape +func cryptBlocksChain(src, dst *byte, length int, key *uint32, iv *byte, enc int, nr int) + +const cbcEncrypt = 1 +const cbcDecrypt = 0 + +func cryptBlocksEnc(b *Block, civ *[BlockSize]byte, dst, src []byte) { + if !supportsAES { + cryptBlocksEncGeneric(b, civ, dst, src) + } else { + cryptBlocksChain(&src[0], &dst[0], len(src), &b.enc[0], &civ[0], cbcEncrypt, b.rounds) + } +} + +func cryptBlocksDec(b *Block, civ *[BlockSize]byte, dst, src []byte) { + if !supportsAES { + cryptBlocksDecGeneric(b, civ, dst, src) + } else { + cryptBlocksChain(&src[0], &dst[0], len(src), &b.dec[0], &civ[0], cbcDecrypt, b.rounds) + } +} diff --git a/crypto/internal/fips140/aes/cbc_s390x.go b/crypto/internal/fips140/aes/cbc_s390x.go new file mode 100644 index 00000000000..b4eb997a60c --- /dev/null +++ b/crypto/internal/fips140/aes/cbc_s390x.go @@ -0,0 +1,30 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package aes + +// cryptBlocksChain invokes the cipher message with chaining (KMC) instruction +// with the given function code. The length must be a multiple of BlockSize (16). +// +//go:noescape +func cryptBlocksChain(c code, iv, key, dst, src *byte, length int) + +func cryptBlocksEnc(b *Block, civ *[BlockSize]byte, dst, src []byte) { + if b.fallback != nil { + cryptBlocksEncGeneric(b, civ, dst, src) + return + } + cryptBlocksChain(b.function, &civ[0], &b.key[0], &dst[0], &src[0], len(src)) +} + +func cryptBlocksDec(b *Block, civ *[BlockSize]byte, dst, src []byte) { + if b.fallback != nil { + cryptBlocksDecGeneric(b, civ, dst, src) + return + } + // Decrypt function code is encrypt + 128. + cryptBlocksChain(b.function+128, &civ[0], &b.key[0], &dst[0], &src[0], len(src)) +} diff --git a/crypto/aes/const.go b/crypto/internal/fips140/aes/const.go similarity index 97% rename from crypto/aes/const.go rename to crypto/internal/fips140/aes/const.go index 4eca4b9aff8..3ecc922b5a4 100644 --- a/crypto/aes/const.go +++ b/crypto/internal/fips140/aes/const.go @@ -2,15 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package aes implements AES encryption (formerly Rijndael), as defined in -// U.S. Federal Information Processing Standards Publication 197. -// -// The AES operations in this package are not implemented using constant-time algorithms. -// An exception is when running on systems with enabled hardware support for AES -// that makes these operations constant-time. Examples include amd64 systems using AES-NI -// extensions and s390x systems using Message-Security-Assist extensions. -// On such systems, when the result of NewCipher is passed to cipher.NewGCM, -// the GHASH operation used by GCM is also constant-time. package aes // This file contains AES constants - 8720 bytes of initialized data. diff --git a/crypto/internal/fips140/aes/ctr.go b/crypto/internal/fips140/aes/ctr.go new file mode 100644 index 00000000000..699a30e07ff --- /dev/null +++ b/crypto/internal/fips140/aes/ctr.go @@ -0,0 +1,149 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aes + +import ( + "math/bits" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +type CTR struct { + b Block + ivlo, ivhi uint64 // start counter as 64-bit limbs + offset uint64 // for XORKeyStream only +} + +func NewCTR(b *Block, iv []byte) *CTR { + // Allocate the CTR here, in an easily inlineable function, so + // the allocation can be done in the caller's stack frame + // instead of the heap. See issue 70499. + c := newCTR(b, iv) + return &c +} +func newCTR(b *Block, iv []byte) CTR { + if len(iv) != BlockSize { + panic("bad IV length") + } + + return CTR{ + b: *b, + ivlo: byteorder.BEUint64(iv[8:16]), + ivhi: byteorder.BEUint64(iv[0:8]), + offset: 0, + } +} + +func (c *CTR) XORKeyStream(dst, src []byte) { + c.XORKeyStreamAt(dst, src, c.offset) + + var carry uint64 + c.offset, carry = bits.Add64(c.offset, uint64(len(src)), 0) + if carry != 0 { + panic("crypto/aes: counter overflow") + } +} + +// RoundToBlock is used by CTR_DRBG, which discards the rightmost unused bits at +// each request. It rounds the offset up to the next block boundary. +func RoundToBlock(c *CTR) { + if remainder := c.offset % BlockSize; remainder != 0 { + var carry uint64 + c.offset, carry = bits.Add64(c.offset, BlockSize-remainder, 0) + if carry != 0 { + panic("crypto/aes: counter overflow") + } + } +} + +// XORKeyStreamAt behaves like XORKeyStream but keeps no state, and instead +// seeks into the keystream by the given bytes offset from the start (ignoring +// any XORKetStream calls). This allows for random access into the keystream, up +// to 16 EiB from the start. +func (c *CTR) XORKeyStreamAt(dst, src []byte, offset uint64) { + if len(dst) < len(src) { + panic("crypto/aes: len(dst) < len(src)") + } + dst = dst[:len(src)] + if alias.InexactOverlap(dst, src) { + panic("crypto/aes: invalid buffer overlap") + } + fips140.RecordApproved() + + ivlo, ivhi := add128(c.ivlo, c.ivhi, offset/BlockSize) + + if blockOffset := offset % BlockSize; blockOffset != 0 { + // We have a partial block at the beginning. + var in, out [BlockSize]byte + copy(in[blockOffset:], src) + ctrBlocks1(&c.b, &out, &in, ivlo, ivhi) + n := copy(dst, out[blockOffset:]) + src = src[n:] + dst = dst[n:] + ivlo, ivhi = add128(ivlo, ivhi, 1) + } + + for len(src) >= 8*BlockSize { + ctrBlocks8(&c.b, (*[8 * BlockSize]byte)(dst), (*[8 * BlockSize]byte)(src), ivlo, ivhi) + src = src[8*BlockSize:] + dst = dst[8*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 8) + } + + // The tail can have at most 7 = 4 + 2 + 1 blocks. + if len(src) >= 4*BlockSize { + ctrBlocks4(&c.b, (*[4 * BlockSize]byte)(dst), (*[4 * BlockSize]byte)(src), ivlo, ivhi) + src = src[4*BlockSize:] + dst = dst[4*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 4) + } + if len(src) >= 2*BlockSize { + ctrBlocks2(&c.b, (*[2 * BlockSize]byte)(dst), (*[2 * BlockSize]byte)(src), ivlo, ivhi) + src = src[2*BlockSize:] + dst = dst[2*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 2) + } + if len(src) >= 1*BlockSize { + ctrBlocks1(&c.b, (*[1 * BlockSize]byte)(dst), (*[1 * BlockSize]byte)(src), ivlo, ivhi) + src = src[1*BlockSize:] + dst = dst[1*BlockSize:] + ivlo, ivhi = add128(ivlo, ivhi, 1) + } + + if len(src) != 0 { + // We have a partial block at the end. + var in, out [BlockSize]byte + copy(in[:], src) + ctrBlocks1(&c.b, &out, &in, ivlo, ivhi) + copy(dst, out[:]) + } +} + +// Each ctrBlocksN function XORs src with N blocks of counter keystream, and +// stores it in dst. src is loaded in full before storing dst, so they can +// overlap even inexactly. The starting counter value is passed in as a pair of +// little-endian 64-bit integers. + +func ctrBlocks(b *Block, dst, src []byte, ivlo, ivhi uint64) { + buf := make([]byte, len(src), 8*BlockSize) + for i := 0; i < len(buf); i += BlockSize { + byteorder.BEPutUint64(buf[i:], ivhi) + byteorder.BEPutUint64(buf[i+8:], ivlo) + ivlo, ivhi = add128(ivlo, ivhi, 1) + encryptBlock(b, buf[i:], buf[i:]) + } + // XOR into buf first, in case src and dst overlap (see above). + subtle.XORBytes(buf, src, buf) + copy(dst, buf) +} + +func add128(lo, hi uint64, x uint64) (uint64, uint64) { + lo, c := bits.Add64(lo, x, 0) + hi, _ = bits.Add64(hi, 0, c) + return lo, hi +} diff --git a/crypto/internal/fips140/aes/ctr_amd64.s b/crypto/internal/fips140/aes/ctr_amd64.s new file mode 100644 index 00000000000..e6710834dd2 --- /dev/null +++ b/crypto/internal/fips140/aes/ctr_amd64.s @@ -0,0 +1,494 @@ +// Code generated by command: go run ctr_amd64_asm.go -out ../../ctr_amd64.s. DO NOT EDIT. + +//go:build !purego + +#include "textflag.h" + +// func ctrBlocks1Asm(nr int, xk *[60]uint32, dst *[16]byte, src *[16]byte, ivlo uint64, ivhi uint64) +// Requires: AES, SSE, SSE2, SSE4.1, SSSE3 +TEXT ·ctrBlocks1Asm(SB), $0-48 + MOVQ nr+0(FP), AX + MOVQ xk+8(FP), CX + MOVQ dst+16(FP), DX + MOVQ src+24(FP), BX + MOVQ ivlo+32(FP), SI + MOVQ ivhi+40(FP), DI + MOVOU bswapMask<>+0(SB), X0 + MOVQ SI, X1 + PINSRQ $0x01, DI, X1 + PSHUFB X0, X1 + MOVUPS (CX), X0 + PXOR X0, X1 + ADDQ $0x10, CX + SUBQ $0x0c, AX + JE enc192 + JB enc128 + MOVUPS (CX), X0 + AESENC X0, X1 + MOVUPS 16(CX), X0 + AESENC X0, X1 + ADDQ $0x20, CX + +enc192: + MOVUPS (CX), X0 + AESENC X0, X1 + MOVUPS 16(CX), X0 + AESENC X0, X1 + ADDQ $0x20, CX + +enc128: + MOVUPS (CX), X0 + AESENC X0, X1 + MOVUPS 16(CX), X0 + AESENC X0, X1 + MOVUPS 32(CX), X0 + AESENC X0, X1 + MOVUPS 48(CX), X0 + AESENC X0, X1 + MOVUPS 64(CX), X0 + AESENC X0, X1 + MOVUPS 80(CX), X0 + AESENC X0, X1 + MOVUPS 96(CX), X0 + AESENC X0, X1 + MOVUPS 112(CX), X0 + AESENC X0, X1 + MOVUPS 128(CX), X0 + AESENC X0, X1 + MOVUPS 144(CX), X0 + AESENCLAST X0, X1 + MOVUPS (BX), X0 + PXOR X1, X0 + MOVUPS X0, (DX) + RET + +DATA bswapMask<>+0(SB)/8, $0x08090a0b0c0d0e0f +DATA bswapMask<>+8(SB)/8, $0x0001020304050607 +GLOBL bswapMask<>(SB), RODATA|NOPTR, $16 + +// func ctrBlocks2Asm(nr int, xk *[60]uint32, dst *[32]byte, src *[32]byte, ivlo uint64, ivhi uint64) +// Requires: AES, SSE, SSE2, SSE4.1, SSSE3 +TEXT ·ctrBlocks2Asm(SB), $0-48 + MOVQ nr+0(FP), AX + MOVQ xk+8(FP), CX + MOVQ dst+16(FP), DX + MOVQ src+24(FP), BX + MOVQ ivlo+32(FP), SI + MOVQ ivhi+40(FP), DI + MOVOU bswapMask<>+0(SB), X0 + MOVQ SI, X1 + PINSRQ $0x01, DI, X1 + PSHUFB X0, X1 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X2 + PINSRQ $0x01, DI, X2 + PSHUFB X0, X2 + MOVUPS (CX), X0 + PXOR X0, X1 + PXOR X0, X2 + ADDQ $0x10, CX + SUBQ $0x0c, AX + JE enc192 + JB enc128 + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + ADDQ $0x20, CX + +enc192: + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + ADDQ $0x20, CX + +enc128: + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 32(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 48(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 64(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 80(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 96(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 112(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 128(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + MOVUPS 144(CX), X0 + AESENCLAST X0, X1 + AESENCLAST X0, X2 + MOVUPS (BX), X0 + PXOR X1, X0 + MOVUPS X0, (DX) + MOVUPS 16(BX), X0 + PXOR X2, X0 + MOVUPS X0, 16(DX) + RET + +// func ctrBlocks4Asm(nr int, xk *[60]uint32, dst *[64]byte, src *[64]byte, ivlo uint64, ivhi uint64) +// Requires: AES, SSE, SSE2, SSE4.1, SSSE3 +TEXT ·ctrBlocks4Asm(SB), $0-48 + MOVQ nr+0(FP), AX + MOVQ xk+8(FP), CX + MOVQ dst+16(FP), DX + MOVQ src+24(FP), BX + MOVQ ivlo+32(FP), SI + MOVQ ivhi+40(FP), DI + MOVOU bswapMask<>+0(SB), X0 + MOVQ SI, X1 + PINSRQ $0x01, DI, X1 + PSHUFB X0, X1 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X2 + PINSRQ $0x01, DI, X2 + PSHUFB X0, X2 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X3 + PINSRQ $0x01, DI, X3 + PSHUFB X0, X3 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X4 + PINSRQ $0x01, DI, X4 + PSHUFB X0, X4 + MOVUPS (CX), X0 + PXOR X0, X1 + PXOR X0, X2 + PXOR X0, X3 + PXOR X0, X4 + ADDQ $0x10, CX + SUBQ $0x0c, AX + JE enc192 + JB enc128 + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + ADDQ $0x20, CX + +enc192: + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + ADDQ $0x20, CX + +enc128: + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 32(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 48(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 64(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 80(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 96(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 112(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 128(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + MOVUPS 144(CX), X0 + AESENCLAST X0, X1 + AESENCLAST X0, X2 + AESENCLAST X0, X3 + AESENCLAST X0, X4 + MOVUPS (BX), X0 + PXOR X1, X0 + MOVUPS X0, (DX) + MOVUPS 16(BX), X0 + PXOR X2, X0 + MOVUPS X0, 16(DX) + MOVUPS 32(BX), X0 + PXOR X3, X0 + MOVUPS X0, 32(DX) + MOVUPS 48(BX), X0 + PXOR X4, X0 + MOVUPS X0, 48(DX) + RET + +// func ctrBlocks8Asm(nr int, xk *[60]uint32, dst *[128]byte, src *[128]byte, ivlo uint64, ivhi uint64) +// Requires: AES, SSE, SSE2, SSE4.1, SSSE3 +TEXT ·ctrBlocks8Asm(SB), $0-48 + MOVQ nr+0(FP), AX + MOVQ xk+8(FP), CX + MOVQ dst+16(FP), DX + MOVQ src+24(FP), BX + MOVQ ivlo+32(FP), SI + MOVQ ivhi+40(FP), DI + MOVOU bswapMask<>+0(SB), X0 + MOVQ SI, X1 + PINSRQ $0x01, DI, X1 + PSHUFB X0, X1 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X2 + PINSRQ $0x01, DI, X2 + PSHUFB X0, X2 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X3 + PINSRQ $0x01, DI, X3 + PSHUFB X0, X3 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X4 + PINSRQ $0x01, DI, X4 + PSHUFB X0, X4 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X5 + PINSRQ $0x01, DI, X5 + PSHUFB X0, X5 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X6 + PINSRQ $0x01, DI, X6 + PSHUFB X0, X6 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X7 + PINSRQ $0x01, DI, X7 + PSHUFB X0, X7 + ADDQ $0x01, SI + ADCQ $0x00, DI + MOVQ SI, X8 + PINSRQ $0x01, DI, X8 + PSHUFB X0, X8 + MOVUPS (CX), X0 + PXOR X0, X1 + PXOR X0, X2 + PXOR X0, X3 + PXOR X0, X4 + PXOR X0, X5 + PXOR X0, X6 + PXOR X0, X7 + PXOR X0, X8 + ADDQ $0x10, CX + SUBQ $0x0c, AX + JE enc192 + JB enc128 + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + ADDQ $0x20, CX + +enc192: + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + ADDQ $0x20, CX + +enc128: + MOVUPS (CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 16(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 32(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 48(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 64(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 80(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 96(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 112(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 128(CX), X0 + AESENC X0, X1 + AESENC X0, X2 + AESENC X0, X3 + AESENC X0, X4 + AESENC X0, X5 + AESENC X0, X6 + AESENC X0, X7 + AESENC X0, X8 + MOVUPS 144(CX), X0 + AESENCLAST X0, X1 + AESENCLAST X0, X2 + AESENCLAST X0, X3 + AESENCLAST X0, X4 + AESENCLAST X0, X5 + AESENCLAST X0, X6 + AESENCLAST X0, X7 + AESENCLAST X0, X8 + MOVUPS (BX), X0 + PXOR X1, X0 + MOVUPS X0, (DX) + MOVUPS 16(BX), X0 + PXOR X2, X0 + MOVUPS X0, 16(DX) + MOVUPS 32(BX), X0 + PXOR X3, X0 + MOVUPS X0, 32(DX) + MOVUPS 48(BX), X0 + PXOR X4, X0 + MOVUPS X0, 48(DX) + MOVUPS 64(BX), X0 + PXOR X5, X0 + MOVUPS X0, 64(DX) + MOVUPS 80(BX), X0 + PXOR X6, X0 + MOVUPS X0, 80(DX) + MOVUPS 96(BX), X0 + PXOR X7, X0 + MOVUPS X0, 96(DX) + MOVUPS 112(BX), X0 + PXOR X8, X0 + MOVUPS X0, 112(DX) + RET diff --git a/crypto/internal/fips140/aes/ctr_arm64.s b/crypto/internal/fips140/aes/ctr_arm64.s new file mode 100644 index 00000000000..fc4ab4eaada --- /dev/null +++ b/crypto/internal/fips140/aes/ctr_arm64.s @@ -0,0 +1,729 @@ +// Code generated by ctr_arm64_gen.go. DO NOT EDIT. + +//go:build !purego + +#include "textflag.h" + +#define NR R9 +#define XK R10 +#define DST R11 +#define SRC R12 +#define IV_LOW_LE R16 +#define IV_HIGH_LE R17 +#define IV_LOW_BE R19 +#define IV_HIGH_BE R20 + +// V0.B16 - V7.B16 are for blocks (<=8). See BLOCK_OFFSET. +// V8.B16 - V22.B16 are for <=15 round keys (<=15). See ROUND_KEY_OFFSET. +// V23.B16 - V30.B16 are for destinations (<=8). See DST_OFFSET. + +// func ctrBlocks1Asm(nr int, xk *[60]uint32, dst *[1*16]byte, src *[1*16]byte, ivlo uint64, ivhi uint64) +TEXT ·ctrBlocks1Asm(SB), NOSPLIT, $0 + MOVD nr+0(FP), NR + MOVD xk+8(FP), XK + MOVD dst+16(FP), DST + MOVD src+24(FP), SRC + MOVD ivlo+32(FP), IV_LOW_LE + MOVD ivhi+40(FP), IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V0.D[1] + VMOV IV_HIGH_BE, V0.D[0] + + CMP $12, NR + BLT Lenc128 + BEQ Lenc192 + +Lenc256: + VLD1.P 32(XK), [V8.B16, V9.B16] + + AESE V8.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V9.B16, V0.B16 + AESMC V0.B16, V0.B16 + +Lenc192: + VLD1.P 32(XK), [V10.B16, V11.B16] + + AESE V10.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V11.B16, V0.B16 + AESMC V0.B16, V0.B16 + +Lenc128: + VLD1.P 64(XK), [V12.B16, V13.B16, V14.B16, V15.B16] + VLD1.P 64(XK), [V16.B16, V17.B16, V18.B16, V19.B16] + VLD1.P 48(XK), [V20.B16, V21.B16, V22.B16] + + AESE V12.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V13.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V14.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V15.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V16.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V17.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V18.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V19.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V20.B16, V0.B16 + AESMC V0.B16, V0.B16 + + AESE V21.B16, V0.B16 + + VEOR V0.B16, V22.B16, V0.B16 + + VLD1.P 16(SRC), [V23.B16] + VEOR V23.B16, V0.B16, V23.B16 + VST1.P [V23.B16], 16(DST) + + RET + +// func ctrBlocks2Asm(nr int, xk *[60]uint32, dst *[2*16]byte, src *[2*16]byte, ivlo uint64, ivhi uint64) +TEXT ·ctrBlocks2Asm(SB), NOSPLIT, $0 + MOVD nr+0(FP), NR + MOVD xk+8(FP), XK + MOVD dst+16(FP), DST + MOVD src+24(FP), SRC + MOVD ivlo+32(FP), IV_LOW_LE + MOVD ivhi+40(FP), IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V0.D[1] + VMOV IV_HIGH_BE, V0.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V1.D[1] + VMOV IV_HIGH_BE, V1.D[0] + + CMP $12, NR + BLT Lenc128 + BEQ Lenc192 + +Lenc256: + VLD1.P 32(XK), [V8.B16, V9.B16] + + AESE V8.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V8.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V9.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V9.B16, V1.B16 + AESMC V1.B16, V1.B16 + +Lenc192: + VLD1.P 32(XK), [V10.B16, V11.B16] + + AESE V10.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V10.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V11.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V11.B16, V1.B16 + AESMC V1.B16, V1.B16 + +Lenc128: + VLD1.P 64(XK), [V12.B16, V13.B16, V14.B16, V15.B16] + VLD1.P 64(XK), [V16.B16, V17.B16, V18.B16, V19.B16] + VLD1.P 48(XK), [V20.B16, V21.B16, V22.B16] + + AESE V12.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V12.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V13.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V13.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V14.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V14.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V15.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V15.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V16.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V16.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V17.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V17.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V18.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V18.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V19.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V19.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V20.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V20.B16, V1.B16 + AESMC V1.B16, V1.B16 + + AESE V21.B16, V0.B16 + AESE V21.B16, V1.B16 + + VEOR V0.B16, V22.B16, V0.B16 + VEOR V1.B16, V22.B16, V1.B16 + + VLD1.P 32(SRC), [V23.B16, V24.B16] + VEOR V23.B16, V0.B16, V23.B16 + VEOR V24.B16, V1.B16, V24.B16 + VST1.P [V23.B16, V24.B16], 32(DST) + + RET + +// func ctrBlocks4Asm(nr int, xk *[60]uint32, dst *[4*16]byte, src *[4*16]byte, ivlo uint64, ivhi uint64) +TEXT ·ctrBlocks4Asm(SB), NOSPLIT, $0 + MOVD nr+0(FP), NR + MOVD xk+8(FP), XK + MOVD dst+16(FP), DST + MOVD src+24(FP), SRC + MOVD ivlo+32(FP), IV_LOW_LE + MOVD ivhi+40(FP), IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V0.D[1] + VMOV IV_HIGH_BE, V0.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V1.D[1] + VMOV IV_HIGH_BE, V1.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V2.D[1] + VMOV IV_HIGH_BE, V2.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V3.D[1] + VMOV IV_HIGH_BE, V3.D[0] + + CMP $12, NR + BLT Lenc128 + BEQ Lenc192 + +Lenc256: + VLD1.P 32(XK), [V8.B16, V9.B16] + + AESE V8.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V8.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V8.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V8.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V9.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V9.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V9.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V9.B16, V3.B16 + AESMC V3.B16, V3.B16 + +Lenc192: + VLD1.P 32(XK), [V10.B16, V11.B16] + + AESE V10.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V10.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V10.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V10.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V11.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V11.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V11.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V11.B16, V3.B16 + AESMC V3.B16, V3.B16 + +Lenc128: + VLD1.P 64(XK), [V12.B16, V13.B16, V14.B16, V15.B16] + VLD1.P 64(XK), [V16.B16, V17.B16, V18.B16, V19.B16] + VLD1.P 48(XK), [V20.B16, V21.B16, V22.B16] + + AESE V12.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V12.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V12.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V12.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V13.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V13.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V13.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V13.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V14.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V14.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V14.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V14.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V15.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V15.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V15.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V15.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V16.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V16.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V16.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V16.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V17.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V17.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V17.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V17.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V18.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V18.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V18.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V18.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V19.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V19.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V19.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V19.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V20.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V20.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V20.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V20.B16, V3.B16 + AESMC V3.B16, V3.B16 + + AESE V21.B16, V0.B16 + AESE V21.B16, V1.B16 + AESE V21.B16, V2.B16 + AESE V21.B16, V3.B16 + + VEOR V0.B16, V22.B16, V0.B16 + VEOR V1.B16, V22.B16, V1.B16 + VEOR V2.B16, V22.B16, V2.B16 + VEOR V3.B16, V22.B16, V3.B16 + + VLD1.P 64(SRC), [V23.B16, V24.B16, V25.B16, V26.B16] + VEOR V23.B16, V0.B16, V23.B16 + VEOR V24.B16, V1.B16, V24.B16 + VEOR V25.B16, V2.B16, V25.B16 + VEOR V26.B16, V3.B16, V26.B16 + VST1.P [V23.B16, V24.B16, V25.B16, V26.B16], 64(DST) + + RET + +// func ctrBlocks8Asm(nr int, xk *[60]uint32, dst *[8*16]byte, src *[8*16]byte, ivlo uint64, ivhi uint64) +TEXT ·ctrBlocks8Asm(SB), NOSPLIT, $0 + MOVD nr+0(FP), NR + MOVD xk+8(FP), XK + MOVD dst+16(FP), DST + MOVD src+24(FP), SRC + MOVD ivlo+32(FP), IV_LOW_LE + MOVD ivhi+40(FP), IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V0.D[1] + VMOV IV_HIGH_BE, V0.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V1.D[1] + VMOV IV_HIGH_BE, V1.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V2.D[1] + VMOV IV_HIGH_BE, V2.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V3.D[1] + VMOV IV_HIGH_BE, V3.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V4.D[1] + VMOV IV_HIGH_BE, V4.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V5.D[1] + VMOV IV_HIGH_BE, V5.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V6.D[1] + VMOV IV_HIGH_BE, V6.D[0] + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + VMOV IV_LOW_BE, V7.D[1] + VMOV IV_HIGH_BE, V7.D[0] + + CMP $12, NR + BLT Lenc128 + BEQ Lenc192 + +Lenc256: + VLD1.P 32(XK), [V8.B16, V9.B16] + + AESE V8.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V8.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V8.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V8.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V8.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V8.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V8.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V8.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V9.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V9.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V9.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V9.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V9.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V9.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V9.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V9.B16, V7.B16 + AESMC V7.B16, V7.B16 + +Lenc192: + VLD1.P 32(XK), [V10.B16, V11.B16] + + AESE V10.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V10.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V10.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V10.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V10.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V10.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V10.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V10.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V11.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V11.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V11.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V11.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V11.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V11.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V11.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V11.B16, V7.B16 + AESMC V7.B16, V7.B16 + +Lenc128: + VLD1.P 64(XK), [V12.B16, V13.B16, V14.B16, V15.B16] + VLD1.P 64(XK), [V16.B16, V17.B16, V18.B16, V19.B16] + VLD1.P 48(XK), [V20.B16, V21.B16, V22.B16] + + AESE V12.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V12.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V12.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V12.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V12.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V12.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V12.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V12.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V13.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V13.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V13.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V13.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V13.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V13.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V13.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V13.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V14.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V14.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V14.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V14.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V14.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V14.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V14.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V14.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V15.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V15.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V15.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V15.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V15.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V15.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V15.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V15.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V16.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V16.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V16.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V16.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V16.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V16.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V16.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V16.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V17.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V17.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V17.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V17.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V17.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V17.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V17.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V17.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V18.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V18.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V18.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V18.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V18.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V18.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V18.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V18.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V19.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V19.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V19.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V19.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V19.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V19.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V19.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V19.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V20.B16, V0.B16 + AESMC V0.B16, V0.B16 + AESE V20.B16, V1.B16 + AESMC V1.B16, V1.B16 + AESE V20.B16, V2.B16 + AESMC V2.B16, V2.B16 + AESE V20.B16, V3.B16 + AESMC V3.B16, V3.B16 + AESE V20.B16, V4.B16 + AESMC V4.B16, V4.B16 + AESE V20.B16, V5.B16 + AESMC V5.B16, V5.B16 + AESE V20.B16, V6.B16 + AESMC V6.B16, V6.B16 + AESE V20.B16, V7.B16 + AESMC V7.B16, V7.B16 + + AESE V21.B16, V0.B16 + AESE V21.B16, V1.B16 + AESE V21.B16, V2.B16 + AESE V21.B16, V3.B16 + AESE V21.B16, V4.B16 + AESE V21.B16, V5.B16 + AESE V21.B16, V6.B16 + AESE V21.B16, V7.B16 + + VEOR V0.B16, V22.B16, V0.B16 + VEOR V1.B16, V22.B16, V1.B16 + VEOR V2.B16, V22.B16, V2.B16 + VEOR V3.B16, V22.B16, V3.B16 + VEOR V4.B16, V22.B16, V4.B16 + VEOR V5.B16, V22.B16, V5.B16 + VEOR V6.B16, V22.B16, V6.B16 + VEOR V7.B16, V22.B16, V7.B16 + + VLD1.P 64(SRC), [V23.B16, V24.B16, V25.B16, V26.B16] + VLD1.P 64(SRC), [V27.B16, V28.B16, V29.B16, V30.B16] + VEOR V23.B16, V0.B16, V23.B16 + VEOR V24.B16, V1.B16, V24.B16 + VEOR V25.B16, V2.B16, V25.B16 + VEOR V26.B16, V3.B16, V26.B16 + VEOR V27.B16, V4.B16, V27.B16 + VEOR V28.B16, V5.B16, V28.B16 + VEOR V29.B16, V6.B16, V29.B16 + VEOR V30.B16, V7.B16, V30.B16 + VST1.P [V23.B16, V24.B16, V25.B16, V26.B16], 64(DST) + VST1.P [V27.B16, V28.B16, V29.B16, V30.B16], 64(DST) + + RET + diff --git a/crypto/internal/fips140/aes/ctr_arm64_gen.go b/crypto/internal/fips140/aes/ctr_arm64_gen.go new file mode 100644 index 00000000000..1c032083c35 --- /dev/null +++ b/crypto/internal/fips140/aes/ctr_arm64_gen.go @@ -0,0 +1,213 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +// Generate Go assembly for XORing CTR output to n blocks at once with one key. +package main + +import ( + "fmt" + "os" + "strings" + "text/template" +) + +// First registers in their groups. +const ( + blockOffset = 0 + roundKeyOffset = 8 + dstOffset = 23 +) + +var tmplArm64Str = ` +// Code generated by ctr_arm64_gen.go. DO NOT EDIT. + +//go:build !purego + +#include "textflag.h" + +#define NR R9 +#define XK R10 +#define DST R11 +#define SRC R12 +#define IV_LOW_LE R16 +#define IV_HIGH_LE R17 +#define IV_LOW_BE R19 +#define IV_HIGH_BE R20 + +// V0.B16 - V7.B16 are for blocks (<=8). See BLOCK_OFFSET. +// V8.B16 - V22.B16 are for <=15 round keys (<=15). See ROUND_KEY_OFFSET. +// V23.B16 - V30.B16 are for destinations (<=8). See DST_OFFSET. + +{{define "load_keys"}} + {{- range regs_batches (round_key_reg $.FirstKey) $.NKeys }} + VLD1.P {{ .Size }}(XK), [{{ .Regs }}] + {{- end }} +{{ end }} + +{{define "enc"}} + {{ range $i := xrange $.N -}} + AESE V{{ round_key_reg $.Key}}.B16, V{{ block_reg $i }}.B16 + {{- if $.WithMc }} + AESMC V{{ block_reg $i }}.B16, V{{ block_reg $i }}.B16 + {{- end }} + {{ end }} +{{ end }} + +{{ range $N := $.Sizes }} +// func ctrBlocks{{$N}}Asm(nr int, xk *[60]uint32, dst *[{{$N}}*16]byte, src *[{{$N}}*16]byte, ivlo uint64, ivhi uint64) +TEXT ·ctrBlocks{{ $N }}Asm(SB),NOSPLIT,$0 + MOVD nr+0(FP), NR + MOVD xk+8(FP), XK + MOVD dst+16(FP), DST + MOVD src+24(FP), SRC + MOVD ivlo+32(FP), IV_LOW_LE + MOVD ivhi+40(FP), IV_HIGH_LE + + {{/* Prepare plain from IV and blockIndex. */}} + + {{/* Copy to plaintext registers. */}} + {{ range $i := xrange $N }} + REV IV_LOW_LE, IV_LOW_BE + REV IV_HIGH_LE, IV_HIGH_BE + {{- /* https://developer.arm.com/documentation/dui0801/g/A64-SIMD-Vector-Instructions/MOV--vector--from-general- */}} + VMOV IV_LOW_BE, V{{ block_reg $i }}.D[1] + VMOV IV_HIGH_BE, V{{ block_reg $i }}.D[0] + {{- if ne (add $i 1) $N }} + ADDS $1, IV_LOW_LE + ADC $0, IV_HIGH_LE + {{ end }} + {{ end }} + + {{/* Num rounds branching. */}} + CMP $12, NR + BLT Lenc128 + BEQ Lenc192 + + {{/* 2 extra rounds for 256-bit keys. */}} + Lenc256: + {{- template "load_keys" (load_keys_args 0 2) }} + {{- template "enc" (enc_args 0 $N true) }} + {{- template "enc" (enc_args 1 $N true) }} + + {{/* 2 extra rounds for 192-bit keys. */}} + Lenc192: + {{- template "load_keys" (load_keys_args 2 2) }} + {{- template "enc" (enc_args 2 $N true) }} + {{- template "enc" (enc_args 3 $N true) }} + + {{/* 10 rounds for 128-bit (with special handling for final). */}} + Lenc128: + {{- template "load_keys" (load_keys_args 4 11) }} + {{- range $r := xrange 9 }} + {{- template "enc" (enc_args (add $r 4) $N true) }} + {{ end }} + {{ template "enc" (enc_args 13 $N false) }} + + {{/* We need to XOR blocks with the last round key (key 14, register V22). */}} + {{ range $i := xrange $N }} + VEOR V{{ block_reg $i }}.B16, V{{ round_key_reg 14 }}.B16, V{{ block_reg $i }}.B16 + {{- end }} + + {{/* XOR results to destination. */}} + {{- range regs_batches $.DstOffset $N }} + VLD1.P {{ .Size }}(SRC), [{{ .Regs }}] + {{- end }} + {{- range $i := xrange $N }} + VEOR V{{ add $.DstOffset $i }}.B16, V{{ block_reg $i }}.B16, V{{ add $.DstOffset $i }}.B16 + {{- end }} + {{- range regs_batches $.DstOffset $N }} + VST1.P [{{ .Regs }}], {{ .Size }}(DST) + {{- end }} + + RET +{{ end }} +` + +func main() { + type Params struct { + DstOffset int + Sizes []int + } + + params := Params{ + DstOffset: dstOffset, + Sizes: []int{1, 2, 4, 8}, + } + + type RegsBatch struct { + Size int + Regs string // Comma-separated list of registers. + } + + type LoadKeysArgs struct { + FirstKey int + NKeys int + } + + type EncArgs struct { + Key int + N int + WithMc bool + } + + funcs := template.FuncMap{ + "add": func(a, b int) int { + return a + b + }, + "xrange": func(n int) []int { + result := make([]int, n) + for i := 0; i < n; i++ { + result[i] = i + } + return result + }, + "block_reg": func(block int) int { + return blockOffset + block + }, + "round_key_reg": func(key int) int { + return roundKeyOffset + key + }, + "regs_batches": func(firstReg, nregs int) []RegsBatch { + result := make([]RegsBatch, 0) + for nregs != 0 { + batch := 4 + if nregs < batch { + batch = nregs + } + regsList := make([]string, 0, batch) + for j := firstReg; j < firstReg+batch; j++ { + regsList = append(regsList, fmt.Sprintf("V%d.B16", j)) + } + result = append(result, RegsBatch{ + Size: 16 * batch, + Regs: strings.Join(regsList, ", "), + }) + nregs -= batch + firstReg += batch + } + return result + }, + "enc_args": func(key, n int, withMc bool) EncArgs { + return EncArgs{ + Key: key, + N: n, + WithMc: withMc, + } + }, + "load_keys_args": func(firstKey, nkeys int) LoadKeysArgs { + return LoadKeysArgs{ + FirstKey: firstKey, + NKeys: nkeys, + } + }, + } + + var tmpl = template.Must(template.New("ctr_arm64").Funcs(funcs).Parse(tmplArm64Str)) + + if err := tmpl.Execute(os.Stdout, params); err != nil { + panic(err) + } +} diff --git a/crypto/internal/fips140/aes/ctr_asm.go b/crypto/internal/fips140/aes/ctr_asm.go new file mode 100644 index 00000000000..463e232c45c --- /dev/null +++ b/crypto/internal/fips140/aes/ctr_asm.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (amd64 || arm64 || ppc64 || ppc64le) && !purego + +package aes + +//go:generate sh -c "go run ./ctr_arm64_gen.go | asmfmt > ctr_arm64.s" + +//go:noescape +func ctrBlocks1Asm(nr int, xk *[60]uint32, dst, src *[BlockSize]byte, ivlo, ivhi uint64) + +//go:noescape +func ctrBlocks2Asm(nr int, xk *[60]uint32, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) + +//go:noescape +func ctrBlocks4Asm(nr int, xk *[60]uint32, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) + +//go:noescape +func ctrBlocks8Asm(nr int, xk *[60]uint32, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) + +func ctrBlocks1(b *Block, dst, src *[BlockSize]byte, ivlo, ivhi uint64) { + if !supportsAES { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) + } else { + ctrBlocks1Asm(b.rounds, &b.enc, dst, src, ivlo, ivhi) + } +} + +func ctrBlocks2(b *Block, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) { + if !supportsAES { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) + } else { + ctrBlocks2Asm(b.rounds, &b.enc, dst, src, ivlo, ivhi) + } +} + +func ctrBlocks4(b *Block, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) { + if !supportsAES { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) + } else { + ctrBlocks4Asm(b.rounds, &b.enc, dst, src, ivlo, ivhi) + } +} + +func ctrBlocks8(b *Block, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) { + if !supportsAES { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) + } else { + ctrBlocks8Asm(b.rounds, &b.enc, dst, src, ivlo, ivhi) + } +} diff --git a/crypto/internal/fips140/aes/ctr_noasm.go b/crypto/internal/fips140/aes/ctr_noasm.go new file mode 100644 index 00000000000..a170606a6db --- /dev/null +++ b/crypto/internal/fips140/aes/ctr_noasm.go @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !arm64 && !s390x && !ppc64 && !ppc64le) || purego + +package aes + +func ctrBlocks1(b *Block, dst, src *[BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks2(b *Block, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks4(b *Block, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks8(b *Block, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocks(b, dst[:], src[:], ivlo, ivhi) +} diff --git a/crypto/internal/fips140/aes/ctr_s390x.go b/crypto/internal/fips140/aes/ctr_s390x.go new file mode 100644 index 00000000000..d8865d6e2e1 --- /dev/null +++ b/crypto/internal/fips140/aes/ctr_s390x.go @@ -0,0 +1,49 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package aes + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +func ctrBlocks1(b *Block, dst, src *[BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocksS390x(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks2(b *Block, dst, src *[2 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocksS390x(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks4(b *Block, dst, src *[4 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocksS390x(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocks8(b *Block, dst, src *[8 * BlockSize]byte, ivlo, ivhi uint64) { + ctrBlocksS390x(b, dst[:], src[:], ivlo, ivhi) +} + +func ctrBlocksS390x(b *Block, dst, src []byte, ivlo, ivhi uint64) { + if b.fallback != nil { + ctrBlocks(b, dst, src, ivlo, ivhi) + return + } + + buf := make([]byte, len(src), 8*BlockSize) + for i := 0; i < len(buf); i += BlockSize { + byteorder.BEPutUint64(buf[i:], ivhi) + byteorder.BEPutUint64(buf[i+8:], ivlo) + ivlo, ivhi = add128(ivlo, ivhi, 1) + } + + // Encrypt the buffer using AES in ECB mode. + cryptBlocks(b.function, &b.key[0], &buf[0], &buf[0], len(buf)) + + // XOR into buf first, in case src and dst overlap (see ctrBlocks). + subtle.XORBytes(buf, src, buf) + copy(dst, buf) +} diff --git a/crypto/aes/_asm/gcm/gcm_amd64_asm.go b/crypto/internal/fips140/aes/gcm/_asm/gcm/gcm_amd64_asm.go similarity index 99% rename from crypto/aes/_asm/gcm/gcm_amd64_asm.go rename to crypto/internal/fips140/aes/gcm/_asm/gcm/gcm_amd64_asm.go index 60a82674c33..ed5f14b9386 100644 --- a/crypto/aes/_asm/gcm/gcm_amd64_asm.go +++ b/crypto/internal/fips140/aes/gcm/_asm/gcm/gcm_amd64_asm.go @@ -18,7 +18,7 @@ import ( . "github.com/mmcloughlin/avo/reg" ) -//go:generate go run . -out ../../gcm_amd64.s -pkg aes +//go:generate go run . -out ../../gcm_amd64.s var ( B0 VecPhysical = X0 @@ -42,7 +42,7 @@ var ( ) func main() { - Package("github.com/runZeroInc/excrypto/crypto/aes") + Package("crypto/aes") ConstraintExpr("!purego") gcmAesFinish() diff --git a/crypto/internal/fips140/aes/gcm/_asm/gcm/go.mod b/crypto/internal/fips140/aes/gcm/_asm/gcm/go.mod new file mode 100644 index 00000000000..3fd2094068e --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/_asm/gcm/go.mod @@ -0,0 +1,11 @@ +module crypto/aes/_asm/gcm + +go 1.24 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) diff --git a/crypto/internal/fips140/aes/gcm/_asm/gcm/go.sum b/crypto/internal/fips140/aes/gcm/_asm/gcm/go.sum new file mode 100644 index 00000000000..76af484b2eb --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/_asm/gcm/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/crypto/internal/fips140/aes/gcm/cast.go b/crypto/internal/fips140/aes/gcm/cast.go new file mode 100644 index 00000000000..2f32161875d --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/cast.go @@ -0,0 +1,45 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" +) + +func init() { + // Counter KDF covers CMAC per IG 10.3.B, and CMAC covers GCM per IG 10.3.A + // Resolution 1.d(i). AES decryption is covered by the CBC CAST in package + // crypto/internal/fips140/aes. + fips140.CAST("CounterKDF", func() error { + key := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + context := [12]byte{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, + } + want := [32]byte{ + 0xe6, 0x86, 0x96, 0x97, 0x08, 0xfc, 0x90, 0x30, + 0x36, 0x1c, 0x65, 0x94, 0xb2, 0x62, 0xa5, 0xf7, + 0xcb, 0x9d, 0x93, 0x94, 0xda, 0xf1, 0x94, 0x09, + 0x6a, 0x27, 0x5e, 0x85, 0x22, 0x5e, 0x7a, 0xee, + } + b, err := aes.New(key) + if err != nil { + return err + } + got := NewCounterKDF(b).DeriveKey(0xFF, context) + if got != want { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/aes/gcm/cmac.go b/crypto/internal/fips140/aes/gcm/cmac.go new file mode 100644 index 00000000000..c55bbfe7798 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/cmac.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" +) + +// CMAC implements the CMAC mode from NIST SP 800-38B. +// +// It is optimized for use in Counter KDF (SP 800-108r1) and XAES-256-GCM +// (https://c2sp.org/XAES-256-GCM), rather than for exposing it to applications +// as a stand-alone MAC. +type CMAC struct { + b aes.Block + k1 [aes.BlockSize]byte + k2 [aes.BlockSize]byte +} + +func NewCMAC(b *aes.Block) *CMAC { + c := &CMAC{b: *b} + c.deriveSubkeys() + return c +} + +func (c *CMAC) deriveSubkeys() { + aes.EncryptBlockInternal(&c.b, c.k1[:], c.k1[:]) + msb := shiftLeft(&c.k1) + c.k1[len(c.k1)-1] ^= msb * 0b10000111 + + c.k2 = c.k1 + msb = shiftLeft(&c.k2) + c.k2[len(c.k2)-1] ^= msb * 0b10000111 +} + +func (c *CMAC) MAC(m []byte) [aes.BlockSize]byte { + fips140.RecordApproved() + _ = c.b // Hoist the nil check out of the loop. + var x [aes.BlockSize]byte + if len(m) == 0 { + // Special-cased as a single empty partial final block. + x = c.k2 + x[len(m)] ^= 0b10000000 + aes.EncryptBlockInternal(&c.b, x[:], x[:]) + return x + } + for len(m) >= aes.BlockSize { + subtle.XORBytes(x[:], m[:aes.BlockSize], x[:]) + if len(m) == aes.BlockSize { + // Final complete block. + subtle.XORBytes(x[:], c.k1[:], x[:]) + } + aes.EncryptBlockInternal(&c.b, x[:], x[:]) + m = m[aes.BlockSize:] + } + if len(m) > 0 { + // Final incomplete block. + subtle.XORBytes(x[:], m, x[:]) + subtle.XORBytes(x[:], c.k2[:], x[:]) + x[len(m)] ^= 0b10000000 + aes.EncryptBlockInternal(&c.b, x[:], x[:]) + } + return x +} + +// shiftLeft sets x to x << 1, and returns MSB₁(x). +func shiftLeft(x *[aes.BlockSize]byte) byte { + var msb byte + for i := len(x) - 1; i >= 0; i-- { + msb, x[i] = x[i]>>7, x[i]<<1|msb + } + return msb +} diff --git a/crypto/internal/fips140/aes/gcm/ctrkdf.go b/crypto/internal/fips140/aes/gcm/ctrkdf.go new file mode 100644 index 00000000000..4b9491ceefe --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/ctrkdf.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" +) + +// CounterKDF implements a KDF in Counter Mode instantiated with CMAC-AES, +// according to NIST SP 800-108 Revision 1 Update 1, Section 4.1. +// +// It produces a 256-bit output, and accepts a 8-bit Label and a 96-bit Context. +// It uses a counter of 16 bits placed before the fixed data. The fixed data is +// the sequence Label || 0x00 || Context. The L field is omitted, since the +// output key length is fixed. +// +// It's optimized for use in XAES-256-GCM (https://c2sp.org/XAES-256-GCM), +// rather than for exposing it to applications as a stand-alone KDF. +type CounterKDF struct { + mac CMAC +} + +// NewCounterKDF creates a new CounterKDF with the given key. +func NewCounterKDF(b *aes.Block) *CounterKDF { + return &CounterKDF{mac: *NewCMAC(b)} +} + +// DeriveKey derives a key from the given label and context. +func (kdf *CounterKDF) DeriveKey(label byte, context [12]byte) [32]byte { + fips140.RecordApproved() + var output [32]byte + + var input [aes.BlockSize]byte + input[2] = label + copy(input[4:], context[:]) + + input[1] = 0x01 // i = 1 + K1 := kdf.mac.MAC(input[:]) + + input[1] = 0x02 // i = 2 + K2 := kdf.mac.MAC(input[:]) + + copy(output[:], K1[:]) + copy(output[aes.BlockSize:], K2[:]) + return output +} diff --git a/crypto/internal/fips140/aes/gcm/gcm.go b/crypto/internal/fips140/aes/gcm/gcm.go new file mode 100644 index 00000000000..9d11ee6975f --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm.go @@ -0,0 +1,144 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" +) + +// GCM represents a Galois Counter Mode with a specific key. +type GCM struct { + cipher aes.Block + nonceSize int + tagSize int + gcmPlatformData +} + +func New(cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) { + // This function is outlined to let the allocation happen on the parent stack. + return newGCM(&GCM{}, cipher, nonceSize, tagSize) +} + +// newGCM is marked go:noinline to avoid it inlining into New, and making New +// too complex to inline itself. +// +//go:noinline +func newGCM(g *GCM, cipher *aes.Block, nonceSize, tagSize int) (*GCM, error) { + if tagSize < gcmMinimumTagSize || tagSize > gcmBlockSize { + return nil, errors.New("cipher: incorrect tag size given to GCM") + } + if nonceSize <= 0 { + return nil, errors.New("cipher: the nonce can't have zero length") + } + if cipher.BlockSize() != gcmBlockSize { + return nil, errors.New("cipher: NewGCM requires 128-bit block cipher") + } + g.cipher = *cipher + g.nonceSize = nonceSize + g.tagSize = tagSize + initGCM(g) + return g, nil +} + +const ( + gcmBlockSize = 16 + gcmTagSize = 16 + gcmMinimumTagSize = 12 // NIST SP 800-38D recommends tags with 12 or more bytes. + gcmStandardNonceSize = 12 +) + +func (g *GCM) NonceSize() int { + return g.nonceSize +} + +func (g *GCM) Overhead() int { + return g.tagSize +} + +func (g *GCM) Seal(dst, nonce, plaintext, data []byte) []byte { + fips140.RecordNonApproved() + return g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCM) sealAfterIndicator(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != g.nonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + if g.nonceSize == 0 { + panic("crypto/cipher: incorrect GCM nonce size") + } + if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize { + panic("crypto/cipher: message too large for GCM") + } + + ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize) + if alias.InexactOverlap(out, plaintext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, data) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } + + seal(out, g, nonce, plaintext, data) + return ret +} + +var errOpen = errors.New("cipher: message authentication failed") + +func (g *GCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + if len(nonce) != g.nonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + // Sanity check to prevent the authentication from always succeeding if an + // implementation leaves tagSize uninitialized, for example. + if g.tagSize < gcmMinimumTagSize { + panic("crypto/cipher: incorrect GCM tag size") + } + + if len(ciphertext) < g.tagSize { + return nil, errOpen + } + if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) { + return nil, errOpen + } + + ret, out := sliceForAppend(dst, len(ciphertext)-g.tagSize) + if alias.InexactOverlap(out, ciphertext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, data) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } + + fips140.RecordApproved() + if err := open(out, g, nonce, ciphertext, data); err != nil { + // We sometimes decrypt and authenticate concurrently, so we overwrite + // dst in the event of a tag mismatch. To be consistent across platforms + // and to avoid releasing unauthenticated plaintext, we clear the buffer + // in the event of an error. + clear(out) + return nil, err + } + return ret, nil +} + +// sliceForAppend takes a slice and a requested number of bytes. It returns a +// slice with the contents of the given slice followed by that many bytes and a +// second slice that aliases into it and contains only the extra bytes. If the +// original slice has sufficient capacity then no allocation is performed. +func sliceForAppend(in []byte, n int) (head, tail []byte) { + if total := len(in) + n; cap(in) >= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} diff --git a/crypto/aes/gcm_amd64.s b/crypto/internal/fips140/aes/gcm/gcm_amd64.s similarity index 100% rename from crypto/aes/gcm_amd64.s rename to crypto/internal/fips140/aes/gcm/gcm_amd64.s diff --git a/crypto/aes/gcm_arm64.s b/crypto/internal/fips140/aes/gcm/gcm_arm64.s similarity index 100% rename from crypto/aes/gcm_arm64.s rename to crypto/internal/fips140/aes/gcm/gcm_arm64.s diff --git a/crypto/internal/fips140/aes/gcm/gcm_asm.go b/crypto/internal/fips140/aes/gcm/gcm_asm.go new file mode 100644 index 00000000000..cd6dc02db33 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_asm.go @@ -0,0 +1,131 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (amd64 || arm64) && !purego + +package gcm + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// The following functions are defined in gcm_*.s. + +//go:noescape +func gcmAesInit(productTable *[256]byte, ks []uint32) + +//go:noescape +func gcmAesData(productTable *[256]byte, data []byte, T *[16]byte) + +//go:noescape +func gcmAesEnc(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) + +//go:noescape +func gcmAesDec(productTable *[256]byte, dst, src []byte, ctr, T *[16]byte, ks []uint32) + +//go:noescape +func gcmAesFinish(productTable *[256]byte, tagMask, T *[16]byte, pLen, dLen uint64) + +// Keep in sync with crypto/tls.hasAESGCMHardwareSupport. +var supportsAESGCM = cpu.X86HasAES && cpu.X86HasPCLMULQDQ && cpu.X86HasSSE41 && cpu.X86HasSSSE3 || + cpu.ARM64HasAES && cpu.ARM64HasPMULL + +func init() { + if cpu.AMD64 { + impl.Register("gcm", "AES-NI", &supportsAESGCM) + } + if cpu.ARM64 { + impl.Register("gcm", "Armv8.0", &supportsAESGCM) + } +} + +// checkGenericIsExpected is called by the variable-time implementation to make +// sure it is not used when hardware support is available. It shouldn't happen, +// but this way it's more evidently correct. +func checkGenericIsExpected() { + if supportsAESGCM { + panic("gcm: internal error: using generic implementation despite hardware support") + } +} + +type gcmPlatformData struct { + productTable [256]byte +} + +func initGCM(g *GCM) { + if !supportsAESGCM { + return + } + gcmAesInit(&g.productTable, aes.EncryptionKeySchedule(&g.cipher)) +} + +func seal(out []byte, g *GCM, nonce, plaintext, data []byte) { + if !supportsAESGCM { + sealGeneric(out, g, nonce, plaintext, data) + return + } + + var counter, tagMask [gcmBlockSize]byte + + if len(nonce) == gcmStandardNonceSize { + // Init counter to nonce||1 + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + // Otherwise counter = GHASH(nonce) + gcmAesData(&g.productTable, nonce, &counter) + gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0)) + } + + aes.EncryptBlockInternal(&g.cipher, tagMask[:], counter[:]) + + var tagOut [gcmTagSize]byte + gcmAesData(&g.productTable, data, &tagOut) + + if len(plaintext) > 0 { + gcmAesEnc(&g.productTable, out, plaintext, &counter, &tagOut, aes.EncryptionKeySchedule(&g.cipher)) + } + gcmAesFinish(&g.productTable, &tagMask, &tagOut, uint64(len(plaintext)), uint64(len(data))) + copy(out[len(plaintext):], tagOut[:]) +} + +func open(out []byte, g *GCM, nonce, ciphertext, data []byte) error { + if !supportsAESGCM { + return openGeneric(out, g, nonce, ciphertext, data) + } + + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + + // See GCM spec, section 7.1. + var counter, tagMask [gcmBlockSize]byte + + if len(nonce) == gcmStandardNonceSize { + // Init counter to nonce||1 + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + // Otherwise counter = GHASH(nonce) + gcmAesData(&g.productTable, nonce, &counter) + gcmAesFinish(&g.productTable, &tagMask, &counter, uint64(len(nonce)), uint64(0)) + } + + aes.EncryptBlockInternal(&g.cipher, tagMask[:], counter[:]) + + var expectedTag [gcmTagSize]byte + gcmAesData(&g.productTable, data, &expectedTag) + + if len(ciphertext) > 0 { + gcmAesDec(&g.productTable, out, ciphertext, &counter, &expectedTag, aes.EncryptionKeySchedule(&g.cipher)) + } + gcmAesFinish(&g.productTable, &tagMask, &expectedTag, uint64(len(ciphertext)), uint64(len(data))) + + if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { + return errOpen + } + return nil +} diff --git a/crypto/internal/fips140/aes/gcm/gcm_generic.go b/crypto/internal/fips140/aes/gcm/gcm_generic.go new file mode 100644 index 00000000000..45ce7d004e4 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_generic.go @@ -0,0 +1,105 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +func sealGeneric(out []byte, g *GCM, nonce, plaintext, additionalData []byte) { + var H, counter, tagMask [gcmBlockSize]byte + aes.EncryptBlockInternal(&g.cipher, H[:], H[:]) + deriveCounterGeneric(&H, &counter, nonce) + gcmCounterCryptGeneric(&g.cipher, tagMask[:], tagMask[:], &counter) + + gcmCounterCryptGeneric(&g.cipher, out, plaintext, &counter) + + var tag [gcmTagSize]byte + gcmAuthGeneric(tag[:], &H, &tagMask, out[:len(plaintext)], additionalData) + copy(out[len(plaintext):], tag[:]) +} + +func openGeneric(out []byte, g *GCM, nonce, ciphertext, additionalData []byte) error { + var H, counter, tagMask [gcmBlockSize]byte + aes.EncryptBlockInternal(&g.cipher, H[:], H[:]) + deriveCounterGeneric(&H, &counter, nonce) + gcmCounterCryptGeneric(&g.cipher, tagMask[:], tagMask[:], &counter) + + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + + var expectedTag [gcmTagSize]byte + gcmAuthGeneric(expectedTag[:], &H, &tagMask, ciphertext, additionalData) + if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { + return errOpen + } + + gcmCounterCryptGeneric(&g.cipher, out, ciphertext, &counter) + + return nil +} + +// deriveCounterGeneric computes the initial GCM counter state from the given nonce. +// See NIST SP 800-38D, section 7.1. This assumes that counter is filled with +// zeros on entry. +func deriveCounterGeneric(H, counter *[gcmBlockSize]byte, nonce []byte) { + // GCM has two modes of operation with respect to the initial counter + // state: a "fast path" for 96-bit (12-byte) nonces, and a "slow path" + // for nonces of other lengths. For a 96-bit nonce, the nonce, along + // with a four-byte big-endian counter starting at one, is used + // directly as the starting counter. For other nonce sizes, the counter + // is computed by passing it through the GHASH function. + if len(nonce) == gcmStandardNonceSize { + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + lenBlock := make([]byte, 16) + byteorder.BEPutUint64(lenBlock[8:], uint64(len(nonce))*8) + ghash(counter, H, nonce, lenBlock) + } +} + +// gcmCounterCryptGeneric encrypts src using AES in counter mode with 32-bit +// wrapping (which is different from AES-CTR) and places the result into out. +// counter is the initial value and will be updated with the next value. +func gcmCounterCryptGeneric(b *aes.Block, out, src []byte, counter *[gcmBlockSize]byte) { + var mask [gcmBlockSize]byte + + for len(src) >= gcmBlockSize { + aes.EncryptBlockInternal(b, mask[:], counter[:]) + gcmInc32(counter) + + subtle.XORBytes(out, src, mask[:]) + out = out[gcmBlockSize:] + src = src[gcmBlockSize:] + } + + if len(src) > 0 { + aes.EncryptBlockInternal(b, mask[:], counter[:]) + gcmInc32(counter) + subtle.XORBytes(out, src, mask[:]) + } +} + +// gcmInc32 treats the final four bytes of counterBlock as a big-endian value +// and increments it. +func gcmInc32(counterBlock *[gcmBlockSize]byte) { + ctr := counterBlock[len(counterBlock)-4:] + byteorder.BEPutUint32(ctr, byteorder.BEUint32(ctr)+1) +} + +// gcmAuthGeneric calculates GHASH(additionalData, ciphertext), masks the result +// with tagMask and writes the result to out. +func gcmAuthGeneric(out []byte, H, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) { + checkGenericIsExpected() + lenBlock := make([]byte, 16) + byteorder.BEPutUint64(lenBlock[:8], uint64(len(additionalData))*8) + byteorder.BEPutUint64(lenBlock[8:], uint64(len(ciphertext))*8) + var S [gcmBlockSize]byte + ghash(&S, H, additionalData, ciphertext, lenBlock) + subtle.XORBytes(out, S[:], tagMask[:]) +} diff --git a/crypto/internal/fips140/aes/gcm/gcm_noasm.go b/crypto/internal/fips140/aes/gcm/gcm_noasm.go new file mode 100644 index 00000000000..4ae3831a458 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_noasm.go @@ -0,0 +1,21 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !s390x && !ppc64 && !ppc64le && !arm64) || purego + +package gcm + +func checkGenericIsExpected() {} + +type gcmPlatformData struct{} + +func initGCM(g *GCM) {} + +func seal(out []byte, g *GCM, nonce, plaintext, data []byte) { + sealGeneric(out, g, nonce, plaintext, data) +} + +func open(out []byte, g *GCM, nonce, ciphertext, data []byte) error { + return openGeneric(out, g, nonce, ciphertext, data) +} diff --git a/crypto/internal/fips140/aes/gcm/gcm_nonces.go b/crypto/internal/fips140/aes/gcm/gcm_nonces.go new file mode 100644 index 00000000000..0d4c41caaae --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_nonces.go @@ -0,0 +1,258 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "math" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// SealWithRandomNonce encrypts plaintext to out, and writes a random nonce to +// nonce. nonce must be 12 bytes, and out must be 16 bytes longer than plaintext. +// out and plaintext may overlap exactly or not at all. additionalData and out +// must not overlap. +// +// This complies with FIPS 140-3 IG C.H Scenario 2. +// +// Note that this is NOT a [cipher.AEAD].Seal method. +func SealWithRandomNonce(g *GCM, nonce, out, plaintext, additionalData []byte) { + if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize { + panic("crypto/cipher: message too large for GCM") + } + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCMWithRandomNonce") + } + if len(out) != len(plaintext)+gcmTagSize { + panic("crypto/cipher: incorrect output length given to GCMWithRandomNonce") + } + if alias.InexactOverlap(out, plaintext) { + panic("crypto/cipher: invalid buffer overlap of output and input") + } + if alias.AnyOverlap(out, additionalData) { + panic("crypto/cipher: invalid buffer overlap of output and additional data") + } + fips140.RecordApproved() + drbg.Read(nonce) + seal(out, g, nonce, plaintext, additionalData) +} + +// NewGCMWithCounterNonce returns a new AEAD that works like GCM, but enforces +// the construction of deterministic nonces. The nonce must be 96 bits, the +// first 32 bits must be an encoding of the module name, and the last 64 bits +// must be a counter. +// +// This complies with FIPS 140-3 IG C.H Scenario 3. +func NewGCMWithCounterNonce(cipher *aes.Block) (*GCMWithCounterNonce, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMWithCounterNonce{g: *g}, nil +} + +type GCMWithCounterNonce struct { + g GCM + ready bool + fixedName uint32 + start uint64 + next uint64 +} + +func (g *GCMWithCounterNonce) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMWithCounterNonce) Overhead() int { return gcmTagSize } + +func (g *GCMWithCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + if !g.ready { + // The first invocation sets the fixed name encoding and start counter. + g.ready = true + g.start = counter + g.fixedName = byteorder.BEUint32(nonce[:4]) + } + if g.fixedName != byteorder.BEUint32(nonce[:4]) { + panic("crypto/cipher: incorrect module name given to GCMWithCounterNonce") + } + counter -= g.start + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMWithCounterNonce) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} + +// NewGCMForTLS12 returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 5288, Section 3 and RFC 9325, +// Section 7.2.1. +// +// This complies with FIPS 140-3 IG C.H Scenario 1.a. +func NewGCMForTLS12(cipher *aes.Block) (*GCMForTLS12, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMForTLS12{g: *g}, nil +} + +type GCMForTLS12 struct { + g GCM + next uint64 +} + +func (g *GCMForTLS12) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMForTLS12) Overhead() int { return gcmTagSize } + +func (g *GCMForTLS12) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMForTLS12) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} + +// NewGCMForTLS13 returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 8446, Section 5.3. +func NewGCMForTLS13(cipher *aes.Block) (*GCMForTLS13, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMForTLS13{g: *g}, nil +} + +type GCMForTLS13 struct { + g GCM + ready bool + mask uint64 + next uint64 +} + +func (g *GCMForTLS13) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMForTLS13) Overhead() int { return gcmTagSize } + +func (g *GCMForTLS13) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + if !g.ready { + // In the first call, the counter is zero, so we learn the XOR mask. + g.ready = true + g.mask = counter + } + counter ^= g.mask + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMForTLS13) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} + +// NewGCMForSSH returns a new AEAD that works like GCM, but enforces the +// construction of nonces as specified in RFC 5647. +// +// This complies with FIPS 140-3 IG C.H Scenario 1.d. +func NewGCMForSSH(cipher *aes.Block) (*GCMForSSH, error) { + g, err := newGCM(&GCM{}, cipher, gcmStandardNonceSize, gcmTagSize) + if err != nil { + return nil, err + } + return &GCMForSSH{g: *g}, nil +} + +type GCMForSSH struct { + g GCM + ready bool + start uint64 + next uint64 +} + +func (g *GCMForSSH) NonceSize() int { return gcmStandardNonceSize } + +func (g *GCMForSSH) Overhead() int { return gcmTagSize } + +func (g *GCMForSSH) Seal(dst, nonce, plaintext, data []byte) []byte { + if len(nonce) != gcmStandardNonceSize { + panic("crypto/cipher: incorrect nonce length given to GCM") + } + + counter := byteorder.BEUint64(nonce[len(nonce)-8:]) + if !g.ready { + // In the first call we learn the start value. + g.ready = true + g.start = counter + } + counter -= g.start + + // Ensure the counter is monotonically increasing. + if counter == math.MaxUint64 { + panic("crypto/cipher: counter wrapped") + } + if counter < g.next { + panic("crypto/cipher: counter decreased") + } + g.next = counter + 1 + + fips140.RecordApproved() + return g.g.sealAfterIndicator(dst, nonce, plaintext, data) +} + +func (g *GCMForSSH) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { + fips140.RecordApproved() + return g.g.Open(dst, nonce, ciphertext, data) +} diff --git a/crypto/internal/fips140/aes/gcm/gcm_ppc64x.go b/crypto/internal/fips140/aes/gcm/gcm_ppc64x.go new file mode 100644 index 00000000000..84dc2fb318e --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_ppc64x.go @@ -0,0 +1,188 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (ppc64le || ppc64) && !purego + +package gcm + +import ( + "runtime" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// This file implements GCM using an optimized GHASH function. + +//go:noescape +func gcmInit(productTable *[256]byte, h []byte) + +//go:noescape +func gcmHash(output []byte, productTable *[256]byte, inp []byte, len int) + +func counterCryptASM(nr int, out, in []byte, counter *[gcmBlockSize]byte, key *uint32) + +// The POWER architecture doesn't have a way to turn off AES-GCM support +// at runtime with GODEBUG=cpu.something=off, so introduce a new GODEBUG +// knob for that. It's intentionally only checked at init() time, to +// avoid the performance overhead of checking it every time. +var supportsAESGCM = godebug.Value("#ppc64gcm") != "off" + +func init() { + impl.Register("gcm", "POWER8", &supportsAESGCM) +} + +func checkGenericIsExpected() { + if supportsAESGCM { + panic("gcm: internal error: using generic implementation despite hardware support") + } +} + +type gcmPlatformData struct { + productTable [256]byte +} + +func initGCM(g *GCM) { + if !supportsAESGCM { + return + } + + hle := make([]byte, gcmBlockSize) + aes.EncryptBlockInternal(&g.cipher, hle, hle) + + // Reverse the bytes in each 8 byte chunk + // Load little endian, store big endian + var h1, h2 uint64 + if runtime.GOARCH == "ppc64le" { + h1 = byteorder.LEUint64(hle[:8]) + h2 = byteorder.LEUint64(hle[8:]) + } else { + h1 = byteorder.BEUint64(hle[:8]) + h2 = byteorder.BEUint64(hle[8:]) + } + byteorder.BEPutUint64(hle[:8], h1) + byteorder.BEPutUint64(hle[8:], h2) + gcmInit(&g.productTable, hle) +} + +// deriveCounter computes the initial GCM counter state from the given nonce. +func deriveCounter(counter *[gcmBlockSize]byte, nonce []byte, productTable *[256]byte) { + if len(nonce) == gcmStandardNonceSize { + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + var hash [16]byte + paddedGHASH(&hash, nonce, productTable) + lens := gcmLengths(0, uint64(len(nonce))*8) + paddedGHASH(&hash, lens[:], productTable) + copy(counter[:], hash[:]) + } +} + +// counterCrypt encrypts in using AES in counter mode and places the result +// into out. counter is the initial count value and will be updated with the next +// count value. The length of out must be greater than or equal to the length +// of in. +// counterCryptASM implements counterCrypt which then allows the loop to +// be unrolled and optimized. +func counterCrypt(b *aes.Block, out, in []byte, counter *[gcmBlockSize]byte) { + enc := aes.EncryptionKeySchedule(b) + rounds := len(enc)/4 - 1 + counterCryptASM(rounds, out, in, counter, &enc[0]) +} + +// paddedGHASH pads data with zeroes until its length is a multiple of +// 16-bytes. It then calculates a new value for hash using the ghash +// algorithm. +func paddedGHASH(hash *[16]byte, data []byte, productTable *[256]byte) { + if siz := len(data) - (len(data) % gcmBlockSize); siz > 0 { + gcmHash(hash[:], productTable, data[:], siz) + data = data[siz:] + } + if len(data) > 0 { + var s [16]byte + copy(s[:], data) + gcmHash(hash[:], productTable, s[:], len(s)) + } +} + +// auth calculates GHASH(ciphertext, additionalData), masks the result with +// tagMask and writes the result to out. +func auth(out, ciphertext, aad []byte, tagMask *[gcmTagSize]byte, productTable *[256]byte) { + var hash [16]byte + paddedGHASH(&hash, aad, productTable) + paddedGHASH(&hash, ciphertext, productTable) + lens := gcmLengths(uint64(len(aad))*8, uint64(len(ciphertext))*8) + paddedGHASH(&hash, lens[:], productTable) + + copy(out, hash[:]) + for i := range out { + out[i] ^= tagMask[i] + } +} + +func seal(out []byte, g *GCM, nonce, plaintext, data []byte) { + if !supportsAESGCM { + sealGeneric(out, g, nonce, plaintext, data) + return + } + + var counter, tagMask [gcmBlockSize]byte + deriveCounter(&counter, nonce, &g.productTable) + + aes.EncryptBlockInternal(&g.cipher, tagMask[:], counter[:]) + gcmInc32(&counter) + + counterCrypt(&g.cipher, out, plaintext, &counter) + auth(out[len(plaintext):], out[:len(plaintext)], data, &tagMask, &g.productTable) +} + +func open(out []byte, g *GCM, nonce, ciphertext, data []byte) error { + if !supportsAESGCM { + return openGeneric(out, g, nonce, ciphertext, data) + } + + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + + var counter, tagMask [gcmBlockSize]byte + deriveCounter(&counter, nonce, &g.productTable) + + aes.EncryptBlockInternal(&g.cipher, tagMask[:], counter[:]) + gcmInc32(&counter) + + var expectedTag [gcmTagSize]byte + auth(expectedTag[:], ciphertext, data, &tagMask, &g.productTable) + + if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { + return errOpen + } + + counterCrypt(&g.cipher, out, ciphertext, &counter) + return nil +} + +func gcmLengths(len0, len1 uint64) [16]byte { + return [16]byte{ + byte(len0 >> 56), + byte(len0 >> 48), + byte(len0 >> 40), + byte(len0 >> 32), + byte(len0 >> 24), + byte(len0 >> 16), + byte(len0 >> 8), + byte(len0), + byte(len1 >> 56), + byte(len1 >> 48), + byte(len1 >> 40), + byte(len1 >> 32), + byte(len1 >> 24), + byte(len1 >> 16), + byte(len1 >> 8), + byte(len1), + } +} diff --git a/crypto/aes/gcm_ppc64x.s b/crypto/internal/fips140/aes/gcm/gcm_ppc64x.s similarity index 96% rename from crypto/aes/gcm_ppc64x.s rename to crypto/internal/fips140/aes/gcm/gcm_ppc64x.s index 987f4e718e1..558399b10a7 100644 --- a/crypto/aes/gcm_ppc64x.s +++ b/crypto/internal/fips140/aes/gcm/gcm_ppc64x.s @@ -12,7 +12,7 @@ // # details see http://www.openssl.org/~appro/cryptogams/. // # ==================================================================== -// The implementations for gcmHash, gcmInit and gcmMul are based on the generated asm +// The implementations for gcmHash and gcmInit are based on the generated asm // from the script https://github.com/dot-asm/cryptogams/blob/master/ppc/ghashp8-ppc.pl // from commit d47afb3c. @@ -94,6 +94,18 @@ #define ESPERM V10 #define TMP2 V11 +DATA ·rcon+0x00(SB)/8, $0x0f0e0d0c0b0a0908 // Permute for vector doubleword endian swap +DATA ·rcon+0x08(SB)/8, $0x0706050403020100 +DATA ·rcon+0x10(SB)/8, $0x0100000001000000 // RCON +DATA ·rcon+0x18(SB)/8, $0x0100000001000000 // RCON +DATA ·rcon+0x20(SB)/8, $0x1b0000001b000000 +DATA ·rcon+0x28(SB)/8, $0x1b0000001b000000 +DATA ·rcon+0x30(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK +DATA ·rcon+0x38(SB)/8, $0x0d0e0f0c0d0e0f0c // MASK +DATA ·rcon+0x40(SB)/8, $0x0000000000000000 +DATA ·rcon+0x48(SB)/8, $0x0000000000000000 +GLOBL ·rcon(SB), RODATA, $80 + // The following macros provide appropriate // implementations for endianness as well as // ISA specific for power8 and power9. @@ -833,52 +845,6 @@ done_4x: STXVD2X VXL, (XIP+R0) // write out Xi RET -// func gcmMul(output []byte, productTable *[256]byte) -TEXT ·gcmMul(SB), NOSPLIT, $0-32 - MOVD output+0(FP), XIP - MOVD productTable+24(FP), HTBL - - MOVD $0x10, R8 - MOVD $0x20, R9 - MOVD $0x30, R10 - LXVD2X (XIP)(R0), VIN // load Xi - - LXVD2X (HTBL)(R8), VHL // Load pre-computed table - LXVD2X (HTBL)(R9), VH - LXVD2X (HTBL)(R10), VHH - LXVD2X (HTBL)(R0), VXC2 -#ifdef GOARCH_ppc64le - VSPLTISB $0x07, T0 - VXOR LEMASK, T0, LEMASK - VPERM IN, IN, LEMASK, IN -#endif - VXOR ZERO, ZERO, ZERO - - VPMSUMD IN, HL, XL // H.lo·Xi.lo - VPMSUMD IN, H, XM // H.hi·Xi.lo+H.lo·Xi.hi - VPMSUMD IN, HH, XH // H.hi·Xi.hi - - VPMSUMD XL, XC2, T2 // 1st reduction phase - - VSLDOI $8, XM, ZERO, T0 - VSLDOI $8, ZERO, XM, T1 - VXOR XL, T0, XL - VXOR XH, T1, XH - - VSLDOI $8, XL, XL, XL - VXOR XL, T2, XL - - VSLDOI $8, XL, XL, T1 // 2nd reduction phase - VPMSUMD XL, XC2, XL - VXOR T1, XH, T1 - VXOR XL, T1, XL - -#ifdef GOARCH_ppc64le - VPERM XL, XL, LEMASK, XL -#endif - STXVD2X VXL, (XIP+R0) // write out Xi - RET - #define BLK_INP R3 #define BLK_OUT R4 #define BLK_KEY R5 diff --git a/crypto/internal/fips140/aes/gcm/gcm_s390x.go b/crypto/internal/fips140/aes/gcm/gcm_s390x.go new file mode 100644 index 00000000000..b954f64cd80 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_s390x.go @@ -0,0 +1,251 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package gcm + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// This file contains two implementations of AES-GCM. The first implementation +// (useGHASH) uses the KMCTR instruction to encrypt using AES in counter mode +// and the KIMD instruction for GHASH. The second implementation (useGCM) uses +// the newer KMA instruction which performs both operations (but still requires +// KIMD to hash large nonces). + +// Keep in sync with crypto/tls.hasAESGCMHardwareSupport. +var useGHASH = cpu.S390XHasAES && cpu.S390XHasAESCTR && cpu.S390XHasGHASH +var useGCM = useGHASH && cpu.S390XHasAESGCM + +func init() { + impl.Register("gcm", "CPACF/KIMD", &useGHASH) + impl.Register("gcm", "CPACF/KMA", &useGCM) +} + +func checkGenericIsExpected() { + if useGHASH || useGCM { + panic("gcm: internal error: using generic implementation despite hardware support") + } +} + +// gcmLengths writes len0 || len1 as big-endian values to a 16-byte array. +func gcmLengths(len0, len1 uint64) [16]byte { + v := [16]byte{} + byteorder.BEPutUint64(v[0:], len0) + byteorder.BEPutUint64(v[8:], len1) + return v +} + +// gcmHashKey represents the 16-byte hash key required by the GHASH algorithm. +type gcmHashKey [16]byte + +type gcmPlatformData struct { + hashKey gcmHashKey +} + +func initGCM(g *GCM) { + if !useGCM && !useGHASH { + return + } + // Note that hashKey is also used in the KMA codepath to hash large nonces. + aes.EncryptBlockInternal(&g.cipher, g.hashKey[:], g.hashKey[:]) +} + +// ghashAsm uses the GHASH algorithm to hash data with the given key. The initial +// hash value is given by hash which will be updated with the new hash value. +// The length of data must be a multiple of 16-bytes. +// +//go:noescape +func ghashAsm(key *gcmHashKey, hash *[16]byte, data []byte) + +// paddedGHASH pads data with zeroes until its length is a multiple of +// 16-bytes. It then calculates a new value for hash using the GHASH algorithm. +func paddedGHASH(hashKey *gcmHashKey, hash *[16]byte, data []byte) { + siz := len(data) &^ 0xf // align size to 16-bytes + if siz > 0 { + ghashAsm(hashKey, hash, data[:siz]) + data = data[siz:] + } + if len(data) > 0 { + var s [16]byte + copy(s[:], data) + ghashAsm(hashKey, hash, s[:]) + } +} + +// cryptBlocksGCM encrypts src using AES in counter mode using the given +// function code and key. The rightmost 32-bits of the counter are incremented +// between each block as required by the GCM spec. The initial counter value +// is given by cnt, which is updated with the value of the next counter value +// to use. +// +// The lengths of both dst and buf must be greater than or equal to the length +// of src. buf may be partially or completely overwritten during the execution +// of the function. +// +//go:noescape +func cryptBlocksGCM(fn int, key, dst, src, buf []byte, cnt *[gcmBlockSize]byte) + +// counterCrypt encrypts src using AES in counter mode and places the result +// into dst. cnt is the initial count value and will be updated with the next +// count value. The length of dst must be greater than or equal to the length +// of src. +func counterCrypt(g *GCM, dst, src []byte, cnt *[gcmBlockSize]byte) { + // Copying src into a buffer improves performance on some models when + // src and dst point to the same underlying array. We also need a + // buffer for counter values. + var ctrbuf, srcbuf [2048]byte + for len(src) >= 16 { + siz := len(src) + if len(src) > len(ctrbuf) { + siz = len(ctrbuf) + } + siz &^= 0xf // align siz to 16-bytes + copy(srcbuf[:], src[:siz]) + cryptBlocksGCM(aes.BlockFunction(&g.cipher), aes.BlockKey(&g.cipher), dst[:siz], srcbuf[:siz], ctrbuf[:], cnt) + src = src[siz:] + dst = dst[siz:] + } + if len(src) > 0 { + var x [16]byte + aes.EncryptBlockInternal(&g.cipher, x[:], cnt[:]) + for i := range src { + dst[i] = src[i] ^ x[i] + } + gcmInc32(cnt) + } +} + +// deriveCounter computes the initial GCM counter state from the given nonce. +// See NIST SP 800-38D, section 7.1 and deriveCounterGeneric in gcm_generic.go. +func deriveCounter(H *gcmHashKey, counter *[gcmBlockSize]byte, nonce []byte) { + if len(nonce) == gcmStandardNonceSize { + copy(counter[:], nonce) + counter[gcmBlockSize-1] = 1 + } else { + var hash [16]byte + paddedGHASH(H, &hash, nonce) + lens := gcmLengths(0, uint64(len(nonce))*8) + paddedGHASH(H, &hash, lens[:]) + copy(counter[:], hash[:]) + } +} + +// gcmAuth calculates GHASH(additionalData, ciphertext), masks the result +// with tagMask and writes the result to out. +func gcmAuth(out []byte, H *gcmHashKey, tagMask *[gcmBlockSize]byte, ciphertext, additionalData []byte) { + var hash [16]byte + paddedGHASH(H, &hash, additionalData) + paddedGHASH(H, &hash, ciphertext) + lens := gcmLengths(uint64(len(additionalData))*8, uint64(len(ciphertext))*8) + paddedGHASH(H, &hash, lens[:]) + + copy(out, hash[:]) + for i := range out { + out[i] ^= tagMask[i] + } +} + +func seal(out []byte, g *GCM, nonce, plaintext, data []byte) { + switch { + case useGCM: + sealKMA(out, g, nonce, plaintext, data) + case useGHASH: + sealAsm(out, g, nonce, plaintext, data) + default: + sealGeneric(out, g, nonce, plaintext, data) + } +} + +func sealAsm(out []byte, g *GCM, nonce, plaintext, additionalData []byte) { + var counter, tagMask [gcmBlockSize]byte + deriveCounter(&g.hashKey, &counter, nonce) + counterCrypt(g, tagMask[:], tagMask[:], &counter) + + counterCrypt(g, out, plaintext, &counter) + + var tag [gcmTagSize]byte + gcmAuth(tag[:], &g.hashKey, &tagMask, out[:len(plaintext)], additionalData) + copy(out[len(plaintext):], tag[:]) +} + +func open(out []byte, g *GCM, nonce, ciphertext, data []byte) error { + switch { + case useGCM: + return openKMA(out, g, nonce, ciphertext, data) + case useGHASH: + return openAsm(out, g, nonce, ciphertext, data) + default: + return openGeneric(out, g, nonce, ciphertext, data) + } +} + +func openAsm(out []byte, g *GCM, nonce, ciphertext, additionalData []byte) error { + var counter, tagMask [gcmBlockSize]byte + deriveCounter(&g.hashKey, &counter, nonce) + counterCrypt(g, tagMask[:], tagMask[:], &counter) + + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + + var expectedTag [gcmTagSize]byte + gcmAuth(expectedTag[:], &g.hashKey, &tagMask, ciphertext, additionalData) + if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { + return errOpen + } + + counterCrypt(g, out, ciphertext, &counter) + + return nil +} + +// flags for the KMA instruction +const ( + kmaHS = 1 << 10 // hash subkey supplied + kmaLAAD = 1 << 9 // last series of additional authenticated data + kmaLPC = 1 << 8 // last series of plaintext or ciphertext blocks + kmaDecrypt = 1 << 7 // decrypt +) + +// kmaGCM executes the encryption or decryption operation given by fn. The tag +// will be calculated and written to tag. cnt should contain the current +// counter state and will be overwritten with the updated counter state. +// TODO(mundaym): could pass in hash subkey +// +//go:noescape +func kmaGCM(fn int, key, dst, src, aad []byte, tag *[16]byte, cnt *[gcmBlockSize]byte) + +func sealKMA(out []byte, g *GCM, nonce, plaintext, data []byte) { + var counter [gcmBlockSize]byte + deriveCounter(&g.hashKey, &counter, nonce) + fc := aes.BlockFunction(&g.cipher) | kmaLAAD | kmaLPC + + var tag [gcmTagSize]byte + kmaGCM(fc, aes.BlockKey(&g.cipher), out[:len(plaintext)], plaintext, data, &tag, &counter) + copy(out[len(plaintext):], tag[:]) +} + +func openKMA(out []byte, g *GCM, nonce, ciphertext, data []byte) error { + tag := ciphertext[len(ciphertext)-g.tagSize:] + ciphertext = ciphertext[:len(ciphertext)-g.tagSize] + + var counter [gcmBlockSize]byte + deriveCounter(&g.hashKey, &counter, nonce) + fc := aes.BlockFunction(&g.cipher) | kmaLAAD | kmaLPC | kmaDecrypt + + var expectedTag [gcmTagSize]byte + kmaGCM(fc, aes.BlockKey(&g.cipher), out[:len(ciphertext)], ciphertext, data, &expectedTag, &counter) + + if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 { + return errOpen + } + + return nil +} diff --git a/crypto/internal/fips140/aes/gcm/gcm_s390x.s b/crypto/internal/fips140/aes/gcm/gcm_s390x.s new file mode 100644 index 00000000000..23a15dfcb0c --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/gcm_s390x.s @@ -0,0 +1,130 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +// func cryptBlocksGCM(fn code, key, dst, src, buf []byte, cnt *[16]byte) +TEXT ·cryptBlocksGCM(SB),NOSPLIT,$0-112 + MOVD src_len+64(FP), R0 + MOVD buf_base+80(FP), R1 + MOVD cnt+104(FP), R12 + LMG (R12), R2, R3 + + // Check that the src size is less than or equal to the buffer size. + MOVD buf_len+88(FP), R4 + CMP R0, R4 + BGT crash + + // Check that the src size is a multiple of 16-bytes. + MOVD R0, R4 + AND $0xf, R4 + BLT crash // non-zero + + // Check that the src size is less than or equal to the dst size. + MOVD dst_len+40(FP), R4 + CMP R0, R4 + BGT crash + + MOVD R2, R4 + MOVD R2, R6 + MOVD R2, R8 + MOVD R3, R5 + MOVD R3, R7 + MOVD R3, R9 + ADDW $1, R5 + ADDW $2, R7 + ADDW $3, R9 +incr: + CMP R0, $64 + BLT tail + STMG R2, R9, (R1) + ADDW $4, R3 + ADDW $4, R5 + ADDW $4, R7 + ADDW $4, R9 + MOVD $64(R1), R1 + SUB $64, R0 + BR incr +tail: + CMP R0, $0 + BEQ crypt + STMG R2, R3, (R1) + ADDW $1, R3 + MOVD $16(R1), R1 + SUB $16, R0 + BR tail +crypt: + STMG R2, R3, (R12) // update next counter value + MOVD fn+0(FP), R0 // function code (encryption) + MOVD key_base+8(FP), R1 // key + MOVD buf_base+80(FP), R2 // counter values + MOVD dst_base+32(FP), R4 // dst + MOVD src_base+56(FP), R6 // src + MOVD src_len+64(FP), R7 // len +loop: + KMCTR R4, R2, R6 // cipher message with counter (KMCTR) + BVS loop // branch back if interrupted + RET +crash: + MOVD $0, (R0) + RET + + +// func ghashAsm(key *gcmHashKey, hash *[16]byte, data []byte) +TEXT ·ghashAsm(SB),NOSPLIT,$32-40 + MOVD $65, R0 // GHASH function code + MOVD key+0(FP), R2 + LMG (R2), R6, R7 + MOVD hash+8(FP), R8 + LMG (R8), R4, R5 + MOVD $params-32(SP), R1 + STMG R4, R7, (R1) + LMG data+16(FP), R2, R3 // R2=base, R3=len +loop: + KIMD R0, R2 // compute intermediate message digest (KIMD) + BVS loop // branch back if interrupted + MVC $16, (R1), (R8) + MOVD $0, R0 + RET + +// func kmaGCM(fn int, key, dst, src, aad []byte, tag *[16]byte, cnt *[gcmBlockSize]byte) +TEXT ·kmaGCM(SB),NOSPLIT,$112-120 + MOVD fn+0(FP), R0 + MOVD $params-112(SP), R1 + + // load ptr/len pairs + LMG dst+32(FP), R2, R3 // R2=base R3=len + LMG src+56(FP), R4, R5 // R4=base R5=len + LMG aad+80(FP), R6, R7 // R6=base R7=len + + // setup parameters + MOVD cnt+112(FP), R8 + XC $12, (R1), (R1) // reserved + MVC $4, 12(R8), 12(R1) // set chain value + MVC $16, (R8), 64(R1) // set initial counter value + XC $32, 16(R1), 16(R1) // set hash subkey and tag + SLD $3, R7, R12 + MOVD R12, 48(R1) // set total AAD length + SLD $3, R5, R12 + MOVD R12, 56(R1) // set total plaintext/ciphertext length + + LMG key+8(FP), R8, R9 // R8=base R9=len + MVC $16, (R8), 80(R1) // set key + CMPBEQ R9, $16, kma + MVC $8, 16(R8), 96(R1) + CMPBEQ R9, $24, kma + MVC $8, 24(R8), 104(R1) + +kma: + KMA R2, R6, R4 // Cipher Message with Authentication + BVS kma + + MOVD tag+104(FP), R2 + MVC $16, 16(R1), 0(R2) // copy tag to output + MOVD cnt+112(FP), R8 + MVC $4, 12(R1), 12(R8) // update counter value + + RET diff --git a/crypto/internal/fips140/aes/gcm/ghash.go b/crypto/internal/fips140/aes/gcm/ghash.go new file mode 100644 index 00000000000..361119dd400 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/ghash.go @@ -0,0 +1,163 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// gcmFieldElement represents a value in GF(2¹²⁸). In order to reflect the GCM +// standard and make binary.BigEndian suitable for marshaling these values, the +// bits are stored in big endian order. For example: +// +// the coefficient of x⁰ can be obtained by v.low >> 63. +// the coefficient of x⁶³ can be obtained by v.low & 1. +// the coefficient of x⁶⁴ can be obtained by v.high >> 63. +// the coefficient of x¹²⁷ can be obtained by v.high & 1. +type gcmFieldElement struct { + low, high uint64 +} + +// GHASH is exposed to allow crypto/cipher to implement non-AES GCM modes. +// It is not allowed as a stand-alone operation in FIPS mode because it +// is not ACVP tested. +func GHASH(key *[16]byte, inputs ...[]byte) []byte { + fips140.RecordNonApproved() + var out [gcmBlockSize]byte + ghash(&out, key, inputs...) + return out[:] +} + +// ghash is a variable-time generic implementation of GHASH, which shouldn't +// be used on any architecture with hardware support for AES-GCM. +// +// Each input is zero-padded to 128-bit before being absorbed. +func ghash(out, H *[gcmBlockSize]byte, inputs ...[]byte) { + // productTable contains the first sixteen powers of the key, H. + // However, they are in bit reversed order. + var productTable [16]gcmFieldElement + + // We precompute 16 multiples of H. However, when we do lookups + // into this table we'll be using bits from a field element and + // therefore the bits will be in the reverse order. So normally one + // would expect, say, 4*H to be in index 4 of the table but due to + // this bit ordering it will actually be in index 0010 (base 2) = 2. + x := gcmFieldElement{ + byteorder.BEUint64(H[:8]), + byteorder.BEUint64(H[8:]), + } + productTable[reverseBits(1)] = x + + for i := 2; i < 16; i += 2 { + productTable[reverseBits(i)] = ghashDouble(&productTable[reverseBits(i/2)]) + productTable[reverseBits(i+1)] = ghashAdd(&productTable[reverseBits(i)], &x) + } + + var y gcmFieldElement + for _, input := range inputs { + ghashUpdate(&productTable, &y, input) + } + + byteorder.BEPutUint64(out[:], y.low) + byteorder.BEPutUint64(out[8:], y.high) +} + +// reverseBits reverses the order of the bits of 4-bit number in i. +func reverseBits(i int) int { + i = ((i << 2) & 0xc) | ((i >> 2) & 0x3) + i = ((i << 1) & 0xa) | ((i >> 1) & 0x5) + return i +} + +// ghashAdd adds two elements of GF(2¹²⁸) and returns the sum. +func ghashAdd(x, y *gcmFieldElement) gcmFieldElement { + // Addition in a characteristic 2 field is just XOR. + return gcmFieldElement{x.low ^ y.low, x.high ^ y.high} +} + +// ghashDouble returns the result of doubling an element of GF(2¹²⁸). +func ghashDouble(x *gcmFieldElement) (double gcmFieldElement) { + msbSet := x.high&1 == 1 + + // Because of the bit-ordering, doubling is actually a right shift. + double.high = x.high >> 1 + double.high |= x.low << 63 + double.low = x.low >> 1 + + // If the most-significant bit was set before shifting then it, + // conceptually, becomes a term of x^128. This is greater than the + // irreducible polynomial so the result has to be reduced. The + // irreducible polynomial is 1+x+x^2+x^7+x^128. We can subtract that to + // eliminate the term at x^128 which also means subtracting the other + // four terms. In characteristic 2 fields, subtraction == addition == + // XOR. + if msbSet { + double.low ^= 0xe100000000000000 + } + + return +} + +var ghashReductionTable = []uint16{ + 0x0000, 0x1c20, 0x3840, 0x2460, 0x7080, 0x6ca0, 0x48c0, 0x54e0, + 0xe100, 0xfd20, 0xd940, 0xc560, 0x9180, 0x8da0, 0xa9c0, 0xb5e0, +} + +// ghashMul sets y to y*H, where H is the GCM key, fixed during New. +func ghashMul(productTable *[16]gcmFieldElement, y *gcmFieldElement) { + var z gcmFieldElement + + for i := 0; i < 2; i++ { + word := y.high + if i == 1 { + word = y.low + } + + // Multiplication works by multiplying z by 16 and adding in + // one of the precomputed multiples of H. + for j := 0; j < 64; j += 4 { + msw := z.high & 0xf + z.high >>= 4 + z.high |= z.low << 60 + z.low >>= 4 + z.low ^= uint64(ghashReductionTable[msw]) << 48 + + // the values in |table| are ordered for little-endian bit + // positions. See the comment in New. + t := productTable[word&0xf] + + z.low ^= t.low + z.high ^= t.high + word >>= 4 + } + } + + *y = z +} + +// updateBlocks extends y with more polynomial terms from blocks, based on +// Horner's rule. There must be a multiple of gcmBlockSize bytes in blocks. +func updateBlocks(productTable *[16]gcmFieldElement, y *gcmFieldElement, blocks []byte) { + for len(blocks) > 0 { + y.low ^= byteorder.BEUint64(blocks) + y.high ^= byteorder.BEUint64(blocks[8:]) + ghashMul(productTable, y) + blocks = blocks[gcmBlockSize:] + } +} + +// ghashUpdate extends y with more polynomial terms from data. If data is not a +// multiple of gcmBlockSize bytes long then the remainder is zero padded. +func ghashUpdate(productTable *[16]gcmFieldElement, y *gcmFieldElement, data []byte) { + fullBlocks := (len(data) >> 4) << 4 + updateBlocks(productTable, y, data[:fullBlocks]) + + if len(data) != fullBlocks { + var partialBlock [gcmBlockSize]byte + copy(partialBlock[:], data[fullBlocks:]) + updateBlocks(productTable, y, partialBlock[:]) + } +} diff --git a/crypto/internal/fips140/aes/gcm/interface_test.go b/crypto/internal/fips140/aes/gcm/interface_test.go new file mode 100644 index 00000000000..935fb673604 --- /dev/null +++ b/crypto/internal/fips140/aes/gcm/interface_test.go @@ -0,0 +1,12 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gcm_test + +import ( + "github.com/runZeroInc/excrypto/crypto/cipher" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" +) + +var _ cipher.AEAD = (*gcm.GCM)(nil) diff --git a/crypto/internal/fips140/aes/interface_test.go b/crypto/internal/fips140/aes/interface_test.go new file mode 100644 index 00000000000..ab6c40aaa10 --- /dev/null +++ b/crypto/internal/fips140/aes/interface_test.go @@ -0,0 +1,15 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package aes_test + +import ( + "github.com/runZeroInc/excrypto/crypto/cipher" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" +) + +var _ cipher.Block = (*aes.Block)(nil) +var _ cipher.Stream = (*aes.CTR)(nil) +var _ cipher.BlockMode = (*aes.CBCDecrypter)(nil) +var _ cipher.BlockMode = (*aes.CBCEncrypter)(nil) diff --git a/crypto/internal/alias/alias.go b/crypto/internal/fips140/alias/alias.go similarity index 100% rename from crypto/internal/alias/alias.go rename to crypto/internal/fips140/alias/alias.go diff --git a/crypto/subtle/xor_loong64.go b/crypto/internal/fips140/asan.go similarity index 65% rename from crypto/subtle/xor_loong64.go rename to crypto/internal/fips140/asan.go index e49f0fc9e3f..af8f24df811 100644 --- a/crypto/subtle/xor_loong64.go +++ b/crypto/internal/fips140/asan.go @@ -2,9 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build !purego +//go:build asan -package subtle +package fips140 -//go:noescape -func xorBytes(dst, a, b *byte, n int) +const asanEnabled = true diff --git a/crypto/internal/fips140/bigmod/_asm/go.mod b/crypto/internal/fips140/bigmod/_asm/go.mod new file mode 100644 index 00000000000..3773fa5aac1 --- /dev/null +++ b/crypto/internal/fips140/bigmod/_asm/go.mod @@ -0,0 +1,12 @@ +module crypto/internal/fips140/bigmod/_asm + +go 1.19 + +require github.com/mmcloughlin/avo v0.4.0 + +require ( + golang.org/x/mod v0.4.2 // indirect + golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 // indirect + golang.org/x/tools v0.1.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/crypto/internal/fips140/bigmod/_asm/go.sum b/crypto/internal/fips140/bigmod/_asm/go.sum new file mode 100644 index 00000000000..b4b59140f0d --- /dev/null +++ b/crypto/internal/fips140/bigmod/_asm/go.sum @@ -0,0 +1,32 @@ +github.com/mmcloughlin/avo v0.4.0 h1:jeHDRktVD+578ULxWpQHkilor6pkdLF7u7EiTzDbfcU= +github.com/mmcloughlin/avo v0.4.0/go.mod h1:RW9BfYA3TgO9uCdNrKU2h6J8cPD8ZLznvfgHAeszb1s= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk= +golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/crypto/internal/bigmod/_asm/nat_amd64_asm.go b/crypto/internal/fips140/bigmod/_asm/nat_amd64_asm.go similarity index 97% rename from crypto/internal/bigmod/_asm/nat_amd64_asm.go rename to crypto/internal/fips140/bigmod/_asm/nat_amd64_asm.go index 6bc87117ec3..548216dc482 100644 --- a/crypto/internal/bigmod/_asm/nat_amd64_asm.go +++ b/crypto/internal/fips140/bigmod/_asm/nat_amd64_asm.go @@ -15,7 +15,7 @@ import ( //go:generate go run . -out ../nat_amd64.s -pkg bigmod func main() { - Package("github.com/runZeroInc/excrypto/crypto/internal/bigmod") + Package("crypto/internal/fips140/bigmod") ConstraintExpr("!purego") addMulVVW(1024) diff --git a/crypto/internal/bigmod/nat.go b/crypto/internal/fips140/bigmod/nat.go similarity index 63% rename from crypto/internal/bigmod/nat.go rename to crypto/internal/fips140/bigmod/nat.go index 8b2621e6330..5fbb9958655 100644 --- a/crypto/internal/bigmod/nat.go +++ b/crypto/internal/fips140/bigmod/nat.go @@ -6,9 +6,11 @@ package bigmod import ( "errors" - "github.com/runZeroInc/excrypto/internal/byteorder" - "math/big" "math/bits" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" ) const ( @@ -18,6 +20,15 @@ const ( _S = _W / 8 ) +// Note: These functions make many loops over all the words in a Nat. +// These loops used to be in assembly, invisible to -race, -asan, and -msan, +// but now they are in Go and incur significant overhead in those modes. +// To bring the old performance back, we mark all functions that loop +// over Nat words with //go:norace. Because //go:norace does not +// propagate across inlining, we must also mark functions that inline +// //go:norace functions - specifically, those that inline add, addMulVVW, +// assign, cmpGeq, rshift1, and sub. + // choice represents a constant-time boolean. The value of choice is always // either 1 or 0. We use an int instead of bool in order to make decisions in // constant time by turning it into a mask. @@ -92,6 +103,32 @@ func (x *Nat) reset(n int) *Nat { return x } +// resetToBytes assigns x = b, where b is a slice of big-endian bytes, resizing +// n to the appropriate size. +// +// The announced length of x is set based on the actual bit size of the input, +// ignoring leading zeroes. +func (x *Nat) resetToBytes(b []byte) *Nat { + x.reset((len(b) + _S - 1) / _S) + if err := x.setBytes(b); err != nil { + panic("bigmod: internal error: bad arithmetic") + } + return x.trim() +} + +// trim reduces the size of x to match its value. +func (x *Nat) trim() *Nat { + // Trim most significant (trailing in little-endian) zero limbs. + // We assume comparison with zero (but not the branch) is constant time. + for i := len(x.limbs) - 1; i >= 0; i-- { + if x.limbs[i] != 0 { + break + } + x.limbs = x.limbs[:i] + } + return x +} + // set assigns x = y, optionally resizing x to the appropriate size. func (x *Nat) set(y *Nat) *Nat { x.reset(len(y.limbs)) @@ -99,23 +136,17 @@ func (x *Nat) set(y *Nat) *Nat { return x } -// setBig assigns x = n, optionally resizing n to the appropriate size. -// -// The announced length of x is set based on the actual bit size of the input, -// ignoring leading zeroes. -func (x *Nat) setBig(n *big.Int) *Nat { - limbs := n.Bits() - x.reset(len(limbs)) - for i := range limbs { - x.limbs[i] = uint(limbs[i]) - } - return x +// Bits returns x as a little-endian slice of uint. The length of the slice +// matches the announced length of x. The result and x share the same underlying +// array. +func (x *Nat) Bits() []uint { + return x.limbs } // Bytes returns x as a zero-extended big-endian byte slice. The size of the // slice will match the size of m. // -// x must have the same size as m and it must be reduced modulo m. +// x must have the same size as m and it must be less than or equal to m. func (x *Nat) Bytes(m *Modulus) []byte { i := m.Size() bytes := make([]byte, i) @@ -139,8 +170,11 @@ func (x *Nat) Bytes(m *Modulus) []byte { // SetBytes returns an error if b >= m. // // The output will be resized to the size of m and overwritten. +// +//go:norace func (x *Nat) SetBytes(b []byte, m *Modulus) (*Nat, error) { - if err := x.setBytes(b, m); err != nil { + x.resetFor(m) + if err := x.setBytes(b); err != nil { return nil, err } if x.cmpGeq(m.nat) == yes { @@ -155,11 +189,14 @@ func (x *Nat) SetBytes(b []byte, m *Modulus) (*Nat, error) { // // The output will be resized to the size of m and overwritten. func (x *Nat) SetOverflowingBytes(b []byte, m *Modulus) (*Nat, error) { - if err := x.setBytes(b, m); err != nil { + x.resetFor(m) + if err := x.setBytes(b); err != nil { return nil, err } - leading := _W - bitLen(x.limbs[len(x.limbs)-1]) - if leading < m.leading { + // setBytes would have returned an error if the input overflowed the limb + // size of the modulus, so now we only need to check if the most significant + // limb of x has more bits than the most significant limb of the modulus. + if bitLen(x.limbs[len(x.limbs)-1]) > bitLen(m.nat.limbs[len(m.nat.limbs)-1]) { return nil, errors.New("input overflows the modulus size") } x.maybeSubtractModulus(no, m) @@ -170,13 +207,12 @@ func (x *Nat) SetOverflowingBytes(b []byte, m *Modulus) (*Nat, error) { // big-endian encoded uint value. func bigEndianUint(buf []byte) uint { if _W == 64 { - return uint(byteorder.BeUint64(buf)) + return uint(byteorder.BEUint64(buf)) } - return uint(byteorder.BeUint32(buf)) + return uint(byteorder.BEUint32(buf)) } -func (x *Nat) setBytes(b []byte, m *Modulus) error { - x.resetFor(m) +func (x *Nat) setBytes(b []byte) error { i, k := len(b), 0 for k < len(x.limbs) && i >= _S { x.limbs[k] = bigEndianUint(b[i-_S : i]) @@ -193,9 +229,20 @@ func (x *Nat) setBytes(b []byte, m *Modulus) error { return nil } +// SetUint assigns x = y. +// +// The output will be resized to a single limb and overwritten. +func (x *Nat) SetUint(y uint) *Nat { + x.reset(1) + x.limbs[0] = y + return x +} + // Equal returns 1 if x == y, and 0 otherwise. // // Both operands must have the same announced length. +// +//go:norace func (x *Nat) Equal(y *Nat) choice { // Eliminate bounds checks in the loop. size := len(x.limbs) @@ -210,6 +257,8 @@ func (x *Nat) Equal(y *Nat) choice { } // IsZero returns 1 if x == 0, and 0 otherwise. +// +//go:norace func (x *Nat) IsZero() choice { // Eliminate bounds checks in the loop. size := len(x.limbs) @@ -222,9 +271,65 @@ func (x *Nat) IsZero() choice { return zero } +// IsOne returns 1 if x == 1, and 0 otherwise. +// +//go:norace +func (x *Nat) IsOne() choice { + // Eliminate bounds checks in the loop. + size := len(x.limbs) + xLimbs := x.limbs[:size] + + if len(xLimbs) == 0 { + return no + } + + one := ctEq(xLimbs[0], 1) + for i := 1; i < size; i++ { + one &= ctEq(xLimbs[i], 0) + } + return one +} + +// IsMinusOne returns 1 if x == -1 mod m, and 0 otherwise. +// +// The length of x must be the same as the modulus. x must already be reduced +// modulo m. +// +//go:norace +func (x *Nat) IsMinusOne(m *Modulus) choice { + minusOne := m.Nat() + minusOne.SubOne(m) + return x.Equal(minusOne) +} + +// IsOdd returns 1 if x is odd, and 0 otherwise. +func (x *Nat) IsOdd() choice { + if len(x.limbs) == 0 { + return no + } + return choice(x.limbs[0] & 1) +} + +// TrailingZeroBitsVarTime returns the number of trailing zero bits in x. +func (x *Nat) TrailingZeroBitsVarTime() uint { + var t uint + limbs := x.limbs + for _, l := range limbs { + if l == 0 { + t += _W + continue + } + t += uint(bits.TrailingZeros(l)) + break + } + return t +} + // cmpGeq returns 1 if x >= y, and 0 otherwise. // // Both operands must have the same announced length. +// +//go:norace func (x *Nat) cmpGeq(y *Nat) choice { // Eliminate bounds checks in the loop. size := len(x.limbs) @@ -243,6 +348,8 @@ func (x *Nat) cmpGeq(y *Nat) choice { // assign sets x <- y if on == 1, and does nothing otherwise. // // Both operands must have the same announced length. +// +//go:norace func (x *Nat) assign(on choice, y *Nat) *Nat { // Eliminate bounds checks in the loop. size := len(x.limbs) @@ -259,6 +366,8 @@ func (x *Nat) assign(on choice, y *Nat) *Nat { // add computes x += y and returns the carry. // // Both operands must have the same announced length. +// +//go:norace func (x *Nat) add(y *Nat) (c uint) { // Eliminate bounds checks in the loop. size := len(x.limbs) @@ -274,6 +383,8 @@ func (x *Nat) add(y *Nat) (c uint) { // sub computes x -= y. It returns the borrow of the subtraction. // // Both operands must have the same announced length. +// +//go:norace func (x *Nat) sub(y *Nat) (c uint) { // Eliminate bounds checks in the loop. size := len(x.limbs) @@ -286,21 +397,85 @@ func (x *Nat) sub(y *Nat) (c uint) { return } -// Modulus is used for modular arithmetic, precomputing relevant constants. +// ShiftRightVarTime sets x = x >> n. // -// Moduli are assumed to be odd numbers. Moduli can also leak the exact -// number of bits needed to store their value, and are stored without padding. +// The announced length of x is unchanged. // -// Their actual value is still kept secret. +//go:norace +func (x *Nat) ShiftRightVarTime(n uint) *Nat { + // Eliminate bounds checks in the loop. + size := len(x.limbs) + xLimbs := x.limbs[:size] + + shift := int(n % _W) + shiftLimbs := int(n / _W) + + var shiftedLimbs []uint + if shiftLimbs < size { + shiftedLimbs = xLimbs[shiftLimbs:] + } + + for i := range xLimbs { + if i >= len(shiftedLimbs) { + xLimbs[i] = 0 + continue + } + + xLimbs[i] = shiftedLimbs[i] >> shift + if i+1 < len(shiftedLimbs) { + xLimbs[i] |= shiftedLimbs[i+1] << (_W - shift) + } + } + + return x +} + +// BitLenVarTime returns the actual size of x in bits. +// +// The actual size of x (but nothing more) leaks through timing side-channels. +// Note that this is ordinarily secret, as opposed to the announced size of x. +func (x *Nat) BitLenVarTime() int { + // Eliminate bounds checks in the loop. + size := len(x.limbs) + xLimbs := x.limbs[:size] + + for i := size - 1; i >= 0; i-- { + if xLimbs[i] != 0 { + return i*_W + bitLen(xLimbs[i]) + } + } + return 0 +} + +// bitLen is a version of bits.Len that only leaks the bit length of n, but not +// its value. bits.Len and bits.LeadingZeros use a lookup table for the +// low-order bits on some architectures. +func bitLen(n uint) int { + len := 0 + // We assume, here and elsewhere, that comparison to zero is constant time + // with respect to different non-zero values. + for n != 0 { + len++ + n >>= 1 + } + return len +} + +// Modulus is used for modular arithmetic, precomputing relevant constants. +// +// A Modulus can leak the exact number of bits needed to store its value +// and is stored without padding. Its actual value is still kept secret. type Modulus struct { // The underlying natural number for this modulus. // // This will be stored without any padding, and shouldn't alias with any // other natural number being used. - nat *Nat - leading int // number of leading zeros in the modulus - m0inv uint // -nat.limbs[0]⁻¹ mod _W - rr *Nat // R*R for montgomeryRepresentation + nat *Nat + + // If m is even, the following fields are not set. + odd bool + m0inv uint // -nat.limbs[0]⁻¹ mod _W + rr *Nat // R*R for montgomeryRepresentation } // rr returns R*R with R = 2^(_W * n) and n = len(m.nat.limbs). @@ -369,36 +544,41 @@ func minusInverseModW(x uint) uint { return -y } -// NewModulusFromBig creates a new Modulus from a [big.Int]. +// NewModulus creates a new Modulus from a slice of big-endian bytes. The +// modulus must be greater than one. // -// The Int must be odd. The number of significant bits (and nothing else) is -// leaked through timing side-channels. -func NewModulusFromBig(n *big.Int) (*Modulus, error) { - if b := n.Bits(); len(b) == 0 { - return nil, errors.New("modulus must be >= 0") - } else if b[0]&1 != 1 { - return nil, errors.New("modulus must be odd") +// The number of significant bits and whether the modulus is even is leaked +// through timing side-channels. +func NewModulus(b []byte) (*Modulus, error) { + n := NewNat().resetToBytes(b) + return newModulus(n) +} + +// NewModulusProduct creates a new Modulus from the product of two numbers +// represented as big-endian byte slices. The result must be greater than one. +// +//go:norace +func NewModulusProduct(a, b []byte) (*Modulus, error) { + x := NewNat().resetToBytes(a) + y := NewNat().resetToBytes(b) + n := NewNat().reset(len(x.limbs) + len(y.limbs)) + for i := range y.limbs { + n.limbs[i+len(x.limbs)] = addMulVVW(n.limbs[i:i+len(x.limbs)], x.limbs, y.limbs[i]) } - m := &Modulus{} - m.nat = NewNat().setBig(n) - m.leading = _W - bitLen(m.nat.limbs[len(m.nat.limbs)-1]) - m.m0inv = minusInverseModW(m.nat.limbs[0]) - m.rr = rr(m) - return m, nil + return newModulus(n.trim()) } -// bitLen is a version of bits.Len that only leaks the bit length of n, but not -// its value. bits.Len and bits.LeadingZeros use a lookup table for the -// low-order bits on some architectures. -func bitLen(n uint) int { - var len int - // We assume, here and elsewhere, that comparison to zero is constant time - // with respect to different non-zero values. - for n != 0 { - len++ - n >>= 1 +func newModulus(n *Nat) (*Modulus, error) { + m := &Modulus{nat: n} + if m.nat.IsZero() == yes || m.nat.IsOne() == yes { + return nil, errors.New("modulus must be > 1") } - return len + if m.nat.IsOdd() == 1 { + m.odd = true + m.m0inv = minusInverseModW(m.nat.limbs[0]) + m.rr = rr(m) + } + return m, nil } // Size returns the size of m in bytes. @@ -408,17 +588,23 @@ func (m *Modulus) Size() int { // BitLen returns the size of m in bits. func (m *Modulus) BitLen() int { - return len(m.nat.limbs)*_W - int(m.leading) + return m.nat.BitLenVarTime() } -// Nat returns m as a Nat. The return value must not be written to. +// Nat returns m as a Nat. func (m *Modulus) Nat() *Nat { - return m.nat + // Make a copy so that the caller can't modify m.nat or alias it with + // another Nat in a modulus operation. + n := NewNat() + n.set(m.nat) + return n } // shiftIn calculates x = x << _W + y mod m. // // This assumes that x is already reduced mod m. +// +//go:norace func (x *Nat) shiftIn(y uint, m *Modulus) *Nat { d := NewNat().resetFor(m) @@ -458,6 +644,8 @@ func (x *Nat) shiftIn(y uint, m *Modulus) *Nat { // This works regardless how large the value of x is. // // The output will be resized to the size of m and overwritten. +// +//go:norace func (out *Nat) Mod(x *Nat, m *Modulus) *Nat { out.resetFor(m) // Working our way from the most significant to the least significant limb, @@ -507,6 +695,8 @@ func (out *Nat) resetFor(m *Modulus) *Nat { // overflowed its size, meaning abstractly x > 2^_W*n > m even if x < m. // // x and m operands must have the same announced length. +// +//go:norace func (x *Nat) maybeSubtractModulus(always choice, m *Modulus) { t := NewNat().set(x) underflow := t.sub(m.nat) @@ -520,6 +710,8 @@ func (x *Nat) maybeSubtractModulus(always choice, m *Modulus) { // // The length of both operands must be the same as the modulus. Both operands // must already be reduced modulo m. +// +//go:norace func (x *Nat) Sub(y *Nat, m *Modulus) *Nat { underflow := x.sub(y) // If the subtraction underflowed, add m. @@ -529,10 +721,23 @@ func (x *Nat) Sub(y *Nat, m *Modulus) *Nat { return x } +// SubOne computes x = x - 1 mod m. +// +// The length of x must be the same as the modulus. +func (x *Nat) SubOne(m *Modulus) *Nat { + one := NewNat().ExpandFor(m) + one.limbs[0] = 1 + // Sub asks for x to be reduced modulo m, while SubOne doesn't, but when + // y = 1, it works, and this is an internal use. + return x.Sub(one, m) +} + // Add computes x = x + y mod m. // // The length of both operands must be the same as the modulus. Both operands // must already be reduced modulo m. +// +//go:norace func (x *Nat) Add(y *Nat, m *Modulus) *Nat { overflow := x.add(y) x.maybeSubtractModulus(choice(overflow), m) @@ -570,6 +775,8 @@ func (x *Nat) montgomeryReduction(m *Modulus) *Nat { // // All inputs should be the same length and already reduced modulo m. // x will be resized to the size of m and overwritten. +// +//go:norace func (x *Nat) montgomeryMul(a *Nat, b *Nat, m *Modulus) *Nat { n := len(m.nat.limbs) mLimbs := m.nat.limbs[:n] @@ -691,6 +898,8 @@ func (x *Nat) montgomeryMul(a *Nat, b *Nat, m *Modulus) *Nat { // addMulVVW multiplies the multi-word value x by the single-word value y, // adding the result to the multi-word value z and returning the final carry. // It can be thought of as one row of a pen-and-paper column multiplication. +// +//go:norace func addMulVVW(z, x []uint, y uint) (carry uint) { _ = x[len(z)-1] // bounds check elimination hint for i := range z { @@ -711,18 +920,76 @@ func addMulVVW(z, x []uint, y uint) (carry uint) { // // The length of both operands must be the same as the modulus. Both operands // must already be reduced modulo m. +// +//go:norace func (x *Nat) Mul(y *Nat, m *Modulus) *Nat { - // A Montgomery multiplication by a value out of the Montgomery domain - // takes the result out of Montgomery representation. - xR := NewNat().set(x).montgomeryRepresentation(m) // xR = x * R mod m - return x.montgomeryMul(xR, y, m) // x = xR * y / R mod m + if m.odd { + // A Montgomery multiplication by a value out of the Montgomery domain + // takes the result out of Montgomery representation. + xR := NewNat().set(x).montgomeryRepresentation(m) // xR = x * R mod m + return x.montgomeryMul(xR, y, m) // x = xR * y / R mod m + } + + n := len(m.nat.limbs) + xLimbs := x.limbs[:n] + yLimbs := y.limbs[:n] + + switch n { + default: + // Attempt to use a stack-allocated backing array. + T := make([]uint, 0, preallocLimbs*2) + if cap(T) < n*2 { + T = make([]uint, 0, n*2) + } + T = T[:n*2] + + // T = x * y + for i := 0; i < n; i++ { + T[n+i] = addMulVVW(T[i:n+i], xLimbs, yLimbs[i]) + } + + // x = T mod m + return x.Mod(&Nat{limbs: T}, m) + + // The following specialized cases follow the exact same algorithm, but + // optimized for the sizes most used in RSA. See montgomeryMul for details. + case 1024 / _W: + const n = 1024 / _W // compiler hint + T := make([]uint, n*2) + for i := 0; i < n; i++ { + T[n+i] = addMulVVW1024(&T[i], &xLimbs[0], yLimbs[i]) + } + return x.Mod(&Nat{limbs: T}, m) + case 1536 / _W: + const n = 1536 / _W // compiler hint + T := make([]uint, n*2) + for i := 0; i < n; i++ { + T[n+i] = addMulVVW1536(&T[i], &xLimbs[0], yLimbs[i]) + } + return x.Mod(&Nat{limbs: T}, m) + case 2048 / _W: + const n = 2048 / _W // compiler hint + T := make([]uint, n*2) + for i := 0; i < n; i++ { + T[n+i] = addMulVVW2048(&T[i], &xLimbs[0], yLimbs[i]) + } + return x.Mod(&Nat{limbs: T}, m) + } } // Exp calculates out = x^e mod m. // // The exponent e is represented in big-endian order. The output will be resized // to the size of m and overwritten. x must already be reduced modulo m. +// +// m must be odd, or Exp will panic. +// +//go:norace func (out *Nat) Exp(x *Nat, e []byte, m *Modulus) *Nat { + if !m.odd { + panic("bigmod: modulus for Exp must be odd") + } + // We use a 4 bit window. For our RSA workload, 4 bit windows are faster // than 2 bit windows, but use an extra 12 nats worth of scratch space. // Using bit sizes that don't divide 8 are more complex to implement, but @@ -771,13 +1038,18 @@ func (out *Nat) Exp(x *Nat, e []byte, m *Modulus) *Nat { // // The output will be resized to the size of m and overwritten. x must already // be reduced modulo m. This leaks the exponent through timing side-channels. +// +// m must be odd, or ExpShortVarTime will panic. func (out *Nat) ExpShortVarTime(x *Nat, e uint, m *Modulus) *Nat { + if !m.odd { + panic("bigmod: modulus for ExpShortVarTime must be odd") + } // For short exponents, precomputing a table and using a window like in Exp // doesn't pay off. Instead, we do a simple conditional square-and-multiply // chain, skipping the initial run of zeroes. xR := NewNat().set(x).montgomeryRepresentation(m) out.set(xR) - for i := bits.UintSize - bitLen(e) + 1; i < bits.UintSize; i++ { + for i := bits.UintSize - bits.Len(e) + 1; i < bits.UintSize; i++ { out.montgomeryMul(out, out, m) if k := (e >> (bits.UintSize - i - 1)) & 1; k != 0 { out.montgomeryMul(out, xR, m) @@ -785,3 +1057,174 @@ func (out *Nat) ExpShortVarTime(x *Nat, e uint, m *Modulus) *Nat { } return out.montgomeryReduction(m) } + +// InverseVarTime calculates x = a⁻¹ mod m and returns (x, true) if a is +// invertible. Otherwise, InverseVarTime returns (x, false) and x is not +// modified. +// +// a must be reduced modulo m, but doesn't need to have the same size. The +// output will be resized to the size of m and overwritten. +// +//go:norace +func (x *Nat) InverseVarTime(a *Nat, m *Modulus) (*Nat, bool) { + u, A, err := extendedGCD(a, m.nat) + if err != nil { + return x, false + } + if u.IsOne() == no { + return x, false + } + return x.set(A), true +} + +// GCDVarTime calculates x = GCD(a, b) where at least one of a or b is odd, and +// both are non-zero. If GCDVarTime returns an error, x is not modified. +// +// The output will be resized to the size of the larger of a and b. +func (x *Nat) GCDVarTime(a, b *Nat) (*Nat, error) { + u, _, err := extendedGCD(a, b) + if err != nil { + return nil, err + } + return x.set(u), nil +} + +// extendedGCD computes u and A such that a = GCD(a, m) and u = A*a - B*m. +// +// u will have the size of the larger of a and m, and A will have the size of m. +// +// It is an error if either a or m is zero, or if they are both even. +func extendedGCD(a, m *Nat) (u, A *Nat, err error) { + // This is the extended binary GCD algorithm described in the Handbook of + // Applied Cryptography, Algorithm 14.61, adapted by BoringSSL to bound + // coefficients and avoid negative numbers. For more details and proof of + // correctness, see https://github.com/mit-plv/fiat-crypto/pull/333/files. + // + // Following the proof linked in the PR above, the changes are: + // + // 1. Negate [B] and [C] so they are positive. The invariant now involves a + // subtraction. + // 2. If step 2 (both [x] and [y] are even) runs, abort immediately. This + // case needs to be handled by the caller. + // 3. Subtract copies of [x] and [y] as needed in step 6 (both [u] and [v] + // are odd) so coefficients stay in bounds. + // 4. Replace the [u >= v] check with [u > v]. This changes the end + // condition to [v = 0] rather than [u = 0]. This saves an extra + // subtraction due to which coefficients were negated. + // 5. Rename x and y to a and n, to capture that one is a modulus. + // 6. Rearrange steps 4 through 6 slightly. Merge the loops in steps 4 and + // 5 into the main loop (step 7's goto), and move step 6 to the start of + // the loop iteration, ensuring each loop iteration halves at least one + // value. + // + // Note this algorithm does not handle either input being zero. + + if a.IsZero() == yes || m.IsZero() == yes { + return nil, nil, errors.New("extendedGCD: a or m is zero") + } + if a.IsOdd() == no && m.IsOdd() == no { + return nil, nil, errors.New("extendedGCD: both a and m are even") + } + + size := max(len(a.limbs), len(m.limbs)) + u = NewNat().set(a).expand(size) + v := NewNat().set(m).expand(size) + + A = NewNat().reset(len(m.limbs)) + A.limbs[0] = 1 + B := NewNat().reset(len(a.limbs)) + C := NewNat().reset(len(m.limbs)) + D := NewNat().reset(len(a.limbs)) + D.limbs[0] = 1 + + // Before and after each loop iteration, the following hold: + // + // u = A*a - B*m + // v = D*m - C*a + // 0 < u <= a + // 0 <= v <= m + // 0 <= A < m + // 0 <= B <= a + // 0 <= C < m + // 0 <= D <= a + // + // After each loop iteration, u and v only get smaller, and at least one of + // them shrinks by at least a factor of two. + for { + // If both u and v are odd, subtract the smaller from the larger. + // If u = v, we need to subtract from v to hit the modified exit condition. + if u.IsOdd() == yes && v.IsOdd() == yes { + if v.cmpGeq(u) == no { + u.sub(v) + A.Add(C, &Modulus{nat: m}) + B.Add(D, &Modulus{nat: a}) + } else { + v.sub(u) + C.Add(A, &Modulus{nat: m}) + D.Add(B, &Modulus{nat: a}) + } + } + + // Exactly one of u and v is now even. + if u.IsOdd() == v.IsOdd() { + panic("bigmod: internal error: u and v are not in the expected state") + } + + // Halve the even one and adjust the corresponding coefficient. + if u.IsOdd() == no { + rshift1(u, 0) + if A.IsOdd() == yes || B.IsOdd() == yes { + rshift1(A, A.add(m)) + rshift1(B, B.add(a)) + } else { + rshift1(A, 0) + rshift1(B, 0) + } + } else { // v.IsOdd() == no + rshift1(v, 0) + if C.IsOdd() == yes || D.IsOdd() == yes { + rshift1(C, C.add(m)) + rshift1(D, D.add(a)) + } else { + rshift1(C, 0) + rshift1(D, 0) + } + } + + if v.IsZero() == yes { + return u, A, nil + } + } +} + +//go:norace +func rshift1(a *Nat, carry uint) { + size := len(a.limbs) + aLimbs := a.limbs[:size] + + for i := range size { + aLimbs[i] >>= 1 + if i+1 < size { + aLimbs[i] |= aLimbs[i+1] << (_W - 1) + } else { + aLimbs[i] |= carry << (_W - 1) + } + } +} + +// DivShortVarTime calculates x = x / y and returns the remainder. +// +// It panics if y is zero. +// +//go:norace +func (x *Nat) DivShortVarTime(y uint) uint { + if y == 0 { + panic("bigmod: division by zero") + } + + var r uint + for i := len(x.limbs) - 1; i >= 0; i-- { + x.limbs[i], r = bits.Div(r, x.limbs[i], y) + } + return r +} diff --git a/crypto/internal/bigmod/nat_386.s b/crypto/internal/fips140/bigmod/nat_386.s similarity index 85% rename from crypto/internal/bigmod/nat_386.s rename to crypto/internal/fips140/bigmod/nat_386.s index b66f33fe054..0637d271e83 100644 --- a/crypto/internal/bigmod/nat_386.s +++ b/crypto/internal/fips140/bigmod/nat_386.s @@ -9,19 +9,19 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB), $0-16 MOVL $32, BX - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB), $0-16 MOVL $48, BX - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB), $0-16 MOVL $64, BX - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) -TEXT addMulVVWxExCrypto(SB), NOFRAME|NOSPLIT, $0 +TEXT addMulVVWx(SB), NOFRAME|NOSPLIT, $0 MOVL z+0(FP), DI MOVL x+4(FP), SI MOVL y+8(FP), BP diff --git a/crypto/internal/bigmod/nat_amd64.s b/crypto/internal/fips140/bigmod/nat_amd64.s similarity index 100% rename from crypto/internal/bigmod/nat_amd64.s rename to crypto/internal/fips140/bigmod/nat_amd64.s diff --git a/crypto/internal/bigmod/nat_arm.s b/crypto/internal/fips140/bigmod/nat_arm.s similarity index 85% rename from crypto/internal/bigmod/nat_arm.s rename to crypto/internal/fips140/bigmod/nat_arm.s index d89f51461df..c7397b89c5f 100644 --- a/crypto/internal/bigmod/nat_arm.s +++ b/crypto/internal/fips140/bigmod/nat_arm.s @@ -9,19 +9,19 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB), $0-16 MOVW $32, R5 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB), $0-16 MOVW $48, R5 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB), $0-16 MOVW $64, R5 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) -TEXT addMulVVWxExCrypto(SB), NOFRAME|NOSPLIT, $0 +TEXT addMulVVWx(SB), NOFRAME|NOSPLIT, $0 MOVW $0, R0 MOVW z+0(FP), R1 MOVW x+4(FP), R2 diff --git a/crypto/internal/bigmod/nat_arm64.s b/crypto/internal/fips140/bigmod/nat_arm64.s similarity index 91% rename from crypto/internal/bigmod/nat_arm64.s rename to crypto/internal/fips140/bigmod/nat_arm64.s index c6b3593cc15..ba1e6118cc8 100644 --- a/crypto/internal/bigmod/nat_arm64.s +++ b/crypto/internal/fips140/bigmod/nat_arm64.s @@ -9,19 +9,19 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB), $0-32 MOVD $16, R0 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB), $0-32 MOVD $24, R0 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB), $0-32 MOVD $32, R0 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) -TEXT addMulVVWxExCrypto(SB), NOFRAME|NOSPLIT, $0 +TEXT addMulVVWx(SB), NOFRAME|NOSPLIT, $0 MOVD z+0(FP), R1 MOVD x+8(FP), R2 MOVD y+16(FP), R3 diff --git a/crypto/internal/bigmod/nat_asm.go b/crypto/internal/fips140/bigmod/nat_asm.go similarity index 77% rename from crypto/internal/bigmod/nat_asm.go rename to crypto/internal/fips140/bigmod/nat_asm.go index b92892edded..42276c35d9c 100644 --- a/crypto/internal/bigmod/nat_asm.go +++ b/crypto/internal/fips140/bigmod/nat_asm.go @@ -6,7 +6,10 @@ package bigmod -import "github.com/runZeroInc/excrypto/internal/cpu" +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) // amd64 assembly uses ADCX/ADOX/MULX if ADX is available to run two carry // chains in the flags in parallel across the whole operation, and aggressively @@ -16,7 +19,13 @@ import "github.com/runZeroInc/excrypto/internal/cpu" // amd64 without ADX, perform better than the compiler output. // TODO(filippo): file cmd/compile performance issue. -var supportADX = cpu.X86.HasADX && cpu.X86.HasBMI2 +var supportADX = cpu.X86HasADX && cpu.X86HasBMI2 + +func init() { + if cpu.AMD64 { + impl.Register("aes", "ADX", &supportADX) + } +} //go:noescape func addMulVVW1024(z, x *uint, y uint) (c uint) diff --git a/crypto/internal/bigmod/nat_loong64.s b/crypto/internal/fips140/bigmod/nat_loong64.s similarity index 92% rename from crypto/internal/bigmod/nat_loong64.s rename to crypto/internal/fips140/bigmod/nat_loong64.s index db93a1ea9f6..4e88586da8d 100644 --- a/crypto/internal/bigmod/nat_loong64.s +++ b/crypto/internal/fips140/bigmod/nat_loong64.s @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// derived from crypto/internal/bigmod/nat_riscv64.s +// derived from crypto/internal/fips140/bigmod/nat_riscv64.s //go:build !purego @@ -11,19 +11,19 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB),$0-32 MOVV $16, R8 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB),$0-32 MOVV $24, R8 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB),$0-32 MOVV $32, R8 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) -TEXT addMulVVWxExCrypto(SB),NOFRAME|NOSPLIT,$0 +TEXT addMulVVWx(SB),NOFRAME|NOSPLIT,$0 MOVV z+0(FP), R4 MOVV x+8(FP), R6 MOVV y+16(FP), R5 diff --git a/crypto/internal/bigmod/nat_noasm.go b/crypto/internal/fips140/bigmod/nat_noasm.go similarity index 92% rename from crypto/internal/bigmod/nat_noasm.go rename to crypto/internal/fips140/bigmod/nat_noasm.go index 2501a6fb4ce..dbec229f5d2 100644 --- a/crypto/internal/bigmod/nat_noasm.go +++ b/crypto/internal/fips140/bigmod/nat_noasm.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build purego || !(386 || amd64 || arm || arm64 || loong64 || ppc64 || ppc64le || riscv64 || s390x) +//go:build purego || !(386 || amd64 || arm || arm64 || loong64 || ppc64 || ppc64le || riscv64 || s390x || wasm) package bigmod diff --git a/crypto/internal/bigmod/nat_ppc64x.s b/crypto/internal/fips140/bigmod/nat_ppc64x.s similarity index 93% rename from crypto/internal/bigmod/nat_ppc64x.s rename to crypto/internal/fips140/bigmod/nat_ppc64x.s index eac02b74b75..94260ca29f3 100644 --- a/crypto/internal/bigmod/nat_ppc64x.s +++ b/crypto/internal/fips140/bigmod/nat_ppc64x.s @@ -9,17 +9,17 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB), $0-32 MOVD $4, R6 // R6 = z_len/4 - JMP addMulVVWxExCrypto<>(SB) + JMP addMulVVWx<>(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB), $0-32 MOVD $6, R6 // R6 = z_len/4 - JMP addMulVVWxExCrypto<>(SB) + JMP addMulVVWx<>(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB), $0-32 MOVD $8, R6 // R6 = z_len/4 - JMP addMulVVWxExCrypto<>(SB) + JMP addMulVVWx<>(SB) // This local function expects to be called only by // callers above. R6 contains the z length/4 @@ -27,7 +27,7 @@ TEXT ·addMulVVW2048(SB), $0-32 // loop iteration, and is guaranteed to be > 0. // If other callers are added this function might // need to change. -TEXT addMulVVWxExCrypto<>(SB), NOSPLIT, $0 +TEXT addMulVVWx<>(SB), NOSPLIT, $0 MOVD z+0(FP), R3 MOVD x+8(FP), R4 MOVD y+16(FP), R5 diff --git a/crypto/internal/bigmod/nat_riscv64.s b/crypto/internal/fips140/bigmod/nat_riscv64.s similarity index 94% rename from crypto/internal/bigmod/nat_riscv64.s rename to crypto/internal/fips140/bigmod/nat_riscv64.s index 8ce4f19caa7..c1d9cc0dd48 100644 --- a/crypto/internal/bigmod/nat_riscv64.s +++ b/crypto/internal/fips140/bigmod/nat_riscv64.s @@ -9,19 +9,19 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB),$0-32 MOV $16, X30 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB),$0-32 MOV $24, X30 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB),$0-32 MOV $32, X30 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) -TEXT addMulVVWxExCrypto(SB),NOFRAME|NOSPLIT,$0 +TEXT addMulVVWx(SB),NOFRAME|NOSPLIT,$0 MOV z+0(FP), X5 MOV x+8(FP), X7 MOV y+16(FP), X6 diff --git a/crypto/internal/bigmod/nat_s390x.s b/crypto/internal/fips140/bigmod/nat_s390x.s similarity index 91% rename from crypto/internal/bigmod/nat_s390x.s rename to crypto/internal/fips140/bigmod/nat_s390x.s index 9248619b6cf..0c07a0c8a6d 100644 --- a/crypto/internal/bigmod/nat_s390x.s +++ b/crypto/internal/fips140/bigmod/nat_s390x.s @@ -9,19 +9,19 @@ // func addMulVVW1024(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1024(SB), $0-32 MOVD $16, R5 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW1536(z, x *uint, y uint) (c uint) TEXT ·addMulVVW1536(SB), $0-32 MOVD $24, R5 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) // func addMulVVW2048(z, x *uint, y uint) (c uint) TEXT ·addMulVVW2048(SB), $0-32 MOVD $32, R5 - JMP addMulVVWxExCrypto(SB) + JMP addMulVVWx(SB) -TEXT addMulVVWxExCrypto(SB), NOFRAME|NOSPLIT, $0 +TEXT addMulVVWx(SB), NOFRAME|NOSPLIT, $0 MOVD z+0(FP), R2 MOVD x+8(FP), R8 MOVD y+16(FP), R9 diff --git a/crypto/internal/bigmod/nat_test.go b/crypto/internal/fips140/bigmod/nat_test.go similarity index 60% rename from crypto/internal/bigmod/nat_test.go rename to crypto/internal/fips140/bigmod/nat_test.go index 7a956e3a57d..36ea559d977 100644 --- a/crypto/internal/bigmod/nat_test.go +++ b/crypto/internal/fips140/bigmod/nat_test.go @@ -5,16 +5,43 @@ package bigmod import ( + "bufio" + "bytes" + cryptorand "crypto/rand" + "encoding/hex" "fmt" "math/big" "math/bits" "math/rand" + "os" "reflect" + "slices" "strings" "testing" "testing/quick" ) +// setBig assigns x = n, optionally resizing n to the appropriate size. +// +// The announced length of x is set based on the actual bit size of the input, +// ignoring leading zeroes. +func (x *Nat) setBig(n *big.Int) *Nat { + limbs := n.Bits() + x.reset(len(limbs)) + for i := range limbs { + x.limbs[i] = uint(limbs[i]) + } + return x +} + +func (n *Nat) asBig() *big.Int { + bits := make([]big.Word, len(n.limbs)) + for i := range n.limbs { + bits[i] = big.Word(n.limbs[i]) + } + return new(big.Int).SetBits(bits) +} + func (n *Nat) String() string { var limbs []string for i := range n.limbs { @@ -70,7 +97,7 @@ func TestMontgomeryRoundtrip(t *testing.T) { one.limbs[0] = 1 aPlusOne := new(big.Int).SetBytes(natBytes(a)) aPlusOne.Add(aPlusOne, big.NewInt(1)) - m, _ := NewModulusFromBig(aPlusOne) + m, _ := NewModulus(aPlusOne.Bytes()) monty := new(Nat).set(a) monty.montgomeryRepresentation(m) aAgain := new(Nat).set(monty) @@ -319,7 +346,7 @@ func TestMulReductions(t *testing.T) { b, _ := new(big.Int).SetString("180692823610368451951102211649591374573781973061758082626801", 10) n := new(big.Int).Mul(a, b) - N, _ := NewModulusFromBig(n) + N, _ := NewModulus(n.Bytes()) A := NewNat().setBig(a).ExpandFor(N) B := NewNat().setBig(b).ExpandFor(N) @@ -328,7 +355,7 @@ func TestMulReductions(t *testing.T) { } i := new(big.Int).ModInverse(a, b) - N, _ = NewModulusFromBig(b) + N, _ = NewModulus(b.Bytes()) A = NewNat().setBig(a).ExpandFor(N) I := NewNat().setBig(i).ExpandFor(N) one := NewNat().setBig(big.NewInt(1)).ExpandFor(N) @@ -338,6 +365,148 @@ func TestMulReductions(t *testing.T) { } } +func TestMul(t *testing.T) { + t.Run("small", func(t *testing.T) { testMul(t, 760/8) }) + t.Run("1024", func(t *testing.T) { testMul(t, 1024/8) }) + t.Run("1536", func(t *testing.T) { testMul(t, 1536/8) }) + t.Run("2048", func(t *testing.T) { testMul(t, 2048/8) }) +} + +func testMul(t *testing.T, n int) { + a, b, m := make([]byte, n), make([]byte, n), make([]byte, n) + cryptorand.Read(a) + cryptorand.Read(b) + cryptorand.Read(m) + + // Pick the highest as the modulus. + if bytes.Compare(a, m) > 0 { + a, m = m, a + } + if bytes.Compare(b, m) > 0 { + b, m = m, b + } + + M, err := NewModulus(m) + if err != nil { + t.Fatal(err) + } + A, err := NewNat().SetBytes(a, M) + if err != nil { + t.Fatal(err) + } + B, err := NewNat().SetBytes(b, M) + if err != nil { + t.Fatal(err) + } + + A.Mul(B, M) + ABytes := A.Bytes(M) + + mBig := new(big.Int).SetBytes(m) + aBig := new(big.Int).SetBytes(a) + bBig := new(big.Int).SetBytes(b) + nBig := new(big.Int).Mul(aBig, bBig) + nBig.Mod(nBig, mBig) + nBigBytes := make([]byte, len(ABytes)) + nBig.FillBytes(nBigBytes) + + if !bytes.Equal(ABytes, nBigBytes) { + t.Errorf("got %x, want %x", ABytes, nBigBytes) + } +} + +func TestIs(t *testing.T) { + checkYes := func(c choice, err string) { + t.Helper() + if c != yes { + t.Error(err) + } + } + checkNot := func(c choice, err string) { + t.Helper() + if c != no { + t.Error(err) + } + } + + mFour := modulusFromBytes([]byte{4}) + n, err := NewNat().SetBytes([]byte{3}, mFour) + if err != nil { + t.Fatal(err) + } + checkYes(n.IsMinusOne(mFour), "3 is not -1 mod 4") + checkNot(n.IsZero(), "3 is zero") + checkNot(n.IsOne(), "3 is one") + checkYes(n.IsOdd(), "3 is not odd") + n.SubOne(mFour) + checkNot(n.IsMinusOne(mFour), "2 is -1 mod 4") + checkNot(n.IsZero(), "2 is zero") + checkNot(n.IsOne(), "2 is one") + checkNot(n.IsOdd(), "2 is odd") + n.SubOne(mFour) + checkNot(n.IsMinusOne(mFour), "1 is -1 mod 4") + checkNot(n.IsZero(), "1 is zero") + checkYes(n.IsOne(), "1 is not one") + checkYes(n.IsOdd(), "1 is not odd") + n.SubOne(mFour) + checkNot(n.IsMinusOne(mFour), "0 is -1 mod 4") + checkYes(n.IsZero(), "0 is not zero") + checkNot(n.IsOne(), "0 is one") + checkNot(n.IsOdd(), "0 is odd") + n.SubOne(mFour) + checkYes(n.IsMinusOne(mFour), "-1 is not -1 mod 4") + checkNot(n.IsZero(), "-1 is zero") + checkNot(n.IsOne(), "-1 is one") + checkYes(n.IsOdd(), "-1 mod 4 is not odd") + + mTwoLimbs := maxModulus(2) + n, err = NewNat().SetBytes([]byte{0x01}, mTwoLimbs) + if err != nil { + t.Fatal(err) + } + if n.IsOne() != 1 { + t.Errorf("1 is not one") + } +} + +func TestTrailingZeroBits(t *testing.T) { + nb := new(big.Int).SetBytes([]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e}) + nb.Lsh(nb, 128) + expected := 129 + for expected >= 0 { + n := NewNat().setBig(nb) + if n.TrailingZeroBitsVarTime() != uint(expected) { + t.Errorf("%d != %d", n.TrailingZeroBitsVarTime(), expected) + } + nb.Rsh(nb, 1) + expected-- + } +} + +func TestRightShift(t *testing.T) { + nb, err := cryptorand.Int(cryptorand.Reader, new(big.Int).Lsh(big.NewInt(1), 1024)) + if err != nil { + t.Fatal(err) + } + for _, shift := range []uint{1, 32, 64, 128, 1024 - 128, 1024 - 64, 1024 - 32, 1024 - 1} { + testShift := func(t *testing.T, shift uint) { + n := NewNat().setBig(nb) + oldLen := len(n.limbs) + n.ShiftRightVarTime(shift) + if len(n.limbs) != oldLen { + t.Errorf("len(n.limbs) = %d, want %d", len(n.limbs), oldLen) + } + exp := new(big.Int).Rsh(nb, shift) + if n.asBig().Cmp(exp) != 0 { + t.Errorf("%v != %v", n.asBig(), exp) + } + } + t.Run(fmt.Sprint(shift-1), func(t *testing.T) { testShift(t, shift-1) }) + t.Run(fmt.Sprint(shift), func(t *testing.T) { testShift(t, shift) }) + t.Run(fmt.Sprint(shift+1), func(t *testing.T) { testShift(t, shift+1) }) + } +} + func natBytes(n *Nat) []byte { return n.Bytes(maxModulus(uint(len(n.limbs)))) } @@ -350,7 +519,7 @@ func natFromBytes(b []byte) *Nat { func modulusFromBytes(b []byte) *Modulus { bb := new(big.Int).SetBytes(b) - m, _ := NewModulusFromBig(bb) + m, _ := NewModulus(bb.Bytes()) return m } @@ -359,7 +528,7 @@ func maxModulus(n uint) *Modulus { b := big.NewInt(1) b.Lsh(b, n*_W) b.Sub(b, big.NewInt(1)) - m, _ := NewModulusFromBig(b) + m, _ := NewModulus(b.Bytes()) return m } @@ -465,16 +634,131 @@ func BenchmarkExp(b *testing.B) { } } -func TestNewModFromBigZero(t *testing.T) { - expected := "modulus must be >= 0" - _, err := NewModulusFromBig(big.NewInt(0)) +func TestNewModulus(t *testing.T) { + expected := "modulus must be > 1" + _, err := NewModulus([]byte{}) if err == nil || err.Error() != expected { - t.Errorf("NewModulusFromBig(0) got %q, want %q", err, expected) + t.Errorf("NewModulus(0) got %q, want %q", err, expected) } - - expected = "modulus must be odd" - _, err = NewModulusFromBig(big.NewInt(2)) + _, err = NewModulus([]byte{0}) + if err == nil || err.Error() != expected { + t.Errorf("NewModulus(0) got %q, want %q", err, expected) + } + _, err = NewModulus([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}) if err == nil || err.Error() != expected { - t.Errorf("NewModulusFromBig(2) got %q, want %q", err, expected) + t.Errorf("NewModulus(0) got %q, want %q", err, expected) + } + _, err = NewModulus([]byte{1}) + if err == nil || err.Error() != expected { + t.Errorf("NewModulus(1) got %q, want %q", err, expected) + } + _, err = NewModulus([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}) + if err == nil || err.Error() != expected { + t.Errorf("NewModulus(1) got %q, want %q", err, expected) + } +} + +func makeTestValue(nbits int) []uint { + n := nbits / _W + x := make([]uint, n) + for i := range n { + x[i]-- + } + return x +} + +func TestAddMulVVWSized(t *testing.T) { + // Sized addMulVVW have architecture-specific implementations on + // a number of architectures. Test that they match the generic + // implementation. + tests := []struct { + n int + f func(z, x *uint, y uint) uint + }{ + {1024, addMulVVW1024}, + {1536, addMulVVW1536}, + {2048, addMulVVW2048}, + } + for _, test := range tests { + t.Run(fmt.Sprint(test.n), func(t *testing.T) { + x := makeTestValue(test.n) + z := makeTestValue(test.n) + z2 := slices.Clone(z) + var y uint + y-- + c := addMulVVW(z, x, y) + c2 := test.f(&z2[0], &x[0], y) + if !slices.Equal(z, z2) || c != c2 { + t.Errorf("%016X, %016X != %016X, %016X", z, c, z2, c2) + } + }) + } +} + +func TestInverse(t *testing.T) { + f, err := os.Open("testdata/mod_inv_tests.txt") + if err != nil { + t.Fatal(err) + } + + var ModInv, A, M string + var lineNum int + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lineNum++ + line := scanner.Text() + if len(line) == 0 || line[0] == '#' { + continue + } + + k, v, _ := strings.Cut(line, " = ") + switch k { + case "ModInv": + ModInv = v + case "A": + A = v + case "M": + M = v + + t.Run(fmt.Sprintf("line %d", lineNum), func(t *testing.T) { + m, err := NewModulus(decodeHex(t, M)) + if err != nil { + t.Skip("modulus <= 1") + } + a, err := NewNat().SetBytes(decodeHex(t, A), m) + if err != nil { + t.Fatal(err) + } + + got, ok := NewNat().InverseVarTime(a, m) + if !ok { + t.Fatal("not invertible") + } + exp, err := NewNat().SetBytes(decodeHex(t, ModInv), m) + if err != nil { + t.Fatal(err) + } + if got.Equal(exp) != 1 { + t.Errorf("%v != %v", got, exp) + } + }) + default: + t.Fatalf("unknown key %q on line %d", k, lineNum) + } + } + if err := scanner.Err(); err != nil { + t.Fatal(err) + } +} + +func decodeHex(t *testing.T, s string) []byte { + t.Helper() + if len(s)%2 != 0 { + s = "0" + s + } + b, err := hex.DecodeString(s) + if err != nil { + t.Fatalf("failed to decode hex %q: %v", s, err) } + return b } diff --git a/crypto/internal/fips140/bigmod/nat_wasm.go b/crypto/internal/fips140/bigmod/nat_wasm.go new file mode 100644 index 00000000000..b4aaff74cf0 --- /dev/null +++ b/crypto/internal/fips140/bigmod/nat_wasm.go @@ -0,0 +1,61 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package bigmod + +import "unsafe" + +// The generic implementation relies on 64x64->128 bit multiplication and +// 64-bit add-with-carry, which are compiler intrinsics on many architectures. +// Wasm doesn't support those. Here we implement it with 32x32->64 bit +// operations, which is more efficient on Wasm. + +func idx(x *uint, i uintptr) *uint { + return (*uint)(unsafe.Pointer(uintptr(unsafe.Pointer(x)) + i*8)) +} + +func addMulVVWWasm(z, x *uint, y uint, n uintptr) (carry uint) { + const mask32 = 1<<32 - 1 + y0 := y & mask32 + y1 := y >> 32 + for i := range n { + xi := *idx(x, i) + x0 := xi & mask32 + x1 := xi >> 32 + zi := *idx(z, i) + z0 := zi & mask32 + z1 := zi >> 32 + c0 := carry & mask32 + c1 := carry >> 32 + + w00 := x0*y0 + z0 + c0 + l00 := w00 & mask32 + h00 := w00 >> 32 + + w01 := x0*y1 + z1 + h00 + l01 := w01 & mask32 + h01 := w01 >> 32 + + w10 := x1*y0 + c1 + l01 + h10 := w10 >> 32 + + carry = x1*y1 + h10 + h01 + *idx(z, i) = w10<<32 + l00 + } + return carry +} + +func addMulVVW1024(z, x *uint, y uint) (c uint) { + return addMulVVWWasm(z, x, y, 1024/_W) +} + +func addMulVVW1536(z, x *uint, y uint) (c uint) { + return addMulVVWWasm(z, x, y, 1536/_W) +} + +func addMulVVW2048(z, x *uint, y uint) (c uint) { + return addMulVVWWasm(z, x, y, 2048/_W) +} diff --git a/crypto/internal/fips140/bigmod/testdata/mod_inv_tests.txt b/crypto/internal/fips140/bigmod/testdata/mod_inv_tests.txt new file mode 100644 index 00000000000..4ebc1966574 --- /dev/null +++ b/crypto/internal/fips140/bigmod/testdata/mod_inv_tests.txt @@ -0,0 +1,115 @@ +# ModInv tests. +# +# These test vectors satisfy ModInv * A = 1 (mod M) and 0 <= ModInv < M. + +ModInv = 00 +A = 00 +M = 01 + +ModInv = 00 +A = 01 +M = 01 + +ModInv = 00 +A = 02 +M = 01 + +ModInv = 00 +A = 03 +M = 01 + +ModInv = 64 +A = 54 +M = e3 + +ModInv = 13 +A = 2b +M = 30 + +ModInv = 2f +A = 30 +M = 37 + +ModInv = 4 +A = 13 +M = 4b + +ModInv = 1c47 +A = cd4 +M = 6a21 + +ModInv = 2b97 +A = 8e7 +M = 49c0 + +ModInv = 29b9 +A = fcb +M = 3092 + +ModInv = a83 +A = 14bf +M = 41ae + +ModInv = 18f15fe1 +A = 11b5d53e +M = 322e92a1 + +ModInv = 32f9453b +A = 8af6df6 +M = 33d45eb7 + +ModInv = d696369 +A = c5f89dd5 +M = fc09c17c + +ModInv = 622839d8 +A = 60c2526 +M = 74200493 + +ModInv = fb5a8aee7bbc4ef +A = 24ebd835a70be4e2 +M = 9c7256574e0c5e93 + +ModInv = 846bc225402419c +A = 23026003ab1fbdb +M = 1683cbe32779c59b + +ModInv = 5ff84f63a78982f9 +A = 4a2420dc733e1a0f +M = a73c6bfabefa09e6 + +ModInv = 133e74d28ef42b43 +A = 2e9511ae29cdd41 +M = 15234df99f19fcda + +ModInv = 46ae1fabe9521e4b99b198fc8439609023aa69be2247c0d1e27c2a0ea332f9c5 +A = 6331fec5f01014046788c919ed50dc86ac7a80c085f1b6f645dd179c0f0dc9cd +M = 8ef409de82318259a8655a39293b1e762fa2cc7e0aeb4c59713a1e1fff6af640 + +ModInv = 444ccea3a7b21677dd294d34de53cc8a5b51e69b37782310a00fc6bcc975709b +A = 679280bd880994c08322143a4ea8a0825d0466fda1bb6b3eb86fc8e90747512b +M = e4fecab84b365c63a0dab4244ce3f921a9c87ec64d69a2031939f55782e99a2e + +ModInv = 1ac7d7a03ceec5f690f567c9d61bf3469c078285bcc5cf00ac944596e887ca17 +A = 1593ef32d9c784f5091bdff952f5c5f592a3aed6ba8ea865efa6d7df87be1805 +M = 1e276882f90c95e0c1976eb079f97af075445b1361c02018d6bd7191162e67b2 + +ModInv = 639108b90dfe946f498be21303058413bbb0e59d0bd6a6115788705abd0666d6 +A = 9258d6238e4923d120b2d1033573ffcac691526ad0842a3b174dccdbb79887bd +M = ce62909c39371d463aaba3d4b72ea6da49cb9b529e39e1972ef3ccd9a66fe08f + +ModInv = aebde7654cb17833a106231c4b9e2f519140e85faee1bfb4192830f03f385e773c0f4767e93e874ffdc3b7a6b7e6a710e5619901c739ee8760a26128e8c91ef8cf761d0e505d8b28ae078d17e6071c372893bb7b72538e518ebc57efa70b7615e406756c49729b7c6e74f84aed7a316b6fa748ff4b9f143129d29dad1bff98bb +A = a29dacaf5487d354280fdd2745b9ace4cd50f2bde41d0ee529bf26a1913244f708085452ff32feab19a7418897990da46a0633f7c8375d583367319091bbbe069b0052c5e48a7daac9fb650db5af768cd2508ec3e2cda7456d4b9ce1c39459627a8b77e038b826cd7e326d0685b0cd0cb50f026f18300dae9f5fd42aa150ee8b +M = d686f9b86697313251685e995c09b9f1e337ddfaa050bd2df15bf4ca1dc46c5565021314765299c434ea1a6ec42bf92a29a7d1ffff599f4e50b79a82243fb24813060580c770d4c1140aeb2ab2685007e948b6f1f62e8001a0545619477d498132c907774479f6d95899e6251e7136f79ab6d3b7c82e4aca421e7d22fe7db19c + +ModInv = 1ec872f4f20439e203597ca4de9d1296743f95781b2fe85d5def808558bbadef02a46b8955f47c83e1625f8bb40228eab09cad2a35c9ad62ab77a30e3932872959c5898674162da244a0ec1f68c0ed89f4b0f3572bfdc658ad15bf1b1c6e1176b0784c9935bd3ff1f49bb43753eacee1d8ca1c0b652d39ec727da83984fe3a0f +A = 2e527b0a1dc32460b2dd94ec446c692989f7b3c7451a5cbeebf69fc0ea9c4871fbe78682d5dc5b66689f7ed889b52161cd9830b589a93d21ab26dbede6c33959f5a0f0d107169e2daaac78bac8cf2d41a1eb1369cb6dc9e865e73bb2e51b886f4e896082db199175e3dde0c4ed826468f238a77bd894245d0918efc9ca84f945 +M = b13133a9ebe0645f987d170c077eea2aa44e85c9ab10386d02867419a590cb182d9826a882306c212dbe75225adde23f80f5b37ca75ed09df20fc277cc7fbbfac8d9ef37a50f6b68ea158f5447283618e64e1426406d26ea85232afb22bf546c75018c1c55cb84c374d58d9d44c0a13ba88ac2e387765cb4c3269e3a983250fa + +ModInv = 30ffa1876313a69de1e4e6ee132ea1d3a3da32f3b56f5cfb11402b0ad517dce605cf8e91d69fa375dd887fa8507bd8a28b2d5ce745799126e86f416047709f93f07fbd88918a047f13100ea71b1d48f6fc6d12e5c917646df3041b302187af641eaedf4908abc36f12c204e1526a7d80e96e302fb0779c28d7da607243732f26 +A = 31157208bde6b85ebecaa63735947b3b36fa351b5c47e9e1c40c947339b78bf96066e5dbe21bb42629e6fcdb81f5f88db590bfdd5f4c0a6a0c3fc6377e5c1fd8235e46e291c688b6d6ecfb36604891c2a7c9cbcc58c26e44b43beecb9c5044b58bb58e35de3cf1128f3c116534fe4e421a33f83603c3df1ae36ec88092f67f2a +M = 53408b23d6cb733e6c9bc3d1e2ea2286a5c83cc4e3e7470f8af3a1d9f28727f5b1f8ae348c1678f5d1105dc3edf2de64e65b9c99545c47e64b770b17c8b4ef5cf194b43a0538053e87a6b95ade1439cebf3d34c6aa72a11c1497f58f76011e16c5be087936d88aba7a740113120e939e27bd3ddcb6580c2841aa406566e33c35 + +ModInv = 87355002f305c81ba0dc97ca2234a2bc02528cefde38b94ac5bd95efc7bf4c140899107fff47f0df9e3c6aa70017ebc90610a750f112cd4f475b9c76b204a953444b4e7196ccf17e93fdaed160b7345ca9b397eddf9446e8ea8ee3676102ce70eaafbe9038a34639789e6f2f1e3f352638f2e8a8f5fc56aaea7ec705ee068dd5 +A = 42a25d0bc96f71750f5ac8a51a1605a41b506cca51c9a7ecf80cad713e56f70f1b4b6fa51cbb101f55fd74f318adefb3af04e0c8a7e281055d5a40dd40913c0e1211767c5be915972c73886106dc49325df6c2df49e9eea4536f0343a8e7d332c6159e4f5bdb20d89f90e67597c4a2a632c31b2ef2534080a9ac61f52303990d +M = d3d3f95d50570351528a76ab1e806bae1968bd420899bdb3d87c823fac439a4354c31f6c888c939784f18fe10a95e6d203b1901caa18937ba6f8be033af10c35fc869cf3d16bef479f280f53b3499e645d0387554623207ca4989e5de00bfeaa5e9ab56474fc60dd4967b100e0832eaaf2fcb2ef82a181567057b880b3afef62 diff --git a/crypto/internal/fips140/boring.go b/crypto/internal/fips140/boring.go new file mode 100644 index 00000000000..d627bc68903 --- /dev/null +++ b/crypto/internal/fips140/boring.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Keep in sync with notboring.go and crypto/internal/boring/boring.go. +//go:build boringcrypto && linux && (amd64 || arm64) && !android && !msan && cgo + +package fips140 + +const boringEnabled = true diff --git a/crypto/internal/fips140/cast.go b/crypto/internal/fips140/cast.go new file mode 100644 index 00000000000..fa656e7baa1 --- /dev/null +++ b/crypto/internal/fips140/cast.go @@ -0,0 +1,83 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140 + +import ( + "errors" + "strings" + _ "unsafe" // for go:linkname + + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" +) + +// fatal is [runtime.fatal], pushed via linkname. +// +//go:linkname fatal crypto/internal/fips140.fatal +func fatal(string) + +// failfipscast is a GODEBUG key allowing simulation of a CAST or PCT failure, +// as required during FIPS 140-3 functional testing. The value is the whole name +// of the target CAST or PCT. +var failfipscast = godebug.Value("#failfipscast") + +// CAST runs the named Cryptographic Algorithm Self-Test (if operated in FIPS +// mode) and aborts the program (stopping the module input/output and entering +// the "error state") if the self-test fails. +// +// CASTs are mandatory self-checks that must be performed by FIPS 140-3 modules +// before the algorithm is used. See Implementation Guidance 10.3.A. +// +// The name must not contain commas, colons, hashes, or equal signs. +// +// If a package p calls CAST from its init function, an import of p should also +// be added to crypto/internal/fips140test. If a package p calls CAST on the first +// use of the algorithm, an invocation of that algorithm should be added to +// fipstest.TestConditionals. +func CAST(name string, f func() error) { + if strings.ContainsAny(name, ",#=:") { + panic("fips: invalid self-test name: " + name) + } + if !Enabled { + return + } + + err := f() + if name == failfipscast { + err = errors.New("simulated CAST failure") + } + if err != nil { + fatal("FIPS 140-3 self-test failed: " + name + ": " + err.Error()) + panic("unreachable") + } + if debug { + println("FIPS 140-3 self-test passed:", name) + } +} + +// PCT runs the named Pairwise Consistency Test (if operated in FIPS mode) and +// returns any errors. If an error is returned, the key must not be used. +// +// PCTs are mandatory for every key pair that is generated/imported, including +// ephemeral keys (which effectively doubles the cost of key establishment). See +// Implementation Guidance 10.3.A Additional Comment 1. +// +// The name must not contain commas, colons, hashes, or equal signs. +// +// If a package p calls PCT during key generation, an invocation of that +// function should be added to fipstest.TestConditionals. +func PCT(name string, f func() error) error { + if strings.ContainsAny(name, ",#=:") { + panic("fips: invalid self-test name: " + name) + } + if !Enabled { + return nil + } + + err := f() + if name == failfipscast { + err = errors.New("simulated PCT failure") + } + return err +} diff --git a/crypto/internal/fips140/check/check.go b/crypto/internal/fips140/check/check.go new file mode 100644 index 00000000000..29c76118200 --- /dev/null +++ b/crypto/internal/fips140/check/check.go @@ -0,0 +1,109 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package check implements the FIPS 140 load-time code+data verification. +// Every FIPS package providing cryptographic functionality except hmac and sha256 +// must import crypto/internal/fips140/check, so that the verification happens +// before initialization of package global variables. +// The hmac and sha256 packages are used by this package, so they cannot import it. +// Instead, those packages must be careful not to change global variables during init. +// (If necessary, we could have check call a PostCheck function in those packages +// after the check has completed.) +package check + +import ( + "io" + "unsafe" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" +) + +// Verified is set when verification succeeded. It can be expected to always be +// true when [fips140.Enabled] is true, or init would have panicked. +var Verified bool + +// Linkinfo holds the go:fipsinfo symbol prepared by the linker. +// See cmd/link/internal/ld/fips.go for details. +// +//go:linkname Linkinfo go:fipsinfo +var Linkinfo struct { + Magic [16]byte + Sum [32]byte + Self uintptr + Sects [4]struct { + // Note: These must be unsafe.Pointer, not uintptr, + // or else checkptr panics about turning uintptrs + // into pointers into the data segment during + // go test -race. + Start unsafe.Pointer + End unsafe.Pointer + } +} + +// "\xff"+fipsMagic is the expected linkinfo.Magic. +// We avoid writing that explicitly so that the string does not appear +// elsewhere in normal binaries, just as a precaution. +const fipsMagic = " Go fipsinfo \xff\x00" + +var zeroSum [32]byte + +func init() { + if !fips140.Enabled { + return + } + + if err := fips140.Supported(); err != nil { + panic("fips140: " + err.Error()) + } + + if Linkinfo.Magic[0] != 0xff || string(Linkinfo.Magic[1:]) != fipsMagic || Linkinfo.Sum == zeroSum { + panic("fips140: no verification checksum found") + } + + h := hmac.New(sha256.New, make([]byte, 32)) + w := io.Writer(h) + + /* + // Uncomment for debugging. + // Commented (as opposed to a const bool flag) + // to avoid import "os" in default builds. + f, err := os.Create("fipscheck.o") + if err != nil { + panic(err) + } + w = io.MultiWriter(h, f) + */ + + w.Write([]byte("go fips object v1\n")) + + var nbuf [8]byte + for _, sect := range Linkinfo.Sects { + n := uintptr(sect.End) - uintptr(sect.Start) + byteorder.BEPutUint64(nbuf[:], uint64(n)) + w.Write(nbuf[:]) + w.Write(unsafe.Slice((*byte)(sect.Start), n)) + } + sum := h.Sum(nil) + + if [32]byte(sum) != Linkinfo.Sum { + panic("fips140: verification mismatch") + } + + // "The temporary value(s) generated during the integrity test of the + // module’s software or firmware shall [05.10] be zeroised from the module + // upon completion of the integrity test" + clear(sum) + clear(nbuf[:]) + h.Reset() + + if godebug.Value("fips140") == "debug" { + println("fips140: verified code+data") + } + + Verified = true +} diff --git a/crypto/internal/fips140/check/checktest/asm.s b/crypto/internal/fips140/check/checktest/asm.s new file mode 100644 index 00000000000..cc74e56f981 --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm.s @@ -0,0 +1,10 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego && !wasm + +#include "textflag.h" + +DATA crypto∕internal∕fips140∕check∕checktest·RODATA(SB)/4, $2 +GLOBL crypto∕internal∕fips140∕check∕checktest·RODATA(SB), RODATA, $4 diff --git a/crypto/internal/fips140/check/checktest/asm_386.s b/crypto/internal/fips140/check/checktest/asm_386.s new file mode 100644 index 00000000000..c2978b51624 --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm_386.s @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +DATA StaticData<>(SB)/4, $10 +GLOBL StaticData<>(SB), NOPTR, $4 + +TEXT StaticText<>(SB), $0 + RET + +TEXT ·PtrStaticData(SB), $0-4 + MOVL $StaticData<>(SB), AX + MOVL AX, ret+0(FP) + RET + +TEXT ·PtrStaticText(SB), $0-4 + MOVL $StaticText<>(SB), AX + MOVL AX, ret+0(FP) + RET diff --git a/crypto/internal/fips140/check/checktest/asm_amd64.s b/crypto/internal/fips140/check/checktest/asm_amd64.s new file mode 100644 index 00000000000..88e4d94074c --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm_amd64.s @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +DATA StaticData<>(SB)/4, $10 +GLOBL StaticData<>(SB), NOPTR, $4 + +TEXT StaticText<>(SB), $0 + RET + +TEXT ·PtrStaticData(SB), $0-8 + MOVQ $StaticData<>(SB), AX + MOVQ AX, ret+0(FP) + RET + +TEXT ·PtrStaticText(SB), $0-8 + MOVQ $StaticText<>(SB), AX + MOVQ AX, ret+0(FP) + RET diff --git a/crypto/internal/fips140/check/checktest/asm_arm.s b/crypto/internal/fips140/check/checktest/asm_arm.s new file mode 100644 index 00000000000..5cc9230100f --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm_arm.s @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +DATA StaticData<>(SB)/4, $10 +GLOBL StaticData<>(SB), NOPTR, $4 + +TEXT StaticText<>(SB), $0 + RET + +TEXT ·PtrStaticData(SB), $0-4 + MOVW $StaticData<>(SB), R1 + MOVW R1, ret+0(FP) + RET + +TEXT ·PtrStaticText(SB), $0-4 + MOVW $StaticText<>(SB), R1 + MOVW R1, ret+0(FP) + RET diff --git a/crypto/internal/fips140/check/checktest/asm_arm64.s b/crypto/internal/fips140/check/checktest/asm_arm64.s new file mode 100644 index 00000000000..721bb03ada5 --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm_arm64.s @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +DATA StaticData<>(SB)/4, $10 +GLOBL StaticData<>(SB), NOPTR, $4 + +TEXT StaticText<>(SB), $0 + RET + +TEXT ·PtrStaticData(SB), $0-8 + MOVD $StaticData<>(SB), R1 + MOVD R1, ret+0(FP) + RET + +TEXT ·PtrStaticText(SB), $0-8 + MOVD $StaticText<>(SB), R1 + MOVD R1, ret+0(FP) + RET diff --git a/crypto/internal/fips140/check/checktest/asm_none.go b/crypto/internal/fips140/check/checktest/asm_none.go new file mode 100644 index 00000000000..956bad1cdad --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm_none.go @@ -0,0 +1,12 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!386 && !amd64 && !arm && !arm64) || purego + +package checktest + +import "unsafe" + +func PtrStaticData() *uint32 { return nil } +func PtrStaticText() unsafe.Pointer { return nil } diff --git a/crypto/internal/fips140/check/checktest/asm_stub.go b/crypto/internal/fips140/check/checktest/asm_stub.go new file mode 100644 index 00000000000..ebb5b17b28f --- /dev/null +++ b/crypto/internal/fips140/check/checktest/asm_stub.go @@ -0,0 +1,12 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (386 || amd64 || arm || arm64) && !purego + +package checktest + +import "unsafe" + +func PtrStaticData() *uint32 +func PtrStaticText() unsafe.Pointer diff --git a/crypto/internal/fips140/check/checktest/test.go b/crypto/internal/fips140/check/checktest/test.go new file mode 100644 index 00000000000..5de69596808 --- /dev/null +++ b/crypto/internal/fips140/check/checktest/test.go @@ -0,0 +1,63 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package checktest defines some code and data for use in +// the crypto/internal/fips140/check test. +package checktest + +import ( + "runtime" + _ "unsafe" // go:linkname + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" +) + +var NOPTRDATA int = 1 + +// The linkname here disables asan registration of this global, +// because asan gets mad about rodata globals. +// +//go:linkname RODATA crypto/internal/fips140/check/checktest.RODATA +var RODATA int32 // set to 2 in asm.s + +// DATA needs to have both a pointer and an int so that _some_ of it gets +// initialized at link time, so it is treated as DATA and not BSS. +// The pointer is deferred to init time. +var DATA = struct { + P *int + X int +}{&NOPTRDATA, 3} + +var NOPTRBSS int + +var BSS *int + +func TEXT() {} + +var ( + globl12 [12]byte + globl8 [8]byte +) + +func init() { + globl8 = [8]byte{1, 2, 3, 4, 5, 6, 7, 8} + globl12 = [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + runtime.Gosched() + + sum := byte(0) + for _, x := range globl12 { + sum += x + } + if sum != 78 { + panic("globl12 did not sum properly") + } + + sum = byte(0) + for _, x := range globl8 { + sum += x + } + if sum != 36 { + panic("globl8 did not sum properly") + } +} diff --git a/crypto/internal/fips140/drbg/cast.go b/crypto/internal/fips140/drbg/cast.go new file mode 100644 index 00000000000..977979a48d3 --- /dev/null +++ b/crypto/internal/fips140/drbg/cast.go @@ -0,0 +1,60 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package drbg + +import ( + "bytes" + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func init() { + // Per IG 10.3.A, Resolution 7: "A KAT of a DRBG may be performed by: + // Instantiate with known data, Reseed with other known data, Generate and + // then compare the result to a pre-computed value." + fips140.CAST("CTR_DRBG", func() error { + entropy := &[SeedSize]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + } + reseedEntropy := &[SeedSize]byte{ + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, + } + additionalInput := &[SeedSize]byte{ + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, + 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, + 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, + 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + } + want := []byte{ + 0x6e, 0x6e, 0x47, 0x9d, 0x24, 0xf8, 0x6a, 0x3b, + 0x77, 0x87, 0xa8, 0xf8, 0x18, 0x6d, 0x98, 0x5a, + 0x53, 0xbe, 0xbe, 0xed, 0xde, 0xab, 0x92, 0x28, + 0xf0, 0xf4, 0xac, 0x6e, 0x10, 0xbf, 0x01, 0x93, + } + c := NewCounter(entropy) + c.Reseed(reseedEntropy, additionalInput) + got := make([]byte, len(want)) + c.Generate(got, additionalInput) + if !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/drbg/ctrdrbg.go b/crypto/internal/fips140/drbg/ctrdrbg.go new file mode 100644 index 00000000000..917660e650f --- /dev/null +++ b/crypto/internal/fips140/drbg/ctrdrbg.go @@ -0,0 +1,136 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package drbg + +import ( + "math/bits" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// Counter is an SP 800-90A Rev. 1 CTR_DRBG instantiated with AES-256. +// +// Per Table 3, it has a security strength of 256 bits, a seed size of 384 bits, +// a counter length of 128 bits, a reseed interval of 2^48 requests, and a +// maximum request size of 2^19 bits (2^16 bytes, 64 KiB). +// +// We support a narrow range of parameters that fit the needs of our RNG: +// AES-256, no derivation function, no personalization string, no prediction +// resistance, and 384-bit additional input. +type Counter struct { + // c is instantiated with K as the key and V as the counter. + c aes.CTR + + reseedCounter uint64 +} + +const ( + keySize = 256 / 8 + SeedSize = keySize + aes.BlockSize + reseedInterval = 1 << 48 + maxRequestSize = (1 << 19) / 8 +) + +func NewCounter(entropy *[SeedSize]byte) *Counter { + // CTR_DRBG_Instantiate_algorithm, per Section 10.2.1.3.1. + fips140.RecordApproved() + + K := make([]byte, keySize) + V := make([]byte, aes.BlockSize) + + // V starts at 0, but is incremented in CTR_DRBG_Update before each use, + // unlike AES-CTR where it is incremented after each use. + V[len(V)-1] = 1 + + cipher, err := aes.New(K) + if err != nil { + panic(err) + } + + c := &Counter{} + c.c = *aes.NewCTR(cipher, V) + c.update(entropy) + c.reseedCounter = 1 + return c +} + +func (c *Counter) update(seed *[SeedSize]byte) { + // CTR_DRBG_Update, per Section 10.2.1.2. + + temp := make([]byte, SeedSize) + c.c.XORKeyStream(temp, seed[:]) + K := temp[:keySize] + V := temp[keySize:] + + // Again, we pre-increment V, like in NewCounter. + increment((*[aes.BlockSize]byte)(V)) + + cipher, err := aes.New(K) + if err != nil { + panic(err) + } + c.c = *aes.NewCTR(cipher, V) +} + +func increment(v *[aes.BlockSize]byte) { + hi := byteorder.BEUint64(v[:8]) + lo := byteorder.BEUint64(v[8:]) + lo, c := bits.Add64(lo, 1, 0) + hi, _ = bits.Add64(hi, 0, c) + byteorder.BEPutUint64(v[:8], hi) + byteorder.BEPutUint64(v[8:], lo) +} + +func (c *Counter) Reseed(entropy, additionalInput *[SeedSize]byte) { + // CTR_DRBG_Reseed_algorithm, per Section 10.2.1.4.1. + fips140.RecordApproved() + + var seed [SeedSize]byte + subtle.XORBytes(seed[:], entropy[:], additionalInput[:]) + c.update(&seed) + c.reseedCounter = 1 +} + +// Generate produces at most maxRequestSize bytes of random data in out. +func (c *Counter) Generate(out []byte, additionalInput *[SeedSize]byte) (reseedRequired bool) { + // CTR_DRBG_Generate_algorithm, per Section 10.2.1.5.1. + fips140.RecordApproved() + + if len(out) > maxRequestSize { + panic("crypto/drbg: internal error: request size exceeds maximum") + } + + // Step 1. + if c.reseedCounter > reseedInterval { + return true + } + + // Step 2. + if additionalInput != nil { + c.update(additionalInput) + } else { + // If the additional input is null, the first CTR_DRBG_Update is + // skipped, but the additional input is replaced with an all-zero string + // for the second CTR_DRBG_Update. + additionalInput = new([SeedSize]byte) + } + + // Steps 3-5. + clear(out) + c.c.XORKeyStream(out, out) + aes.RoundToBlock(&c.c) + + // Step 6. + c.update(additionalInput) + + // Step 7. + c.reseedCounter++ + + // Step 8. + return false +} diff --git a/crypto/internal/fips140/drbg/rand.go b/crypto/internal/fips140/drbg/rand.go new file mode 100644 index 00000000000..83702e97abc --- /dev/null +++ b/crypto/internal/fips140/drbg/rand.go @@ -0,0 +1,101 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package drbg provides cryptographically secure random bytes +// usable by FIPS code. In FIPS mode it uses an SP 800-90A Rev. 1 +// Deterministic Random Bit Generator (DRBG). Otherwise, +// it uses the operating system's random number generator. +package drbg + +import ( + "io" + "sync" + + "github.com/runZeroInc/excrypto/crypto/internal/entropy" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/randutil" + "github.com/runZeroInc/excrypto/crypto/internal/sysrand" +) + +var drbgs = sync.Pool{ + New: func() any { + var c *Counter + entropy.Depleted(func(seed *[48]byte) { + c = NewCounter(seed) + }) + return c + }, +} + +// Read fills b with cryptographically secure random bytes. In FIPS mode, it +// uses an SP 800-90A Rev. 1 Deterministic Random Bit Generator (DRBG). +// Otherwise, it uses the operating system's random number generator. +func Read(b []byte) { + if !fips140.Enabled { + sysrand.Read(b) + return + } + + // At every read, 128 random bits from the operating system are mixed as + // additional input, to make the output as strong as non-FIPS randomness. + // This is not credited as entropy for FIPS purposes, as allowed by Section + // 8.7.2: "Note that a DRBG does not rely on additional input to provide + // entropy, even though entropy could be provided in the additional input". + additionalInput := new([SeedSize]byte) + sysrand.Read(additionalInput[:16]) + + drbg := drbgs.Get().(*Counter) + defer drbgs.Put(drbg) + + for len(b) > 0 { + size := min(len(b), maxRequestSize) + if reseedRequired := drbg.Generate(b[:size], additionalInput); reseedRequired { + // See SP 800-90A Rev. 1, Section 9.3.1, Steps 6-8, as explained in + // Section 9.3.2: if Generate reports a reseed is required, the + // additional input is passed to Reseed along with the entropy and + // then nulled before the next Generate call. + entropy.Depleted(func(seed *[48]byte) { + drbg.Reseed(seed, additionalInput) + }) + additionalInput = nil + continue + } + b = b[size:] + } +} + +// DefaultReader is a sentinel type, embedded in the default +// [crypto/rand.Reader], used to recognize it when passed to +// APIs that accept a rand io.Reader. +type DefaultReader interface{ defaultReader() } + +// ReadWithReader uses Reader to fill b with cryptographically secure random +// bytes. It is intended for use in APIs that expose a rand io.Reader. +// +// If Reader is not the default Reader from crypto/rand, +// [randutil.MaybeReadByte] and [fips140.RecordNonApproved] are called. +func ReadWithReader(r io.Reader, b []byte) error { + if _, ok := r.(DefaultReader); ok { + Read(b) + return nil + } + + fips140.RecordNonApproved() + randutil.MaybeReadByte(r) + _, err := io.ReadFull(r, b) + return err +} + +// ReadWithReaderDeterministic is like ReadWithReader, but it doesn't call +// [randutil.MaybeReadByte] on non-default Readers. +func ReadWithReaderDeterministic(r io.Reader, b []byte) error { + if _, ok := r.(DefaultReader); ok { + Read(b) + return nil + } + + fips140.RecordNonApproved() + _, err := io.ReadFull(r, b) + return err +} diff --git a/crypto/internal/fips140/drbg/rand_test.go b/crypto/internal/fips140/drbg/rand_test.go new file mode 100644 index 00000000000..bc10691df38 --- /dev/null +++ b/crypto/internal/fips140/drbg/rand_test.go @@ -0,0 +1,28 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package drbg + +import ( + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func BenchmarkDBRG(b *testing.B) { + old := fips140.Enabled + defer func() { + fips140.Enabled = old + }() + fips140.Enabled = true + + const N = 64 + b.SetBytes(N) + b.RunParallel(func(pb *testing.PB) { + buf := make([]byte, N) + for pb.Next() { + Read(buf) + } + }) +} diff --git a/crypto/internal/fips140/ecdh/cast.go b/crypto/internal/fips140/ecdh/cast.go new file mode 100644 index 00000000000..cf728c3489e --- /dev/null +++ b/crypto/internal/fips140/ecdh/cast.go @@ -0,0 +1,54 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdh + +import ( + "bytes" + "errors" + "sync" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +var fipsSelfTest = sync.OnceFunc(func() { + // Per IG D.F, Scenario 2, path (1). + fips140.CAST("KAS-ECC-SSC P-256", func() error { + privateKey := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + publicKey := []byte{ + 0x04, + 0x51, 0x5c, 0x3d, 0x6e, 0xb9, 0xe3, 0x96, 0xb9, + 0x04, 0xd3, 0xfe, 0xca, 0x7f, 0x54, 0xfd, 0xcd, + 0x0c, 0xc1, 0xe9, 0x97, 0xbf, 0x37, 0x5d, 0xca, + 0x51, 0x5a, 0xd0, 0xa6, 0xc3, 0xb4, 0x03, 0x5f, + 0x45, 0x36, 0xbe, 0x3a, 0x50, 0xf3, 0x18, 0xfb, + 0xf9, 0xa5, 0x47, 0x59, 0x02, 0xa2, 0x21, 0x50, + 0x2b, 0xef, 0x0d, 0x57, 0xe0, 0x8c, 0x53, 0xb2, + 0xcc, 0x0a, 0x56, 0xf1, 0x7d, 0x9f, 0x93, 0x54, + } + want := []byte{ + 0xb4, 0xf1, 0xfc, 0xce, 0x40, 0x73, 0x5f, 0x83, + 0x6a, 0xf8, 0xd6, 0x31, 0x2d, 0x24, 0x8d, 0x1a, + 0x83, 0x48, 0x40, 0x56, 0x69, 0xa1, 0x95, 0xfa, + 0xc5, 0x35, 0x04, 0x06, 0xba, 0x76, 0xbc, 0xce, + } + k := &PrivateKey{d: privateKey, pub: PublicKey{curve: p256}} + peer := &PublicKey{curve: p256, q: publicKey} + got, err := ecdh(P256(), k, peer) + if err != nil { + return err + } + if !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +}) diff --git a/crypto/internal/fips140/ecdh/ecdh.go b/crypto/internal/fips140/ecdh/ecdh.go new file mode 100644 index 00000000000..94a684f47e5 --- /dev/null +++ b/crypto/internal/fips140/ecdh/ecdh.go @@ -0,0 +1,310 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdh + +import ( + "bytes" + "errors" + "io" + "math/bits" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// PrivateKey and PublicKey are not generic to make it possible to use them +// in other types without instantiating them with a specific point type. +// They are tied to one of the Curve types below through the curveID field. + +// All this is duplicated from crypto/internal/fips/ecdsa, but the standards are +// different and FIPS 140 does not allow reusing keys across them. + +type PrivateKey struct { + pub PublicKey + d []byte // bigmod.(*Nat).Bytes output (fixed length) +} + +func (priv *PrivateKey) Bytes() []byte { + return priv.d +} + +func (priv *PrivateKey) PublicKey() *PublicKey { + return &priv.pub +} + +type PublicKey struct { + curve curveID + q []byte // uncompressed nistec Point.Bytes output +} + +func (pub *PublicKey) Bytes() []byte { + return pub.q +} + +type curveID string + +const ( + p224 curveID = "P-224" + p256 curveID = "P-256" + p384 curveID = "P-384" + p521 curveID = "P-521" +) + +type Curve[P Point[P]] struct { + curve curveID + newPoint func() P + N []byte +} + +// Point is a generic constraint for the [nistec] Point types. +type Point[P any] interface { + *nistec.P224Point | *nistec.P256Point | *nistec.P384Point | *nistec.P521Point + Bytes() []byte + BytesX() ([]byte, error) + SetBytes([]byte) (P, error) + ScalarMult(P, []byte) (P, error) + ScalarBaseMult([]byte) (P, error) +} + +func P224() *Curve[*nistec.P224Point] { + return &Curve[*nistec.P224Point]{ + curve: p224, + newPoint: nistec.NewP224Point, + N: p224Order, + } +} + +var p224Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x3d, +} + +func P256() *Curve[*nistec.P256Point] { + return &Curve[*nistec.P256Point]{ + curve: p256, + newPoint: nistec.NewP256Point, + N: p256Order, + } +} + +var p256Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, + 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51, +} + +func P384() *Curve[*nistec.P384Point] { + return &Curve[*nistec.P384Point]{ + curve: p384, + newPoint: nistec.NewP384Point, + N: p384Order, + } +} + +var p384Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf, + 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a, + 0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73, +} + +func P521() *Curve[*nistec.P521Point] { + return &Curve[*nistec.P521Point]{ + curve: p521, + newPoint: nistec.NewP521Point, + N: p521Order, + } +} + +var p521Order = []byte{0x01, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, + 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, 0x96, 0x6b, + 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, 0xa5, 0xd0, + 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae, + 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09, +} + +// GenerateKey generates a new ECDSA private key pair for the specified curve. +func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) { + fips140.RecordApproved() + // This procedure is equivalent to Key Pair Generation by Testing + // Candidates, specified in NIST SP 800-56A Rev. 3, Section 5.6.1.2.2. + + for { + key := make([]byte, len(c.N)) + if err := drbg.ReadWithReader(rand, key); err != nil { + return nil, err + } + // In tests, rand will return all zeros and NewPrivateKey will reject + // the zero key as it generates the identity as a public key. This also + // makes this function consistent with crypto/elliptic.GenerateKey. + key[1] ^= 0x42 + + // Mask off any excess bits if the size of the underlying field is not a + // whole number of bytes, which is only the case for P-521. + if c.curve == p521 && c.N[0]&0b1111_1110 == 0 { + key[0] &= 0b0000_0001 + } + + privateKey, err := NewPrivateKey(c, key) + if err != nil { + continue + } + return privateKey, nil + } +} + +func NewPrivateKey[P Point[P]](c *Curve[P], key []byte) (*PrivateKey, error) { + // SP 800-56A Rev. 3, Section 5.6.1.2.2 checks that c <= n – 2 and then + // returns d = c + 1. Note that it follows that 0 < d < n. Equivalently, + // we check that 0 < d < n, and return d. + if len(key) != len(c.N) || isZero(key) || !isLess(key, c.N) { + return nil, errors.New("crypto/ecdh: invalid private key") + } + + p, err := c.newPoint().ScalarBaseMult(key) + if err != nil { + // This is unreachable because the only error condition of + // ScalarBaseMult is if the input is not the right size. + panic("crypto/ecdh: internal error: nistec ScalarBaseMult failed for a fixed-size input") + } + + publicKey := p.Bytes() + if len(publicKey) == 1 { + // The encoding of the identity is a single 0x00 byte. This is + // unreachable because the only scalar that generates the identity is + // zero, which is rejected above. + panic("crypto/ecdh: internal error: public key is the identity element") + } + + // A "Pairwise Consistency Test" makes no sense if we just generated the + // public key from an ephemeral private key. Moreover, there is no way to + // check it aside from redoing the exact same computation again. SP 800-56A + // Rev. 3, Section 5.6.2.1.4 acknowledges that, and doesn't require it. + // However, ISO 19790:2012, Section 7.10.3.3 has a blanket requirement for a + // PCT for all generated keys (AS10.35) and FIPS 140-3 IG 10.3.A, Additional + // Comment 1 goes out of its way to say that "the PCT shall be performed + // consistent [...], even if the underlying standard does not require a + // PCT". So we do it. And make ECDH nearly 50% slower (only) in FIPS mode. + if err := fips140.PCT("ECDH PCT", func() error { + p1, err := c.newPoint().ScalarBaseMult(key) + if err != nil { + return err + } + if !bytes.Equal(p1.Bytes(), publicKey) { + return errors.New("crypto/ecdh: public key does not match private key") + } + return nil + }); err != nil { + panic(err) + } + + k := &PrivateKey{d: bytes.Clone(key), pub: PublicKey{curve: c.curve, q: publicKey}} + return k, nil +} + +func NewPublicKey[P Point[P]](c *Curve[P], key []byte) (*PublicKey, error) { + // Reject the point at infinity and compressed encodings. + if len(key) == 0 || key[0] != 4 { + return nil, errors.New("crypto/ecdh: invalid public key") + } + + // SetBytes checks that x and y are in the interval [0, p - 1], and that + // the point is on the curve. Along with the rejection of the point at + // infinity (the identity element) above, this fulfills the requirements + // of NIST SP 800-56A Rev. 3, Section 5.6.2.3.4. + if _, err := c.newPoint().SetBytes(key); err != nil { + return nil, err + } + + return &PublicKey{curve: c.curve, q: bytes.Clone(key)}, nil +} + +func ECDH[P Point[P]](c *Curve[P], k *PrivateKey, peer *PublicKey) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + return ecdh(c, k, peer) +} + +func ecdh[P Point[P]](c *Curve[P], k *PrivateKey, peer *PublicKey) ([]byte, error) { + if c.curve != k.pub.curve { + return nil, errors.New("crypto/ecdh: mismatched curves") + } + if k.pub.curve != peer.curve { + return nil, errors.New("crypto/ecdh: mismatched curves") + } + + // This applies the Shared Secret Computation of the Ephemeral Unified Model + // scheme specified in NIST SP 800-56A Rev. 3, Section 6.1.2.2. + + // Per Section 5.6.2.3.4, Step 1, reject the identity element (0x00). + if len(k.pub.q) == 1 { + return nil, errors.New("crypto/ecdh: public key is the identity element") + } + + // SetBytes checks that (x, y) are reduced modulo p, and that they are on + // the curve, performing Steps 2-3 of Section 5.6.2.3.4. + p, err := c.newPoint().SetBytes(peer.q) + if err != nil { + return nil, err + } + + // Compute P according to Section 5.7.1.2. + if _, err := p.ScalarMult(p, k.d); err != nil { + return nil, err + } + + // BytesX checks that the result is not the identity element, and returns the + // x-coordinate of the result, performing Steps 2-5 of Section 5.7.1.2. + return p.BytesX() +} + +// isZero reports whether x is all zeroes in constant time. +func isZero(x []byte) bool { + var acc byte + for _, b := range x { + acc |= b + } + return acc == 0 +} + +// isLess reports whether a < b, where a and b are big-endian buffers of the +// same length and shorter than 72 bytes. +func isLess(a, b []byte) bool { + if len(a) != len(b) { + panic("crypto/ecdh: internal error: mismatched isLess inputs") + } + + // Copy the values into a fixed-size preallocated little-endian buffer. + // 72 bytes is enough for every scalar in this package, and having a fixed + // size lets us avoid heap allocations. + if len(a) > 72 { + panic("crypto/ecdh: internal error: isLess input too large") + } + bufA, bufB := make([]byte, 72), make([]byte, 72) + for i := range a { + bufA[i], bufB[i] = a[len(a)-i-1], b[len(b)-i-1] + } + + // Perform a subtraction with borrow. + var borrow uint64 + for i := 0; i < len(bufA); i += 8 { + limbA, limbB := byteorder.LEUint64(bufA[i:]), byteorder.LEUint64(bufB[i:]) + _, borrow = bits.Sub64(limbA, limbB, borrow) + } + + // If there is a borrow at the end of the operation, then a < b. + return borrow == 1 +} diff --git a/crypto/internal/fips140/ecdh/order_test.go b/crypto/internal/fips140/ecdh/order_test.go new file mode 100644 index 00000000000..c8aee9d7114 --- /dev/null +++ b/crypto/internal/fips140/ecdh/order_test.go @@ -0,0 +1,27 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdh + +import ( + "bytes" + "testing" + + "github.com/runZeroInc/excrypto/crypto/elliptic" +) + +func TestOrders(t *testing.T) { + if !bytes.Equal(elliptic.P224().Params().N.Bytes(), P224().N) { + t.Errorf("P-224 order mismatch") + } + if !bytes.Equal(elliptic.P256().Params().N.Bytes(), P256().N) { + t.Errorf("P-256 order mismatch") + } + if !bytes.Equal(elliptic.P384().Params().N.Bytes(), P384().N) { + t.Errorf("P-384 order mismatch") + } + if !bytes.Equal(elliptic.P521().Params().N.Bytes(), P521().N) { + t.Errorf("P-521 order mismatch") + } +} diff --git a/crypto/internal/fips140/ecdsa/cast.go b/crypto/internal/fips140/ecdsa/cast.go new file mode 100644 index 00000000000..a2319ff5fd5 --- /dev/null +++ b/crypto/internal/fips140/ecdsa/cast.go @@ -0,0 +1,138 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdsa + +import ( + "bytes" + "errors" + "sync" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" +) + +func testPrivateKey() *PrivateKey { + // https://www.rfc-editor.org/rfc/rfc9500.html#section-2.3 + return &PrivateKey{ + pub: PublicKey{ + curve: p256, + q: []byte{ + 0x04, + 0x42, 0x25, 0x48, 0xF8, 0x8F, 0xB7, 0x82, 0xFF, + 0xB5, 0xEC, 0xA3, 0x74, 0x44, 0x52, 0xC7, 0x2A, + 0x1E, 0x55, 0x8F, 0xBD, 0x6F, 0x73, 0xBE, 0x5E, + 0x48, 0xE9, 0x32, 0x32, 0xCC, 0x45, 0xC5, 0xB1, + 0x6C, 0x4C, 0xD1, 0x0C, 0x4C, 0xB8, 0xD5, 0xB8, + 0xA1, 0x71, 0x39, 0xE9, 0x48, 0x82, 0xC8, 0x99, + 0x25, 0x72, 0x99, 0x34, 0x25, 0xF4, 0x14, 0x19, + 0xAB, 0x7E, 0x90, 0xA4, 0x2A, 0x49, 0x42, 0x72}, + }, + d: []byte{ + 0xE6, 0xCB, 0x5B, 0xDD, 0x80, 0xAA, 0x45, 0xAE, + 0x9C, 0x95, 0xE8, 0xC1, 0x54, 0x76, 0x67, 0x9F, + 0xFE, 0xC9, 0x53, 0xC1, 0x68, 0x51, 0xE7, 0x11, + 0xE7, 0x43, 0x93, 0x95, 0x89, 0xC6, 0x4F, 0xC1, + }, + } +} + +func testHash() []byte { + return []byte{ + 0x17, 0x1b, 0x1f, 0x5e, 0x9f, 0x8f, 0x8c, 0x5c, + 0x42, 0xe8, 0x06, 0x59, 0x7b, 0x54, 0xc7, 0xb4, + 0x49, 0x05, 0xa1, 0xdb, 0x3a, 0x3c, 0x31, 0xd3, + 0xb7, 0x56, 0x45, 0x8c, 0xc2, 0xd6, 0x88, 0x62, + 0x9e, 0xd6, 0x7b, 0x9b, 0x25, 0x68, 0xd6, 0xc6, + 0x18, 0x94, 0x1e, 0xfe, 0xe3, 0x33, 0x78, 0xa6, + 0xe1, 0xce, 0x13, 0x88, 0x81, 0x26, 0x02, 0x52, + 0xdf, 0xc2, 0x0a, 0xf2, 0x67, 0x49, 0x0a, 0x20, + } +} + +func fipsPCT[P Point[P]](c *Curve[P], k *PrivateKey) error { + return fips140.PCT("ECDSA PCT", func() error { + hash := testHash() + drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil) + sig, err := sign(c, k, drbg, hash) + if err != nil { + return err + } + return Verify(c, &k.pub, hash, sig) + }) +} + +var fipsSelfTest = sync.OnceFunc(func() { + fips140.CAST("ECDSA P-256 SHA2-512 sign and verify", func() error { + k := testPrivateKey() + Z := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + persStr := []byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + hash := testHash() + want := &Signature{ + R: []byte{ + 0x33, 0x64, 0x96, 0xff, 0x8a, 0xfe, 0xaa, 0x0b, + 0x2c, 0x4a, 0x1a, 0x97, 0x77, 0xcc, 0x84, 0xa5, + 0x7e, 0x88, 0x1f, 0x16, 0x2d, 0xe0, 0x29, 0xf7, + 0x62, 0xc2, 0x34, 0x18, 0x10, 0x9c, 0x69, 0x8a, + }, S: []byte{ + 0x97, 0x53, 0x2e, 0x13, 0x6e, 0xd0, 0x9b, 0x30, + 0x8a, 0xdf, 0x4f, 0xe0, 0x54, 0x82, 0x14, 0x83, + 0x5e, 0x93, 0xc7, 0x79, 0x4b, 0x18, 0xa3, 0xf1, + 0x8a, 0x60, 0xae, 0x52, 0x31, 0xe4, 0x2e, 0x4e, + }, + } + drbg := newDRBG(sha512.New, Z, nil, plainPersonalizationString(persStr)) + got, err := sign(P256(), k, drbg, hash) + if err != nil { + return err + } + if err := verify(P256(), &k.pub, hash, got); err != nil { + return err + } + if !bytes.Equal(got.R, want.R) || !bytes.Equal(got.S, want.S) { + return errors.New("unexpected result") + } + return nil + }) +}) + +var fipsSelfTestDeterministic = sync.OnceFunc(func() { + fips140.CAST("DetECDSA P-256 SHA2-512 sign", func() error { + k := testPrivateKey() + hash := testHash() + want := &Signature{ + R: []byte{ + 0x9f, 0xc3, 0x83, 0x32, 0x6e, 0xd9, 0x4f, 0x8e, + 0x24, 0xa0, 0x19, 0xef, 0x1d, 0x3a, 0xc3, 0x55, + 0xdd, 0x4b, 0x98, 0xae, 0x78, 0xa7, 0xaf, 0xd3, + 0xfd, 0xf3, 0x22, 0x1c, 0x8b, 0xd6, 0x11, 0x7b, + }, S: []byte{ + 0xd6, 0x52, 0x87, 0x41, 0x71, 0xbd, 0x66, 0xd1, + 0xaf, 0x6c, 0x61, 0xdd, 0xd8, 0xa7, 0xbb, 0xd2, + 0xf7, 0xd5, 0x47, 0x70, 0xe9, 0xe4, 0xac, 0x0a, + 0xb9, 0xfa, 0x0f, 0xbd, 0x3b, 0x9b, 0xc2, 0xfe, + }, + } + drbg := newDRBG(sha512.New, k.d, bits2octets(P256(), hash), nil) + got, err := sign(P256(), k, drbg, hash) + if err != nil { + return err + } + if err := verify(P256(), &k.pub, hash, got); err != nil { + return err + } + if !bytes.Equal(got.R, want.R) || !bytes.Equal(got.S, want.S) { + return errors.New("unexpected result") + } + return nil + }) +}) diff --git a/crypto/internal/fips140/ecdsa/ecdsa.go b/crypto/internal/fips140/ecdsa/ecdsa.go new file mode 100644 index 00000000000..5d79e2b7be8 --- /dev/null +++ b/crypto/internal/fips140/ecdsa/ecdsa.go @@ -0,0 +1,504 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdsa + +import ( + "bytes" + "errors" + "io" + "sync" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" +) + +// PrivateKey and PublicKey are not generic to make it possible to use them +// in other types without instantiating them with a specific point type. +// They are tied to one of the Curve types below through the curveID field. + +type PrivateKey struct { + pub PublicKey + d []byte // bigmod.(*Nat).Bytes output (same length as the curve order) +} + +func (priv *PrivateKey) Bytes() []byte { + return priv.d +} + +func (priv *PrivateKey) PublicKey() *PublicKey { + return &priv.pub +} + +type PublicKey struct { + curve curveID + q []byte // uncompressed nistec Point.Bytes output +} + +func (pub *PublicKey) Bytes() []byte { + return pub.q +} + +type curveID string + +const ( + p224 curveID = "P-224" + p256 curveID = "P-256" + p384 curveID = "P-384" + p521 curveID = "P-521" +) + +type Curve[P Point[P]] struct { + curve curveID + newPoint func() P + ordInverse func([]byte) ([]byte, error) + N *bigmod.Modulus + nMinus2 []byte +} + +// Point is a generic constraint for the [nistec] Point types. +type Point[P any] interface { + *nistec.P224Point | *nistec.P256Point | *nistec.P384Point | *nistec.P521Point + Bytes() []byte + BytesX() ([]byte, error) + SetBytes([]byte) (P, error) + ScalarMult(P, []byte) (P, error) + ScalarBaseMult([]byte) (P, error) + Add(p1, p2 P) P +} + +func precomputeParams[P Point[P]](c *Curve[P], order []byte) { + var err error + c.N, err = bigmod.NewModulus(order) + if err != nil { + panic(err) + } + two, _ := bigmod.NewNat().SetBytes([]byte{2}, c.N) + c.nMinus2 = bigmod.NewNat().ExpandFor(c.N).Sub(two, c.N).Bytes(c.N) +} + +func P224() *Curve[*nistec.P224Point] { return _P224() } + +var _P224 = sync.OnceValue(func() *Curve[*nistec.P224Point] { + c := &Curve[*nistec.P224Point]{ + curve: p224, + newPoint: nistec.NewP224Point, + } + precomputeParams(c, p224Order) + return c +}) + +var p224Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x16, 0xa2, + 0xe0, 0xb8, 0xf0, 0x3e, 0x13, 0xdd, 0x29, 0x45, + 0x5c, 0x5c, 0x2a, 0x3d, +} + +func P256() *Curve[*nistec.P256Point] { return _P256() } + +var _P256 = sync.OnceValue(func() *Curve[*nistec.P256Point] { + c := &Curve[*nistec.P256Point]{ + curve: p256, + newPoint: nistec.NewP256Point, + ordInverse: nistec.P256OrdInverse, + } + precomputeParams(c, p256Order) + return c +}) + +var p256Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xbc, 0xe6, 0xfa, 0xad, 0xa7, 0x17, 0x9e, 0x84, + 0xf3, 0xb9, 0xca, 0xc2, 0xfc, 0x63, 0x25, 0x51} + +func P384() *Curve[*nistec.P384Point] { return _P384() } + +var _P384 = sync.OnceValue(func() *Curve[*nistec.P384Point] { + c := &Curve[*nistec.P384Point]{ + curve: p384, + newPoint: nistec.NewP384Point, + } + precomputeParams(c, p384Order) + return c +}) + +var p384Order = []byte{ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc7, 0x63, 0x4d, 0x81, 0xf4, 0x37, 0x2d, 0xdf, + 0x58, 0x1a, 0x0d, 0xb2, 0x48, 0xb0, 0xa7, 0x7a, + 0xec, 0xec, 0x19, 0x6a, 0xcc, 0xc5, 0x29, 0x73} + +func P521() *Curve[*nistec.P521Point] { return _P521() } + +var _P521 = sync.OnceValue(func() *Curve[*nistec.P521Point] { + c := &Curve[*nistec.P521Point]{ + curve: p521, + newPoint: nistec.NewP521Point, + } + precomputeParams(c, p521Order) + return c +}) + +var p521Order = []byte{0x01, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, + 0x51, 0x86, 0x87, 0x83, 0xbf, 0x2f, 0x96, 0x6b, + 0x7f, 0xcc, 0x01, 0x48, 0xf7, 0x09, 0xa5, 0xd0, + 0x3b, 0xb5, 0xc9, 0xb8, 0x89, 0x9c, 0x47, 0xae, + 0xbb, 0x6f, 0xb7, 0x1e, 0x91, 0x38, 0x64, 0x09} + +func NewPrivateKey[P Point[P]](c *Curve[P], D, Q []byte) (*PrivateKey, error) { + fips140.RecordApproved() + pub, err := NewPublicKey(c, Q) + if err != nil { + return nil, err + } + d, err := bigmod.NewNat().SetBytes(D, c.N) + if err != nil { + return nil, err + } + priv := &PrivateKey{pub: *pub, d: d.Bytes(c.N)} + if err := fipsPCT(c, priv); err != nil { + // This can happen if the application went out of its way to make an + // ecdsa.PrivateKey with a mismatching PublicKey. + return nil, err + } + return priv, nil +} + +func NewPublicKey[P Point[P]](c *Curve[P], Q []byte) (*PublicKey, error) { + // SetBytes checks that Q is a valid point on the curve, and that its + // coordinates are reduced modulo p, fulfilling the requirements of SP + // 800-89, Section 5.3.2. + _, err := c.newPoint().SetBytes(Q) + if err != nil { + return nil, err + } + return &PublicKey{curve: c.curve, q: Q}, nil +} + +// GenerateKey generates a new ECDSA private key pair for the specified curve. +func GenerateKey[P Point[P]](c *Curve[P], rand io.Reader) (*PrivateKey, error) { + fips140.RecordApproved() + + k, Q, err := randomPoint(c, func(b []byte) error { + return drbg.ReadWithReader(rand, b) + }) + if err != nil { + return nil, err + } + + priv := &PrivateKey{ + pub: PublicKey{ + curve: c.curve, + q: Q.Bytes(), + }, + d: k.Bytes(c.N), + } + if err := fipsPCT(c, priv); err != nil { + // This clearly can't happen, but FIPS 140-3 mandates that we check it. + panic(err) + } + return priv, nil +} + +// randomPoint returns a random scalar and the corresponding point using a +// procedure equivalent to FIPS 186-5, Appendix A.2.2 (ECDSA Key Pair Generation +// by Rejection Sampling) and to Appendix A.3.2 (Per-Message Secret Number +// Generation of Private Keys by Rejection Sampling) or Appendix A.3.3 +// (Per-Message Secret Number Generation for Deterministic ECDSA) followed by +// Step 5 of Section 6.4.1. +func randomPoint[P Point[P]](c *Curve[P], generate func([]byte) error) (k *bigmod.Nat, p P, err error) { + for { + b := make([]byte, c.N.Size()) + if err := generate(b); err != nil { + return nil, nil, err + } + + // Take only the leftmost bits of the generated random value. This is + // both necessary to increase the chance of the random value being in + // the correct range and to match the specification. It's unfortunate + // that we need to do a shift instead of a mask, but see the comment on + // rightShift. + // + // These are the most dangerous lines in the package and maybe in the + // library: a single bit of bias in the selection of nonces would likely + // lead to key recovery, but no tests would fail. Look but DO NOT TOUCH. + if excess := len(b)*8 - c.N.BitLen(); excess > 0 { + // Just to be safe, assert that this only happens for the one curve that + // doesn't have a round number of bits. + if c.curve != p521 { + panic("ecdsa: internal error: unexpectedly masking off bits") + } + b = rightShift(b, excess) + } + + // FIPS 186-5, Appendix A.4.2 makes us check x <= N - 2 and then return + // x + 1. Note that it follows that 0 < x + 1 < N. Instead, SetBytes + // checks that k < N, and we explicitly check 0 != k. Since k can't be + // negative, this is strictly equivalent. None of this matters anyway + // because the chance of selecting zero is cryptographically negligible. + if k, err := bigmod.NewNat().SetBytes(b, c.N); err == nil && k.IsZero() == 0 { + p, err := c.newPoint().ScalarBaseMult(k.Bytes(c.N)) + return k, p, err + } + + if testingOnlyRejectionSamplingLooped != nil { + testingOnlyRejectionSamplingLooped() + } + } +} + +// testingOnlyRejectionSamplingLooped is called when rejection sampling in +// randomPoint rejects a candidate for being higher than the modulus. +var testingOnlyRejectionSamplingLooped func() + +// Signature is an ECDSA signature, where r and s are represented as big-endian +// byte slices of the same length as the curve order. +type Signature struct { + R, S []byte +} + +// Sign signs a hash (which shall be the result of hashing a larger message with +// the hash function H) using the private key, priv. If the hash is longer than +// the bit-length of the private key's curve order, the hash will be truncated +// to that length. +func Sign[P Point[P], H fips140.Hash](c *Curve[P], h func() H, priv *PrivateKey, rand io.Reader, hash []byte) (*Signature, error) { + if priv.pub.curve != c.curve { + return nil, errors.New("ecdsa: private key does not match curve") + } + fips140.RecordApproved() + fipsSelfTest() + + // Random ECDSA is dangerous, because a failure of the RNG would immediately + // leak the private key. Instead, we use a "hedged" approach, as specified + // in draft-irtf-cfrg-det-sigs-with-noise-04, Section 4. This has also the + // advantage of closely resembling Deterministic ECDSA. + + Z := make([]byte, len(priv.d)) + if err := drbg.ReadWithReader(rand, Z); err != nil { + return nil, err + } + + // See https://github.com/cfrg/draft-irtf-cfrg-det-sigs-with-noise/issues/6 + // for the FIPS compliance of this method. In short Z is entropy from the + // main DRBG, of length 3/2 of security_strength, so the nonce is optional + // per SP 800-90Ar1, Section 8.6.7, and the rest is a personalization + // string, which per SP 800-90Ar1, Section 8.7.1 may contain secret + // information. + drbg := newDRBG(h, Z, nil, blockAlignedPersonalizationString{priv.d, bits2octets(c, hash)}) + + return sign(c, priv, drbg, hash) +} + +// SignDeterministic signs a hash (which shall be the result of hashing a +// larger message with the hash function H) using the private key, priv. If the +// hash is longer than the bit-length of the private key's curve order, the hash +// will be truncated to that length. This applies Deterministic ECDSA as +// specified in FIPS 186-5 and RFC 6979. +func SignDeterministic[P Point[P], H fips140.Hash](c *Curve[P], h func() H, priv *PrivateKey, hash []byte) (*Signature, error) { + if priv.pub.curve != c.curve { + return nil, errors.New("ecdsa: private key does not match curve") + } + fips140.RecordApproved() + fipsSelfTestDeterministic() + drbg := newDRBG(h, priv.d, bits2octets(c, hash), nil) // RFC 6979, Section 3.3 + return sign(c, priv, drbg, hash) +} + +// bits2octets as specified in FIPS 186-5, Appendix B.2.4 or RFC 6979, +// Section 2.3.4. See RFC 6979, Section 3.5 for the rationale. +func bits2octets[P Point[P]](c *Curve[P], hash []byte) []byte { + e := bigmod.NewNat() + hashToNat(c, e, hash) + return e.Bytes(c.N) +} + +func signGeneric[P Point[P]](c *Curve[P], priv *PrivateKey, drbg *hmacDRBG, hash []byte) (*Signature, error) { + // FIPS 186-5, Section 6.4.1 + + k, R, err := randomPoint(c, func(b []byte) error { + drbg.Generate(b) + return nil + }) + if err != nil { + return nil, err + } + + // kInv = k⁻¹ + kInv := bigmod.NewNat() + inverse(c, kInv, k) + + Rx, err := R.BytesX() + if err != nil { + return nil, err + } + r, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N) + if err != nil { + return nil, err + } + + // The spec wants us to retry here, but the chance of hitting this condition + // on a large prime-order group like the NIST curves we support is + // cryptographically negligible. If we hit it, something is awfully wrong. + if r.IsZero() == 1 { + return nil, errors.New("ecdsa: internal error: r is zero") + } + + e := bigmod.NewNat() + hashToNat(c, e, hash) + + s, err := bigmod.NewNat().SetBytes(priv.d, c.N) + if err != nil { + return nil, err + } + s.Mul(r, c.N) + s.Add(e, c.N) + s.Mul(kInv, c.N) + + // Again, the chance of this happening is cryptographically negligible. + if s.IsZero() == 1 { + return nil, errors.New("ecdsa: internal error: s is zero") + } + + return &Signature{r.Bytes(c.N), s.Bytes(c.N)}, nil +} + +// inverse sets kInv to the inverse of k modulo the order of the curve. +func inverse[P Point[P]](c *Curve[P], kInv, k *bigmod.Nat) { + if c.ordInverse != nil { + kBytes, err := c.ordInverse(k.Bytes(c.N)) + // Some platforms don't implement ordInverse, and always return an error. + if err == nil { + _, err := kInv.SetBytes(kBytes, c.N) + if err != nil { + panic("ecdsa: internal error: ordInverse produced an invalid value") + } + return + } + } + + // Calculate the inverse of s in GF(N) using Fermat's method + // (exponentiation modulo P - 2, per Euler's theorem) + kInv.Exp(k, c.nMinus2, c.N) +} + +// hashToNat sets e to the left-most bits of hash, according to +// FIPS 186-5, Section 6.4.1, point 2 and Section 6.4.2, point 3. +func hashToNat[P Point[P]](c *Curve[P], e *bigmod.Nat, hash []byte) { + // ECDSA asks us to take the left-most log2(N) bits of hash, and use them as + // an integer modulo N. This is the absolute worst of all worlds: we still + // have to reduce, because the result might still overflow N, but to take + // the left-most bits for P-521 we have to do a right shift. + if size := c.N.Size(); len(hash) >= size { + hash = hash[:size] + if excess := len(hash)*8 - c.N.BitLen(); excess > 0 { + hash = rightShift(hash, excess) + } + } + _, err := e.SetOverflowingBytes(hash, c.N) + if err != nil { + panic("ecdsa: internal error: truncated hash is too long") + } +} + +// rightShift implements the right shift necessary for bits2int, which takes the +// leftmost bits of either the hash or HMAC_DRBG output. +// +// Note how taking the rightmost bits would have been as easy as masking the +// first byte, but we can't have nice things. +func rightShift(b []byte, shift int) []byte { + if shift <= 0 || shift >= 8 { + panic("ecdsa: internal error: shift can only be by 1 to 7 bits") + } + b = bytes.Clone(b) + for i := len(b) - 1; i >= 0; i-- { + b[i] >>= shift + if i > 0 { + b[i] |= b[i-1] << (8 - shift) + } + } + return b +} + +// Verify verifies the signature, sig, of hash (which should be the result of +// hashing a larger message) using the public key, pub. If the hash is longer +// than the bit-length of the private key's curve order, the hash will be +// truncated to that length. +// +// The inputs are not considered confidential, and may leak through timing side +// channels, or if an attacker has control of part of the inputs. +func Verify[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature) error { + if pub.curve != c.curve { + return errors.New("ecdsa: public key does not match curve") + } + fips140.RecordApproved() + fipsSelfTest() + return verify(c, pub, hash, sig) +} + +func verifyGeneric[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature) error { + // FIPS 186-5, Section 6.4.2 + + Q, err := c.newPoint().SetBytes(pub.q) + if err != nil { + return err + } + + r, err := bigmod.NewNat().SetBytes(sig.R, c.N) + if err != nil { + return err + } + if r.IsZero() == 1 { + return errors.New("ecdsa: invalid signature: r is zero") + } + s, err := bigmod.NewNat().SetBytes(sig.S, c.N) + if err != nil { + return err + } + if s.IsZero() == 1 { + return errors.New("ecdsa: invalid signature: s is zero") + } + + e := bigmod.NewNat() + hashToNat(c, e, hash) + + // w = s⁻¹ + w := bigmod.NewNat() + inverse(c, w, s) + + // p₁ = [e * s⁻¹]G + p1, err := c.newPoint().ScalarBaseMult(e.Mul(w, c.N).Bytes(c.N)) + if err != nil { + return err + } + // p₂ = [r * s⁻¹]Q + p2, err := Q.ScalarMult(Q, w.Mul(r, c.N).Bytes(c.N)) + if err != nil { + return err + } + // BytesX returns an error for the point at infinity. + Rx, err := p1.Add(p1, p2).BytesX() + if err != nil { + return err + } + + v, err := bigmod.NewNat().SetOverflowingBytes(Rx, c.N) + if err != nil { + return err + } + + if v.Equal(r) != 1 { + return errors.New("ecdsa: signature did not verify") + } + return nil +} diff --git a/crypto/internal/fips140/ecdsa/ecdsa_noasm.go b/crypto/internal/fips140/ecdsa/ecdsa_noasm.go new file mode 100644 index 00000000000..ffcc9fa0884 --- /dev/null +++ b/crypto/internal/fips140/ecdsa/ecdsa_noasm.go @@ -0,0 +1,15 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !s390x || purego + +package ecdsa + +func sign[P Point[P]](c *Curve[P], priv *PrivateKey, drbg *hmacDRBG, hash []byte) (*Signature, error) { + return signGeneric(c, priv, drbg, hash) +} + +func verify[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature) error { + return verifyGeneric(c, pub, hash, sig) +} diff --git a/crypto/ecdsa/ecdsa_s390x.go b/crypto/internal/fips140/ecdsa/ecdsa_s390x.go similarity index 53% rename from crypto/ecdsa/ecdsa_s390x.go rename to crypto/internal/fips140/ecdsa/ecdsa_s390x.go index 6c59c144d71..783dbf479f9 100644 --- a/crypto/ecdsa/ecdsa_s390x.go +++ b/crypto/internal/fips140/ecdsa/ecdsa_s390x.go @@ -8,10 +8,10 @@ package ecdsa import ( "errors" - "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/internal/cpu" - "io" - "math/big" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" ) // kdsa invokes the "compute digital signature authentication" @@ -25,62 +25,88 @@ import ( //go:noescape func kdsa(fc uint64, params *[4096]byte) (errn uint64) -// testingDisableKDSA forces the generic fallback path. It must only be set in tests. -var testingDisableKDSA bool +var supportsKDSA = cpu.S390XHasECDSA + +func init() { + // CP Assist for Cryptographic Functions (CPACF) + // https://www.ibm.com/docs/en/zos/3.1.0?topic=icsf-cp-assist-cryptographic-functions-cpacf + impl.Register("ecdsa", "CPACF", &supportsKDSA) +} // canUseKDSA checks if KDSA instruction is available, and if it is, it checks // the name of the curve to see if it matches the curves supported(P-256, P-384, P-521). // Then, based on the curve name, a function code and a block size will be assigned. // If KDSA instruction is not available or if the curve is not supported, canUseKDSA // will set ok to false. -func canUseKDSA(c elliptic.Curve) (functionCode uint64, blockSize int, ok bool) { - if testingDisableKDSA { - return 0, 0, false - } - if !cpu.S390X.HasECDSA { +func canUseKDSA(c curveID) (functionCode uint64, blockSize int, ok bool) { + if !supportsKDSA { return 0, 0, false } - switch c.Params().Name { - case "P-256": + switch c { + case p256: return 1, 32, true - case "P-384": + case p384: return 2, 48, true - case "P-521": + case p521: + // Note that the block size doesn't match the field size for P-521. return 3, 80, true } return 0, 0, false // A mismatch } -func hashToBytes(dst, hash []byte, c elliptic.Curve) { - l := len(dst) - if n := c.Params().N.BitLen(); n == l*8 { - // allocation free path for curves with a length that is a whole number of bytes - if len(hash) >= l { - // truncate hash - copy(dst, hash[:l]) - return +func hashToBytes[P Point[P]](c *Curve[P], hash []byte) []byte { + e := bigmod.NewNat() + hashToNat(c, e, hash) + return e.Bytes(c.N) +} + +// randomScalar is a copy of [randomPoint] that doesn't call ScalarBaseMult. +func randomScalar[P Point[P]](c *Curve[P], generate func([]byte) error) (k *bigmod.Nat, err error) { + for { + b := make([]byte, c.N.Size()) + if err := generate(b); err != nil { + return nil, err } - // pad hash with leading zeros - p := l - len(hash) - for i := 0; i < p; i++ { - dst[i] = 0 + if excess := len(b)*8 - c.N.BitLen(); excess > 0 { + if c.curve != p521 { + panic("ecdsa: internal error: unexpectedly masking off bits") + } + b = rightShift(b, excess) + } + if k, err := bigmod.NewNat().SetBytes(b, c.N); err == nil && k.IsZero() == 0 { + return k, nil } - copy(dst[p:], hash) - return } - // TODO(mundaym): avoid hashToInt call here - hashToInt(hash, c).FillBytes(dst) } -func signAsm(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err error) { - c := priv.Curve - functionCode, blockSize, ok := canUseKDSA(c) +func appendBlock(p []byte, blocksize int, b []byte) []byte { + if len(b) > blocksize { + panic("ecdsa: internal error: appendBlock input larger than block") + } + padding := blocksize - len(b) + p = append(p, make([]byte, padding)...) + return append(p, b...) +} + +func trimBlock(p []byte, size int) ([]byte, error) { + for _, b := range p[:len(p)-size] { + if b != 0 { + return nil, errors.New("ecdsa: internal error: KDSA produced invalid signature") + } + } + return p[len(p)-size:], nil +} + +func sign[P Point[P]](c *Curve[P], priv *PrivateKey, drbg *hmacDRBG, hash []byte) (*Signature, error) { + functionCode, blockSize, ok := canUseKDSA(c.curve) if !ok { - return nil, errNoAsm + return signGeneric(c, priv, drbg, hash) } for { - var k *big.Int - k, err = randFieldElement(c, csprng) + k, err := randomScalar(c, func(b []byte) error { + drbg.Generate(b) + return nil + }) if err != nil { return nil, err } @@ -108,37 +134,42 @@ func signAsm(priv *PrivateKey, csprng io.Reader, hash []byte) (sig []byte, err e // Copy content into the parameter block. In the sign case, // we copy hashed message, private key and random number into - // the parameter block. - hashToBytes(params[2*blockSize:3*blockSize], hash, c) - priv.D.FillBytes(params[3*blockSize : 4*blockSize]) - k.FillBytes(params[4*blockSize : 5*blockSize]) + // the parameter block. We skip the signature slots. + p := params[:2*blockSize] + p = appendBlock(p, blockSize, hashToBytes(c, hash)) + p = appendBlock(p, blockSize, priv.d) + p = appendBlock(p, blockSize, k.Bytes(c.N)) // Convert verify function code into a sign function code by adding 8. // We also need to set the 'deterministic' bit in the function code, by // adding 128, in order to stop the instruction using its own random number // generator in addition to the random number we supply. switch kdsa(functionCode+136, ¶ms) { case 0: // success - return encodeSignature(params[:blockSize], params[blockSize:2*blockSize]) + elementSize := (c.N.BitLen() + 7) / 8 + r, err := trimBlock(params[:blockSize], elementSize) + if err != nil { + return nil, err + } + s, err := trimBlock(params[blockSize:2*blockSize], elementSize) + if err != nil { + return nil, err + } + return &Signature{R: r, S: s}, nil case 1: // error - return nil, errZeroParam + return nil, errors.New("zero parameter") case 2: // retry continue } - panic("unreachable") } } -func verifyAsm(pub *PublicKey, hash []byte, sig []byte) error { - c := pub.Curve - functionCode, blockSize, ok := canUseKDSA(c) +func verify[P Point[P]](c *Curve[P], pub *PublicKey, hash []byte, sig *Signature) error { + functionCode, blockSize, ok := canUseKDSA(c.curve) if !ok { - return errNoAsm + return verifyGeneric(c, pub, hash, sig) } - r, s, err := parseSignature(sig) - if err != nil { - return err - } + r, s := sig.R, sig.S if len(r) > blockSize || len(s) > blockSize { return errors.New("invalid signature") } @@ -167,11 +198,12 @@ func verifyAsm(pub *PublicKey, hash []byte, sig []byte) error { // Copy content into the parameter block. In the verify case, // we copy signature (r), signature(s), hashed message, public key x component, // and public key y component into the parameter block. - copy(params[0*blockSize+blockSize-len(r):], r) - copy(params[1*blockSize+blockSize-len(s):], s) - hashToBytes(params[2*blockSize:3*blockSize], hash, c) - pub.X.FillBytes(params[3*blockSize : 4*blockSize]) - pub.Y.FillBytes(params[4*blockSize : 5*blockSize]) + p := params[:0] + p = appendBlock(p, blockSize, r) + p = appendBlock(p, blockSize, s) + p = appendBlock(p, blockSize, hashToBytes(c, hash)) + p = appendBlock(p, blockSize, pub.q[1:1+len(pub.q)/2]) + p = appendBlock(p, blockSize, pub.q[1+len(pub.q)/2:]) if kdsa(functionCode, ¶ms) != 0 { return errors.New("invalid signature") } diff --git a/crypto/ecdsa/ecdsa_s390x.s b/crypto/internal/fips140/ecdsa/ecdsa_s390x.s similarity index 100% rename from crypto/ecdsa/ecdsa_s390x.s rename to crypto/internal/fips140/ecdsa/ecdsa_s390x.s diff --git a/crypto/internal/fips140/ecdsa/ecdsa_test.go b/crypto/internal/fips140/ecdsa/ecdsa_test.go new file mode 100644 index 00000000000..4707277b159 --- /dev/null +++ b/crypto/internal/fips140/ecdsa/ecdsa_test.go @@ -0,0 +1,98 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdsa + +import ( + "bytes" + "io" + "testing" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" +) + +func TestRandomPoint(t *testing.T) { + t.Run("P-224", func(t *testing.T) { testRandomPoint(t, P224()) }) + t.Run("P-256", func(t *testing.T) { testRandomPoint(t, P256()) }) + t.Run("P-384", func(t *testing.T) { testRandomPoint(t, P384()) }) + t.Run("P-521", func(t *testing.T) { testRandomPoint(t, P521()) }) +} + +func testRandomPoint[P Point[P]](t *testing.T, c *Curve[P]) { + t.Cleanup(func() { testingOnlyRejectionSamplingLooped = nil }) + var loopCount int + testingOnlyRejectionSamplingLooped = func() { loopCount++ } + + // A sequence of all ones will generate 2^N-1, which should be rejected. + // (Unless, for example, we are masking too many bits.) + r := io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0xff}, 100)), rand.Reader) + if k, p, err := randomPoint(c, func(b []byte) error { + _, err := r.Read(b) + return err + }); err != nil { + t.Fatal(err) + } else if k.IsZero() == 1 { + t.Error("k is zero") + } else if p.Bytes()[0] != 4 { + t.Error("p is infinity") + } + if loopCount == 0 { + t.Error("overflow was not rejected") + } + loopCount = 0 + + // A sequence of all zeroes will generate zero, which should be rejected. + r = io.MultiReader(bytes.NewReader(bytes.Repeat([]byte{0}, 100)), rand.Reader) + if k, p, err := randomPoint(c, func(b []byte) error { + _, err := r.Read(b) + return err + }); err != nil { + t.Fatal(err) + } else if k.IsZero() == 1 { + t.Error("k is zero") + } else if p.Bytes()[0] != 4 { + t.Error("p is infinity") + } + if loopCount == 0 { + t.Error("zero was not rejected") + } + loopCount = 0 + + // P-256 has a 2⁻³² chance of randomly hitting a rejection. For P-224 it's + // 2⁻¹¹², for P-384 it's 2⁻¹⁹⁴, and for P-521 it's 2⁻²⁶², so if we hit in + // tests, something is horribly wrong. (For example, we are masking the + // wrong bits.) + if c.curve == p256 { + return + } + if k, p, err := randomPoint(c, func(b []byte) error { + _, err := rand.Reader.Read(b) + return err + }); err != nil { + t.Fatal(err) + } else if k.IsZero() == 1 { + t.Error("k is zero") + } else if p.Bytes()[0] != 4 { + t.Error("p is infinity") + } + if loopCount > 0 { + t.Error("unexpected rejection") + } +} + +func TestHashToNat(t *testing.T) { + t.Run("P-224", func(t *testing.T) { testHashToNat(t, P224()) }) + t.Run("P-256", func(t *testing.T) { testHashToNat(t, P256()) }) + t.Run("P-384", func(t *testing.T) { testHashToNat(t, P384()) }) + t.Run("P-521", func(t *testing.T) { testHashToNat(t, P521()) }) +} + +func testHashToNat[P Point[P]](t *testing.T, c *Curve[P]) { + for l := 0; l < 600; l++ { + h := bytes.Repeat([]byte{0xff}, l) + hashToNat(c, bigmod.NewNat(), h) + } +} diff --git a/crypto/internal/fips140/ecdsa/hmacdrbg.go b/crypto/internal/fips140/ecdsa/hmacdrbg.go new file mode 100644 index 00000000000..d864183127a --- /dev/null +++ b/crypto/internal/fips140/ecdsa/hmacdrbg.go @@ -0,0 +1,175 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ecdsa + +import ( + "bytes" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" +) + +// hmacDRBG is an SP 800-90A Rev. 1 HMAC_DRBG. +// +// It is only intended to be used to generate ECDSA nonces. Since it will be +// instantiated ex-novo for each signature, its Generate function will only be +// invoked once or twice (only for P-256, with probability 2⁻³²). +// +// Per Table 2, it has a reseed interval of 2^48 requests, and a maximum request +// size of 2^19 bits (2^16 bytes, 64 KiB). +type hmacDRBG struct { + newHMAC func(key []byte) *hmac.HMAC + + hK *hmac.HMAC + V []byte + + reseedCounter uint64 +} + +const ( + reseedInterval = 1 << 48 + maxRequestSize = (1 << 19) / 8 +) + +// plainPersonalizationString is used by HMAC_DRBG as-is. +type plainPersonalizationString []byte + +func (plainPersonalizationString) isPersonalizationString() {} + +// Each entry in blockAlignedPersonalizationString is written to the HMAC at a +// block boundary, as specified in draft-irtf-cfrg-det-sigs-with-noise-04, +// Section 4. +type blockAlignedPersonalizationString [][]byte + +func (blockAlignedPersonalizationString) isPersonalizationString() {} + +type personalizationString interface { + isPersonalizationString() +} + +func newDRBG[H fips140.Hash](hash func() H, entropy, nonce []byte, s personalizationString) *hmacDRBG { + // HMAC_DRBG_Instantiate_algorithm, per Section 10.1.2.3. + fips140.RecordApproved() + + d := &hmacDRBG{ + newHMAC: func(key []byte) *hmac.HMAC { + return hmac.New(hash, key) + }, + } + size := hash().Size() + + // K = 0x00 0x00 0x00 ... 0x00 + K := make([]byte, size) + + // V = 0x01 0x01 0x01 ... 0x01 + d.V = bytes.Repeat([]byte{0x01}, size) + + // HMAC_DRBG_Update, per Section 10.1.2.2. + // K = HMAC (K, V || 0x00 || provided_data) + h := hmac.New(hash, K) + h.Write(d.V) + h.Write([]byte{0x00}) + h.Write(entropy) + h.Write(nonce) + switch s := s.(type) { + case plainPersonalizationString: + h.Write(s) + case blockAlignedPersonalizationString: + l := len(d.V) + 1 + len(entropy) + len(nonce) + for _, b := range s { + pad000(h, l) + h.Write(b) + l = len(b) + } + } + K = h.Sum(K[:0]) + // V = HMAC (K, V) + h = hmac.New(hash, K) + h.Write(d.V) + d.V = h.Sum(d.V[:0]) + // K = HMAC (K, V || 0x01 || provided_data). + h.Reset() + h.Write(d.V) + h.Write([]byte{0x01}) + h.Write(entropy) + h.Write(nonce) + switch s := s.(type) { + case plainPersonalizationString: + h.Write(s) + case blockAlignedPersonalizationString: + l := len(d.V) + 1 + len(entropy) + len(nonce) + for _, b := range s { + pad000(h, l) + h.Write(b) + l = len(b) + } + } + K = h.Sum(K[:0]) + // V = HMAC (K, V) + h = hmac.New(hash, K) + h.Write(d.V) + d.V = h.Sum(d.V[:0]) + + d.hK = h + d.reseedCounter = 1 + return d +} + +// TestingOnlyNewDRBG creates an SP 800-90A Rev. 1 HMAC_DRBG with a plain +// personalization string. +// +// This should only be used for ACVP testing. hmacDRBG is not intended to be +// used directly. +func TestingOnlyNewDRBG(hash func() fips140.Hash, entropy, nonce []byte, s []byte) *hmacDRBG { + return newDRBG(hash, entropy, nonce, plainPersonalizationString(s)) +} + +func pad000(h *hmac.HMAC, writtenSoFar int) { + blockSize := h.BlockSize() + if rem := writtenSoFar % blockSize; rem != 0 { + h.Write(make([]byte, blockSize-rem)) + } +} + +// Generate produces at most maxRequestSize bytes of random data in out. +func (d *hmacDRBG) Generate(out []byte) { + // HMAC_DRBG_Generate_algorithm, per Section 10.1.2.5. + fips140.RecordApproved() + + if len(out) > maxRequestSize { + panic("ecdsa: internal error: request size exceeds maximum") + } + + if d.reseedCounter > reseedInterval { + panic("ecdsa: reseed interval exceeded") + } + + tlen := 0 + for tlen < len(out) { + // V = HMAC_K(V) + // T = T || V + d.hK.Reset() + d.hK.Write(d.V) + d.V = d.hK.Sum(d.V[:0]) + tlen += copy(out[tlen:], d.V) + } + + // Note that if this function shows up on ECDSA-level profiles, this can be + // optimized in the common case by deferring the rest to the next Generate + // call, which will never come in nearly all cases. + + // HMAC_DRBG_Update, per Section 10.1.2.2, without provided_data. + // K = HMAC (K, V || 0x00) + d.hK.Reset() + d.hK.Write(d.V) + d.hK.Write([]byte{0x00}) + K := d.hK.Sum(nil) + // V = HMAC (K, V) + d.hK = d.newHMAC(K) + d.hK.Write(d.V) + d.V = d.hK.Sum(d.V[:0]) + + d.reseedCounter++ +} diff --git a/crypto/internal/fips140/ed25519/cast.go b/crypto/internal/fips140/ed25519/cast.go new file mode 100644 index 00000000000..fb78d345051 --- /dev/null +++ b/crypto/internal/fips140/ed25519/cast.go @@ -0,0 +1,78 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ed25519 + +import ( + "bytes" + "errors" + "sync" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func fipsPCT(k *PrivateKey) error { + return fips140.PCT("Ed25519 sign and verify PCT", func() error { + return pairwiseTest(k) + }) +} + +// pairwiseTest needs to be a top-level function declaration to let the calls +// inline and their allocations not escape. +func pairwiseTest(k *PrivateKey) error { + msg := []byte("PCT") + sig := Sign(k, msg) + // Note that this runs pub.a.SetBytes. If we wanted to make key generation + // in FIPS mode faster, we could reuse A from GenerateKey. But another thing + // that could make it faster is just _not doing a useless self-test_. + pub, err := NewPublicKey(k.PublicKey()) + if err != nil { + return err + } + return Verify(pub, msg, sig) +} + +func signWithoutSelfTest(priv *PrivateKey, message []byte) []byte { + signature := make([]byte, signatureSize) + return signWithDom(signature, priv, message, domPrefixPure, "") +} + +func verifyWithoutSelfTest(pub *PublicKey, message, sig []byte) error { + return verifyWithDom(pub, message, sig, domPrefixPure, "") +} + +var fipsSelfTest = sync.OnceFunc(func() { + fips140.CAST("Ed25519 sign and verify", func() error { + seed := [32]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + msg := []byte("CAST") + want := []byte{ + 0xbd, 0xe7, 0xa5, 0xf3, 0x40, 0x73, 0xb9, 0x5a, + 0x2e, 0x6d, 0x63, 0x20, 0x0a, 0xd5, 0x92, 0x9b, + 0xa2, 0x3d, 0x00, 0x44, 0xb4, 0xc5, 0xfd, 0x62, + 0x1d, 0x5e, 0x33, 0x2f, 0xe4, 0x61, 0x42, 0x31, + 0x5b, 0x10, 0x53, 0x13, 0x4d, 0xcb, 0xd1, 0x1b, + 0x2a, 0xf6, 0xcd, 0x0e, 0xdb, 0x9a, 0xd3, 0x1e, + 0x35, 0xdb, 0x0b, 0xcf, 0x58, 0x90, 0x4f, 0xd7, + 0x69, 0x38, 0xed, 0x30, 0x51, 0x0f, 0xaa, 0x03, + } + k := &PrivateKey{seed: seed} + precomputePrivateKey(k) + pub, err := NewPublicKey(k.PublicKey()) + if err != nil { + return err + } + sig := signWithoutSelfTest(k, msg) + if !bytes.Equal(sig, want) { + return errors.New("unexpected result") + } + return verifyWithoutSelfTest(pub, msg, sig) + }) +}) diff --git a/crypto/internal/fips140/ed25519/ed25519.go b/crypto/internal/fips140/ed25519/ed25519.go new file mode 100644 index 00000000000..625ec64375a --- /dev/null +++ b/crypto/internal/fips140/ed25519/ed25519.go @@ -0,0 +1,342 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ed25519 + +import ( + "bytes" + "errors" + "strconv" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/edwards25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" +) + +// See https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ for the +// components of the keys and the moving parts of the algorithm. + +const ( + seedSize = 32 + publicKeySize = 32 + privateKeySize = seedSize + publicKeySize + signatureSize = 64 + sha512Size = 64 +) + +type PrivateKey struct { + seed [seedSize]byte + pub [publicKeySize]byte + s edwards25519.Scalar + prefix [sha512Size / 2]byte +} + +func (priv *PrivateKey) Bytes() []byte { + k := make([]byte, 0, privateKeySize) + k = append(k, priv.seed[:]...) + k = append(k, priv.pub[:]...) + return k +} + +func (priv *PrivateKey) Seed() []byte { + seed := priv.seed + return seed[:] +} + +func (priv *PrivateKey) PublicKey() []byte { + pub := priv.pub + return pub[:] +} + +type PublicKey struct { + a edwards25519.Point + aBytes [32]byte +} + +func (pub *PublicKey) Bytes() []byte { + a := pub.aBytes + return a[:] +} + +// GenerateKey generates a new Ed25519 private key pair. +func GenerateKey() (*PrivateKey, error) { + priv := &PrivateKey{} + return generateKey(priv) +} + +func generateKey(priv *PrivateKey) (*PrivateKey, error) { + fips140.RecordApproved() + drbg.Read(priv.seed[:]) + precomputePrivateKey(priv) + if err := fipsPCT(priv); err != nil { + // This clearly can't happen, but FIPS 140-3 requires that we check. + panic(err) + } + return priv, nil +} + +func NewPrivateKeyFromSeed(seed []byte) (*PrivateKey, error) { + priv := &PrivateKey{} + return newPrivateKeyFromSeed(priv, seed) +} + +func newPrivateKeyFromSeed(priv *PrivateKey, seed []byte) (*PrivateKey, error) { + fips140.RecordApproved() + if l := len(seed); l != seedSize { + return nil, errors.New("ed25519: bad seed length: " + strconv.Itoa(l)) + } + copy(priv.seed[:], seed) + precomputePrivateKey(priv) + if err := fipsPCT(priv); err != nil { + // This clearly can't happen, but FIPS 140-3 requires that we check. + panic(err) + } + return priv, nil +} + +func precomputePrivateKey(priv *PrivateKey) { + hs := sha512.New() + hs.Write(priv.seed[:]) + h := hs.Sum(make([]byte, 0, sha512Size)) + + s, err := priv.s.SetBytesWithClamping(h[:32]) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + A := (&edwards25519.Point{}).ScalarBaseMult(s) + copy(priv.pub[:], A.Bytes()) + + copy(priv.prefix[:], h[32:]) +} + +func NewPrivateKey(priv []byte) (*PrivateKey, error) { + p := &PrivateKey{} + return newPrivateKey(p, priv) +} + +func newPrivateKey(priv *PrivateKey, privBytes []byte) (*PrivateKey, error) { + fips140.RecordApproved() + if l := len(privBytes); l != privateKeySize { + return nil, errors.New("ed25519: bad private key length: " + strconv.Itoa(l)) + } + + copy(priv.seed[:], privBytes[:32]) + + hs := sha512.New() + hs.Write(priv.seed[:]) + h := hs.Sum(make([]byte, 0, sha512Size)) + + if _, err := priv.s.SetBytesWithClamping(h[:32]); err != nil { + panic("ed25519: internal error: setting scalar failed") + } + // Note that we are not decompressing the public key point here, + // because it takes > 20% of the time of a signature generation. + // Signing doesn't use it as a point anyway. + copy(priv.pub[:], privBytes[32:]) + + copy(priv.prefix[:], h[32:]) + + if err := fipsPCT(priv); err != nil { + // This can happen if the application messed with the private key + // encoding, and the public key doesn't match the seed anymore. + return nil, err + } + + return priv, nil +} + +func NewPublicKey(pub []byte) (*PublicKey, error) { + p := &PublicKey{} + return newPublicKey(p, pub) +} + +func newPublicKey(pub *PublicKey, pubBytes []byte) (*PublicKey, error) { + if l := len(pubBytes); l != publicKeySize { + return nil, errors.New("ed25519: bad public key length: " + strconv.Itoa(l)) + } + // SetBytes checks that the point is on the curve. + if _, err := pub.a.SetBytes(pubBytes); err != nil { + return nil, errors.New("ed25519: bad public key") + } + copy(pub.aBytes[:], pubBytes) + return pub, nil +} + +// Domain separation prefixes used to disambiguate Ed25519/Ed25519ph/Ed25519ctx. +// See RFC 8032, Section 2 and Section 5.1. +const ( + // domPrefixPure is empty for pure Ed25519. + domPrefixPure = "" + // domPrefixPh is dom2(phflag=1) for Ed25519ph. It must be followed by the + // uint8-length prefixed context. + domPrefixPh = "SigEd25519 no Ed25519 collisions\x01" + // domPrefixCtx is dom2(phflag=0) for Ed25519ctx. It must be followed by the + // uint8-length prefixed context. + domPrefixCtx = "SigEd25519 no Ed25519 collisions\x00" +) + +func Sign(priv *PrivateKey, message []byte) []byte { + // Outline the function body so that the returned signature can be + // stack-allocated. + signature := make([]byte, signatureSize) + return sign(signature, priv, message) +} + +func sign(signature []byte, priv *PrivateKey, message []byte) []byte { + fipsSelfTest() + fips140.RecordApproved() + return signWithDom(signature, priv, message, domPrefixPure, "") +} + +func SignPH(priv *PrivateKey, message []byte, context string) ([]byte, error) { + // Outline the function body so that the returned signature can be + // stack-allocated. + signature := make([]byte, signatureSize) + return signPH(signature, priv, message, context) +} + +func signPH(signature []byte, priv *PrivateKey, message []byte, context string) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + if l := len(message); l != sha512Size { + return nil, errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) + } + if l := len(context); l > 255 { + return nil, errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) + } + return signWithDom(signature, priv, message, domPrefixPh, context), nil +} + +func SignCtx(priv *PrivateKey, message []byte, context string) ([]byte, error) { + // Outline the function body so that the returned signature can be + // stack-allocated. + signature := make([]byte, signatureSize) + return signCtx(signature, priv, message, context) +} + +func signCtx(signature []byte, priv *PrivateKey, message []byte, context string) ([]byte, error) { + fipsSelfTest() + // FIPS 186-5 specifies Ed25519 and Ed25519ph (with context), but not Ed25519ctx. + fips140.RecordNonApproved() + // Note that per RFC 8032, Section 5.1, the context SHOULD NOT be empty. + if l := len(context); l > 255 { + return nil, errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l)) + } + return signWithDom(signature, priv, message, domPrefixCtx, context), nil +} + +func signWithDom(signature []byte, priv *PrivateKey, message []byte, domPrefix, context string) []byte { + mh := sha512.New() + if domPrefix != domPrefixPure { + mh.Write([]byte(domPrefix)) + mh.Write([]byte{byte(len(context))}) + mh.Write([]byte(context)) + } + mh.Write(priv.prefix[:]) + mh.Write(message) + messageDigest := make([]byte, 0, sha512Size) + messageDigest = mh.Sum(messageDigest) + r, err := edwards25519.NewScalar().SetUniformBytes(messageDigest) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + + R := (&edwards25519.Point{}).ScalarBaseMult(r) + + kh := sha512.New() + if domPrefix != domPrefixPure { + kh.Write([]byte(domPrefix)) + kh.Write([]byte{byte(len(context))}) + kh.Write([]byte(context)) + } + kh.Write(R.Bytes()) + kh.Write(priv.pub[:]) + kh.Write(message) + hramDigest := make([]byte, 0, sha512Size) + hramDigest = kh.Sum(hramDigest) + k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + + S := edwards25519.NewScalar().MultiplyAdd(k, &priv.s, r) + + copy(signature[:32], R.Bytes()) + copy(signature[32:], S.Bytes()) + + return signature +} + +func Verify(pub *PublicKey, message, sig []byte) error { + return verify(pub, message, sig) +} + +func verify(pub *PublicKey, message, sig []byte) error { + fipsSelfTest() + fips140.RecordApproved() + return verifyWithDom(pub, message, sig, domPrefixPure, "") +} + +func VerifyPH(pub *PublicKey, message []byte, sig []byte, context string) error { + fipsSelfTest() + fips140.RecordApproved() + if l := len(message); l != sha512Size { + return errors.New("ed25519: bad Ed25519ph message hash length: " + strconv.Itoa(l)) + } + if l := len(context); l > 255 { + return errors.New("ed25519: bad Ed25519ph context length: " + strconv.Itoa(l)) + } + return verifyWithDom(pub, message, sig, domPrefixPh, context) +} + +func VerifyCtx(pub *PublicKey, message []byte, sig []byte, context string) error { + fipsSelfTest() + // FIPS 186-5 specifies Ed25519 and Ed25519ph (with context), but not Ed25519ctx. + fips140.RecordNonApproved() + if l := len(context); l > 255 { + return errors.New("ed25519: bad Ed25519ctx context length: " + strconv.Itoa(l)) + } + return verifyWithDom(pub, message, sig, domPrefixCtx, context) +} + +func verifyWithDom(pub *PublicKey, message, sig []byte, domPrefix, context string) error { + if l := len(sig); l != signatureSize { + return errors.New("ed25519: bad signature length: " + strconv.Itoa(l)) + } + + if sig[63]&224 != 0 { + return errors.New("ed25519: invalid signature") + } + + kh := sha512.New() + if domPrefix != domPrefixPure { + kh.Write([]byte(domPrefix)) + kh.Write([]byte{byte(len(context))}) + kh.Write([]byte(context)) + } + kh.Write(sig[:32]) + kh.Write(pub.aBytes[:]) + kh.Write(message) + hramDigest := make([]byte, 0, sha512Size) + hramDigest = kh.Sum(hramDigest) + k, err := edwards25519.NewScalar().SetUniformBytes(hramDigest) + if err != nil { + panic("ed25519: internal error: setting scalar failed") + } + + S, err := edwards25519.NewScalar().SetCanonicalBytes(sig[32:]) + if err != nil { + return errors.New("ed25519: invalid signature") + } + + // [S]B = R + [k]A --> [k](-A) + [S]B = R + minusA := (&edwards25519.Point{}).Negate(&pub.a) + R := (&edwards25519.Point{}).VarTimeDoubleScalarBaseMult(k, minusA, S) + + if !bytes.Equal(sig[:32], R.Bytes()) { + return errors.New("ed25519: invalid signature") + } + return nil +} diff --git a/crypto/internal/edwards25519/doc.go b/crypto/internal/fips140/edwards25519/doc.go similarity index 100% rename from crypto/internal/edwards25519/doc.go rename to crypto/internal/fips140/edwards25519/doc.go diff --git a/crypto/internal/edwards25519/edwards25519.go b/crypto/internal/fips140/edwards25519/edwards25519.go similarity index 98% rename from crypto/internal/edwards25519/edwards25519.go rename to crypto/internal/fips140/edwards25519/edwards25519.go index 90d86c17375..3536a72f351 100644 --- a/crypto/internal/edwards25519/edwards25519.go +++ b/crypto/internal/fips140/edwards25519/edwards25519.go @@ -6,7 +6,10 @@ package edwards25519 import ( "errors" - "github.com/runZeroInc/excrypto/crypto/internal/edwards25519/field" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/edwards25519/field" ) // Point types. diff --git a/crypto/internal/edwards25519/edwards25519_test.go b/crypto/internal/fips140/edwards25519/edwards25519_test.go similarity index 94% rename from crypto/internal/edwards25519/edwards25519_test.go rename to crypto/internal/fips140/edwards25519/edwards25519_test.go index f4b76ddb899..687a3147867 100644 --- a/crypto/internal/edwards25519/edwards25519_test.go +++ b/crypto/internal/fips140/edwards25519/edwards25519_test.go @@ -6,10 +6,10 @@ package edwards25519 import ( "encoding/hex" - "github.com/runZeroInc/excrypto/crypto/internal/edwards25519/field" - "github.com/runZeroInc/excrypto/internal/testenv" "reflect" "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/edwards25519/field" ) var B = NewGeneratorPoint() @@ -277,22 +277,6 @@ func TestNonCanonicalPoints(t *testing.T) { } } -var testAllocationsSink byte - -func TestAllocations(t *testing.T) { - testenv.SkipIfOptimizationOff(t) - - if allocs := testing.AllocsPerRun(100, func() { - p := NewIdentityPoint() - p.Add(p, NewGeneratorPoint()) - s := NewScalar() - testAllocationsSink ^= s.Bytes()[0] - testAllocationsSink ^= p.Bytes()[0] - }); allocs > 0 { - t.Errorf("expected zero allocations, got %0.1v", allocs) - } -} - func decodeHex(s string) []byte { b, err := hex.DecodeString(s) if err != nil { diff --git a/crypto/internal/edwards25519/field/_asm/fe_amd64_asm.go b/crypto/internal/fips140/edwards25519/field/_asm/fe_amd64_asm.go similarity index 98% rename from crypto/internal/edwards25519/field/_asm/fe_amd64_asm.go rename to crypto/internal/fips140/edwards25519/field/_asm/fe_amd64_asm.go index 7f60c198570..e5090521605 100644 --- a/crypto/internal/edwards25519/field/_asm/fe_amd64_asm.go +++ b/crypto/internal/fips140/edwards25519/field/_asm/fe_amd64_asm.go @@ -16,7 +16,7 @@ import ( //go:generate go run . -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field func main() { - Package("github.com/runZeroInc/excrypto/crypto/internal/edwards25519/field") + Package("crypto/internal/fips140/edwards25519/field") ConstraintExpr("!purego") feMul() feSquare() diff --git a/crypto/internal/fips140/edwards25519/field/_asm/go.mod b/crypto/internal/fips140/edwards25519/field/_asm/go.mod new file mode 100644 index 00000000000..6eb11fe7cdd --- /dev/null +++ b/crypto/internal/fips140/edwards25519/field/_asm/go.mod @@ -0,0 +1,12 @@ +module crypto/internal/fips140/edwards25519/field/_asm + +go 1.19 + +require github.com/mmcloughlin/avo v0.4.0 + +require ( + golang.org/x/mod v0.4.2 // indirect + golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 // indirect + golang.org/x/tools v0.1.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect +) diff --git a/crypto/internal/fips140/edwards25519/field/_asm/go.sum b/crypto/internal/fips140/edwards25519/field/_asm/go.sum new file mode 100644 index 00000000000..b4b59140f0d --- /dev/null +++ b/crypto/internal/fips140/edwards25519/field/_asm/go.sum @@ -0,0 +1,32 @@ +github.com/mmcloughlin/avo v0.4.0 h1:jeHDRktVD+578ULxWpQHkilor6pkdLF7u7EiTzDbfcU= +github.com/mmcloughlin/avo v0.4.0/go.mod h1:RW9BfYA3TgO9uCdNrKU2h6J8cPD8ZLznvfgHAeszb1s= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk= +golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/crypto/internal/edwards25519/field/fe.go b/crypto/internal/fips140/edwards25519/field/fe.go similarity index 91% rename from crypto/internal/edwards25519/field/fe.go rename to crypto/internal/fips140/edwards25519/field/fe.go index c4a5dbd96dc..7f90e807f9d 100644 --- a/crypto/internal/edwards25519/field/fe.go +++ b/crypto/internal/fips140/edwards25519/field/fe.go @@ -7,9 +7,12 @@ package field import ( "errors" - "github.com/runZeroInc/excrypto/crypto/subtle" - "github.com/runZeroInc/excrypto/internal/byteorder" "math/bits" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" ) // Element represents an element of the field GF(2^255-19). Note that this @@ -201,20 +204,20 @@ func (v *Element) SetBytes(x []byte) (*Element, error) { } // Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51). - v.l0 = byteorder.LeUint64(x[0:8]) + v.l0 = byteorder.LEUint64(x[0:8]) v.l0 &= maskLow51Bits // Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51). - v.l1 = byteorder.LeUint64(x[6:14]) >> 3 + v.l1 = byteorder.LEUint64(x[6:14]) >> 3 v.l1 &= maskLow51Bits // Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51). - v.l2 = byteorder.LeUint64(x[12:20]) >> 6 + v.l2 = byteorder.LEUint64(x[12:20]) >> 6 v.l2 &= maskLow51Bits // Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51). - v.l3 = byteorder.LeUint64(x[19:27]) >> 1 + v.l3 = byteorder.LEUint64(x[19:27]) >> 1 v.l3 &= maskLow51Bits // Bits 204:255 (bytes 24:32, bits 192:256, shift 12, mask 51). // Note: not bytes 25:33, shift 4, to avoid overread. - v.l4 = byteorder.LeUint64(x[24:32]) >> 12 + v.l4 = byteorder.LEUint64(x[24:32]) >> 12 v.l4 &= maskLow51Bits return v, nil @@ -232,18 +235,22 @@ func (v *Element) bytes(out *[32]byte) []byte { t := *v t.reduce() - var buf [8]byte - for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} { - bitsOffset := i * 51 - byteorder.LePutUint64(buf[:], l<= len(out) { - break - } - out[off] |= bb - } - } + // Pack five 51-bit limbs into four 64-bit words: + // + // 255 204 153 102 51 0 + // ├──l4──┼──l3──┼──l2──┼──l1──┼──l0──┤ + // ├───u3───┼───u2───┼───u1───┼───u0───┤ + // 256 192 128 64 0 + + u0 := t.l1<<51 | t.l0 + u1 := t.l2<<(102-64) | t.l1>>(64-51) + u2 := t.l3<<(153-128) | t.l2>>(128-102) + u3 := t.l4<<(204-192) | t.l3>>(192-153) + + byteorder.LEPutUint64(out[0*8:], u0) + byteorder.LEPutUint64(out[1*8:], u1) + byteorder.LEPutUint64(out[2*8:], u2) + byteorder.LEPutUint64(out[3*8:], u3) return out[:] } diff --git a/crypto/internal/edwards25519/field/fe_alias_test.go b/crypto/internal/fips140/edwards25519/field/fe_alias_test.go similarity index 100% rename from crypto/internal/edwards25519/field/fe_alias_test.go rename to crypto/internal/fips140/edwards25519/field/fe_alias_test.go diff --git a/crypto/internal/edwards25519/field/fe_amd64.go b/crypto/internal/fips140/edwards25519/field/fe_amd64.go similarity index 100% rename from crypto/internal/edwards25519/field/fe_amd64.go rename to crypto/internal/fips140/edwards25519/field/fe_amd64.go diff --git a/crypto/internal/edwards25519/field/fe_amd64.s b/crypto/internal/fips140/edwards25519/field/fe_amd64.s similarity index 100% rename from crypto/internal/edwards25519/field/fe_amd64.s rename to crypto/internal/fips140/edwards25519/field/fe_amd64.s diff --git a/crypto/internal/edwards25519/field/fe_amd64_noasm.go b/crypto/internal/fips140/edwards25519/field/fe_amd64_noasm.go similarity index 100% rename from crypto/internal/edwards25519/field/fe_amd64_noasm.go rename to crypto/internal/fips140/edwards25519/field/fe_amd64_noasm.go diff --git a/crypto/internal/edwards25519/field/fe_arm64.go b/crypto/internal/fips140/edwards25519/field/fe_arm64.go similarity index 100% rename from crypto/internal/edwards25519/field/fe_arm64.go rename to crypto/internal/fips140/edwards25519/field/fe_arm64.go diff --git a/crypto/internal/edwards25519/field/fe_arm64.s b/crypto/internal/fips140/edwards25519/field/fe_arm64.s similarity index 100% rename from crypto/internal/edwards25519/field/fe_arm64.s rename to crypto/internal/fips140/edwards25519/field/fe_arm64.s diff --git a/crypto/internal/edwards25519/field/fe_arm64_noasm.go b/crypto/internal/fips140/edwards25519/field/fe_arm64_noasm.go similarity index 100% rename from crypto/internal/edwards25519/field/fe_arm64_noasm.go rename to crypto/internal/fips140/edwards25519/field/fe_arm64_noasm.go diff --git a/crypto/internal/edwards25519/field/fe_bench_test.go b/crypto/internal/fips140/edwards25519/field/fe_bench_test.go similarity index 88% rename from crypto/internal/edwards25519/field/fe_bench_test.go rename to crypto/internal/fips140/edwards25519/field/fe_bench_test.go index 84fdf05a8e4..fb80ca88fe2 100644 --- a/crypto/internal/edwards25519/field/fe_bench_test.go +++ b/crypto/internal/fips140/edwards25519/field/fe_bench_test.go @@ -47,3 +47,11 @@ func BenchmarkMult32(b *testing.B) { x.Mult32(x, 0xaa42aa42) } } + +func BenchmarkBytes(b *testing.B) { + x := new(Element).One() + b.ResetTimer() + for i := 0; i < b.N; i++ { + x.Bytes() + } +} diff --git a/crypto/internal/edwards25519/field/fe_generic.go b/crypto/internal/fips140/edwards25519/field/fe_generic.go similarity index 100% rename from crypto/internal/edwards25519/field/fe_generic.go rename to crypto/internal/fips140/edwards25519/field/fe_generic.go diff --git a/crypto/internal/edwards25519/field/fe_test.go b/crypto/internal/fips140/edwards25519/field/fe_test.go similarity index 88% rename from crypto/internal/edwards25519/field/fe_test.go rename to crypto/internal/fips140/edwards25519/field/fe_test.go index cf07662048c..1af7386dca6 100644 --- a/crypto/internal/edwards25519/field/fe_test.go +++ b/crypto/internal/fips140/edwards25519/field/fe_test.go @@ -434,55 +434,55 @@ func TestMult32(t *testing.T) { func TestSqrtRatio(t *testing.T) { // From draft-irtf-cfrg-ristretto255-decaf448-00, Appendix A.4. type test struct { - u, v string + u, v []byte wasSquare int - r string + r []byte } var tests = []test{ // If u is 0, the function is defined to return (0, TRUE), even if v // is zero. Note that where used in this package, the denominator v // is never zero. { - "0000000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - 1, "0000000000000000000000000000000000000000000000000000000000000000", + decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), + decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), + 1, decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), }, // 0/1 == 0² { - "0000000000000000000000000000000000000000000000000000000000000000", - "0100000000000000000000000000000000000000000000000000000000000000", - 1, "0000000000000000000000000000000000000000000000000000000000000000", + decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), + decodeHex("0100000000000000000000000000000000000000000000000000000000000000"), + 1, decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), }, // If u is non-zero and v is zero, defined to return (0, FALSE). { - "0100000000000000000000000000000000000000000000000000000000000000", - "0000000000000000000000000000000000000000000000000000000000000000", - 0, "0000000000000000000000000000000000000000000000000000000000000000", + decodeHex("0100000000000000000000000000000000000000000000000000000000000000"), + decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), + 0, decodeHex("0000000000000000000000000000000000000000000000000000000000000000"), }, // 2/1 is not square in this field. { - "0200000000000000000000000000000000000000000000000000000000000000", - "0100000000000000000000000000000000000000000000000000000000000000", - 0, "3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54", + decodeHex("0200000000000000000000000000000000000000000000000000000000000000"), + decodeHex("0100000000000000000000000000000000000000000000000000000000000000"), + 0, decodeHex("3c5ff1b5d8e4113b871bd052f9e7bcd0582804c266ffb2d4f4203eb07fdb7c54"), }, // 4/1 == 2² { - "0400000000000000000000000000000000000000000000000000000000000000", - "0100000000000000000000000000000000000000000000000000000000000000", - 1, "0200000000000000000000000000000000000000000000000000000000000000", + decodeHex("0400000000000000000000000000000000000000000000000000000000000000"), + decodeHex("0100000000000000000000000000000000000000000000000000000000000000"), + 1, decodeHex("0200000000000000000000000000000000000000000000000000000000000000"), }, // 1/4 == (2⁻¹)² == (2^(p-2))² per Euler's theorem { - "0100000000000000000000000000000000000000000000000000000000000000", - "0400000000000000000000000000000000000000000000000000000000000000", - 1, "f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f", + decodeHex("0100000000000000000000000000000000000000000000000000000000000000"), + decodeHex("0400000000000000000000000000000000000000000000000000000000000000"), + 1, decodeHex("f6ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3f"), }, } for i, tt := range tests { - u, _ := new(Element).SetBytes(decodeHex(tt.u)) - v, _ := new(Element).SetBytes(decodeHex(tt.v)) - want, _ := new(Element).SetBytes(decodeHex(tt.r)) + u, _ := new(Element).SetBytes(tt.u) + v, _ := new(Element).SetBytes(tt.v) + want, _ := new(Element).SetBytes(tt.r) got, wasSquare := new(Element).SqrtRatio(u, v) if got.Equal(want) == 0 || wasSquare != tt.wasSquare { t.Errorf("%d: got (%v, %v), want (%v, %v)", i, got, wasSquare, want, tt.wasSquare) diff --git a/crypto/internal/edwards25519/scalar.go b/crypto/internal/fips140/edwards25519/scalar.go similarity index 93% rename from crypto/internal/edwards25519/scalar.go rename to crypto/internal/fips140/edwards25519/scalar.go index 17f4d58506c..2b69aee4988 100644 --- a/crypto/internal/edwards25519/scalar.go +++ b/crypto/internal/fips140/edwards25519/scalar.go @@ -6,7 +6,9 @@ package edwards25519 import ( "errors" - "github.com/runZeroInc/excrypto/internal/byteorder" + "math/bits" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" ) // A Scalar is an integer modulo @@ -179,15 +181,23 @@ func isReduced(s []byte) bool { return false } - for i := len(s) - 1; i >= 0; i-- { - switch { - case s[i] > scalarMinusOneBytes[i]: - return false - case s[i] < scalarMinusOneBytes[i]: - return true - } - } - return true + s0 := byteorder.LEUint64(s[:8]) + s1 := byteorder.LEUint64(s[8:16]) + s2 := byteorder.LEUint64(s[16:24]) + s3 := byteorder.LEUint64(s[24:]) + + l0 := byteorder.LEUint64(scalarMinusOneBytes[:8]) + l1 := byteorder.LEUint64(scalarMinusOneBytes[8:16]) + l2 := byteorder.LEUint64(scalarMinusOneBytes[16:24]) + l3 := byteorder.LEUint64(scalarMinusOneBytes[24:]) + + // Do a constant time subtraction chain scalarMinusOneBytes - s. If there is + // a borrow at the end, then s > scalarMinusOneBytes. + _, b := bits.Sub64(l0, s0, 0) + _, b = bits.Sub64(l1, s1, b) + _, b = bits.Sub64(l2, s2, b) + _, b = bits.Sub64(l3, s3, b) + return b == 0 } // SetBytesWithClamping applies the buffer pruning described in RFC 8032, @@ -271,7 +281,7 @@ func (s *Scalar) nonAdjacentForm(w uint) [256]int8 { var digits [5]uint64 for i := 0; i < 4; i++ { - digits[i] = byteorder.LeUint64(b[i*8:]) + digits[i] = byteorder.LEUint64(b[i*8:]) } width := uint64(1 << w) diff --git a/crypto/internal/edwards25519/scalar_alias_test.go b/crypto/internal/fips140/edwards25519/scalar_alias_test.go similarity index 100% rename from crypto/internal/edwards25519/scalar_alias_test.go rename to crypto/internal/fips140/edwards25519/scalar_alias_test.go diff --git a/crypto/internal/edwards25519/scalar_fiat.go b/crypto/internal/fips140/edwards25519/scalar_fiat.go similarity index 100% rename from crypto/internal/edwards25519/scalar_fiat.go rename to crypto/internal/fips140/edwards25519/scalar_fiat.go diff --git a/crypto/internal/edwards25519/scalar_test.go b/crypto/internal/fips140/edwards25519/scalar_test.go similarity index 92% rename from crypto/internal/edwards25519/scalar_test.go rename to crypto/internal/fips140/edwards25519/scalar_test.go index 05551ef7711..76e920a2feb 100644 --- a/crypto/internal/edwards25519/scalar_test.go +++ b/crypto/internal/fips140/edwards25519/scalar_test.go @@ -26,7 +26,7 @@ func quickCheckConfig(slowScale int) *quick.Config { var scOneBytes = [32]byte{1} var scOne, _ = new(Scalar).SetCanonicalBytes(scOneBytes[:]) -var scMinusOne, _ = new(Scalar).SetCanonicalBytes(scalarMinusOneBytes[:]) +var scMinusOne = new(Scalar).Subtract(new(Scalar), scOne) // Generate returns a valid (reduced modulo l) Scalar with a distribution // weighted towards high, low, and edge values. @@ -38,7 +38,7 @@ func (Scalar) Generate(rand *mathrand.Rand, size int) reflect.Value { case diceRoll == 1: s = scOneBytes case diceRoll == 2: - s = scalarMinusOneBytes + s = [32]byte(scMinusOne.Bytes()) case diceRoll < 5: // Generate a low scalar in [0, 2^125). rand.Read(s[:16]) @@ -96,16 +96,29 @@ func TestScalarSetCanonicalBytes(t *testing.T) { t.Errorf("failed scalar->bytes->scalar round-trip: %v", err) } - b := scalarMinusOneBytes - b[31] += 1 - s := scOne - if out, err := s.SetCanonicalBytes(b[:]); err == nil { - t.Errorf("SetCanonicalBytes worked on a non-canonical value") - } else if s != scOne { - t.Errorf("SetCanonicalBytes modified its receiver") - } else if out != nil { - t.Errorf("SetCanonicalBytes did not return nil with an error") + expectReject := func(b []byte) { + t.Helper() + s := scOne + if out, err := s.SetCanonicalBytes(b[:]); err == nil { + t.Errorf("SetCanonicalBytes worked on a non-canonical value") + } else if s != scOne { + t.Errorf("SetCanonicalBytes modified its receiver") + } else if out != nil { + t.Errorf("SetCanonicalBytes did not return nil with an error") + } } + + b := scMinusOne.Bytes() + b[0] += 1 + expectReject(b) + + b = scMinusOne.Bytes() + b[31] += 1 + expectReject(b) + + b = scMinusOne.Bytes() + b[31] |= 0b1000_0000 + expectReject(b) } func TestScalarSetUniformBytes(t *testing.T) { diff --git a/crypto/internal/edwards25519/scalarmult.go b/crypto/internal/fips140/edwards25519/scalarmult.go similarity index 100% rename from crypto/internal/edwards25519/scalarmult.go rename to crypto/internal/fips140/edwards25519/scalarmult.go diff --git a/crypto/internal/edwards25519/scalarmult_test.go b/crypto/internal/fips140/edwards25519/scalarmult_test.go similarity index 100% rename from crypto/internal/edwards25519/scalarmult_test.go rename to crypto/internal/fips140/edwards25519/scalarmult_test.go diff --git a/crypto/internal/edwards25519/tables.go b/crypto/internal/fips140/edwards25519/tables.go similarity index 98% rename from crypto/internal/edwards25519/tables.go rename to crypto/internal/fips140/edwards25519/tables.go index 8035cdb9013..37ab515dca2 100644 --- a/crypto/internal/edwards25519/tables.go +++ b/crypto/internal/fips140/edwards25519/tables.go @@ -5,7 +5,7 @@ package edwards25519 import ( - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" ) // A dynamic lookup table for variable-base, constant-time scalar muls. diff --git a/crypto/internal/edwards25519/tables_test.go b/crypto/internal/fips140/edwards25519/tables_test.go similarity index 100% rename from crypto/internal/edwards25519/tables_test.go rename to crypto/internal/fips140/edwards25519/tables_test.go diff --git a/crypto/internal/fips140/fips140.go b/crypto/internal/fips140/fips140.go new file mode 100644 index 00000000000..4cf18822bef --- /dev/null +++ b/crypto/internal/fips140/fips140.go @@ -0,0 +1,68 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140 + +import ( + "errors" + "runtime" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" +) + +var Enabled bool + +var debug bool + +func init() { + v := godebug.Value("#fips140") + switch v { + case "on", "only": + Enabled = true + case "debug": + Enabled = true + debug = true + case "off", "": + default: + panic("fips140: unknown GODEBUG setting fips140=" + v) + } +} + +// Supported returns an error if FIPS 140-3 mode can't be enabled. +func Supported() error { + // Keep this in sync with fipsSupported in cmd/dist/test.go. + + // ASAN disapproves of reading swaths of global memory in fips140/check. + // One option would be to expose runtime.asanunpoison through + // crypto/internal/fips140deps and then call it to unpoison the range + // before reading it, but it is unclear whether that would then cause + // false negatives. For now, FIPS+ASAN doesn't need to work. + if asanEnabled { + return errors.New("FIPS 140-3 mode is incompatible with ASAN") + } + + // See EnableFIPS in cmd/internal/obj/fips.go for commentary. + switch { + case runtime.GOARCH == "wasm", + runtime.GOOS == "windows" && runtime.GOARCH == "386", + runtime.GOOS == "windows" && runtime.GOARCH == "arm", + runtime.GOOS == "openbsd", // due to -fexecute-only, see #70880 + runtime.GOOS == "aix": + return errors.New("FIPS 140-3 mode is not supported on " + runtime.GOOS + "-" + runtime.GOARCH) + } + + if boringEnabled { + return errors.New("FIPS 140-3 mode is incompatible with GOEXPERIMENT=boringcrypto") + } + + return nil +} + +func Name() string { + return "Go Cryptographic Module" +} + +func Version() string { + return "v1.0" +} diff --git a/crypto/internal/fips140/hash.go b/crypto/internal/fips140/hash.go new file mode 100644 index 00000000000..bc6c7ca2f5f --- /dev/null +++ b/crypto/internal/fips140/hash.go @@ -0,0 +1,32 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140 + +import "io" + +// Hash is the common interface implemented by all hash functions. It is a copy +// of [hash.Hash] from the standard library, to avoid depending on security +// definitions from outside of the module. +type Hash interface { + // Write (via the embedded io.Writer interface) adds more data to the + // running hash. It never returns an error. + io.Writer + + // Sum appends the current hash to b and returns the resulting slice. + // It does not change the underlying hash state. + Sum(b []byte) []byte + + // Reset resets the Hash to its initial state. + Reset() + + // Size returns the number of bytes Sum will return. + Size() int + + // BlockSize returns the hash's underlying block size. + // The Write method must be able to accept any amount + // of data, but it may operate more efficiently if all writes + // are a multiple of the block size. + BlockSize() int +} diff --git a/crypto/internal/fips140/hkdf/cast.go b/crypto/internal/fips140/hkdf/cast.go new file mode 100644 index 00000000000..cd39865ce5c --- /dev/null +++ b/crypto/internal/fips140/hkdf/cast.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hkdf + +import ( + "bytes" + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" +) + +func init() { + fips140.CAST("HKDF-SHA2-256", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + want := []byte{ + 0xb6, 0x53, 0x00, 0x5b, 0x51, 0x6d, 0x2b, 0xc9, + 0x4a, 0xe4, 0xf9, 0x51, 0x73, 0x1f, 0x71, 0x21, + 0xa6, 0xc1, 0xde, 0x42, 0x4f, 0x2c, 0x99, 0x60, + 0x64, 0xdb, 0x66, 0x3e, 0xec, 0xa6, 0x37, 0xff, + } + got := Key(sha256.New, input, input, string(input), len(want)) + if !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/hkdf/hkdf.go b/crypto/internal/fips140/hkdf/hkdf.go new file mode 100644 index 00000000000..8a5ece9ae07 --- /dev/null +++ b/crypto/internal/fips140/hkdf/hkdf.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hkdf + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" +) + +func Extract[H fips140.Hash](h func() H, secret, salt []byte) []byte { + if len(secret) < 112/8 { + fips140.RecordNonApproved() + } + if salt == nil { + salt = make([]byte, h().Size()) + } + extractor := hmac.New(h, salt) + hmac.MarkAsUsedInKDF(extractor) + extractor.Write(secret) + + return extractor.Sum(nil) +} + +func Expand[H fips140.Hash](h func() H, pseudorandomKey []byte, info string, keyLen int) []byte { + out := make([]byte, 0, keyLen) + expander := hmac.New(h, pseudorandomKey) + hmac.MarkAsUsedInKDF(expander) + var counter uint8 + var buf []byte + + for len(out) < keyLen { + counter++ + if counter == 0 { + panic("hkdf: counter overflow") + } + if counter > 1 { + expander.Reset() + } + expander.Write(buf) + expander.Write([]byte(info)) + expander.Write([]byte{counter}) + buf = expander.Sum(buf[:0]) + remain := keyLen - len(out) + remain = min(remain, len(buf)) + out = append(out, buf[:remain]...) + } + + return out +} + +func Key[H fips140.Hash](h func() H, secret, salt []byte, info string, keyLen int) []byte { + prk := Extract(h, secret, salt) + return Expand(h, prk, info, keyLen) +} diff --git a/crypto/internal/fips140/hmac/cast.go b/crypto/internal/fips140/hmac/cast.go new file mode 100644 index 00000000000..bef36e79df1 --- /dev/null +++ b/crypto/internal/fips140/hmac/cast.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package hmac + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" +) + +func init() { + fips140.CAST("HMAC-SHA2-256", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + want := []byte{ + 0xf0, 0x8d, 0x82, 0x8d, 0x4c, 0x9e, 0xad, 0x3d, + 0xdc, 0x12, 0x9c, 0x4e, 0x70, 0xc4, 0x19, 0x2a, + 0x4f, 0x12, 0x73, 0x23, 0x73, 0x77, 0x66, 0x05, + 0x10, 0xee, 0x57, 0x6b, 0x3a, 0xc7, 0x14, 0x41, + } + h := New(sha256.New, input) + h.Write(input) + h.Write(input) + if got := h.Sum(nil); !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/hmac/hmac.go b/crypto/internal/fips140/hmac/hmac.go new file mode 100644 index 00000000000..2ae2774415a --- /dev/null +++ b/crypto/internal/fips140/hmac/hmac.go @@ -0,0 +1,172 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package hmac implements HMAC according to [FIPS 198-1]. +// +// [FIPS 198-1]: https://doi.org/10.6028/NIST.FIPS.198-1 +package hmac + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" +) + +// key is zero padded to the block size of the hash function +// ipad = 0x36 byte repeated for key length +// opad = 0x5c byte repeated for key length +// hmac = H([key ^ opad] H([key ^ ipad] text)) + +// marshalable is the combination of encoding.BinaryMarshaler and +// encoding.BinaryUnmarshaler. Their method definitions are repeated here to +// avoid a dependency on the encoding package. +type marshalable interface { + MarshalBinary() ([]byte, error) + UnmarshalBinary([]byte) error +} + +type HMAC struct { + opad, ipad []byte + outer, inner fips140.Hash + + // If marshaled is true, then opad and ipad do not contain a padded + // copy of the key, but rather the marshaled state of outer/inner after + // opad/ipad has been fed into it. + marshaled bool + + // forHKDF and keyLen are stored to inform the service indicator decision. + forHKDF bool + keyLen int +} + +func (h *HMAC) Sum(in []byte) []byte { + // Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for + // legacy use (i.e. verification only) and we don't support that. However, + // HKDF uses the HMAC key for the salt, which is allowed to be shorter. + if h.keyLen < 112/8 && !h.forHKDF { + fips140.RecordNonApproved() + } + switch h.inner.(type) { + case *sha256.Digest, *sha512.Digest, *sha3.Digest: + default: + fips140.RecordNonApproved() + } + + origLen := len(in) + in = h.inner.Sum(in) + + if h.marshaled { + if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil { + panic(err) + } + } else { + h.outer.Reset() + h.outer.Write(h.opad) + } + h.outer.Write(in[origLen:]) + return h.outer.Sum(in[:origLen]) +} + +func (h *HMAC) Write(p []byte) (n int, err error) { + return h.inner.Write(p) +} + +func (h *HMAC) Size() int { return h.outer.Size() } +func (h *HMAC) BlockSize() int { return h.inner.BlockSize() } + +func (h *HMAC) Reset() { + if h.marshaled { + if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil { + panic(err) + } + return + } + + h.inner.Reset() + h.inner.Write(h.ipad) + + // If the underlying hash is marshalable, we can save some time by saving a + // copy of the hash state now, and restoring it on future calls to Reset and + // Sum instead of writing ipad/opad every time. + // + // We do this on Reset to avoid slowing down the common single-use case. + // + // This is allowed by FIPS 198-1, Section 6: "Conceptually, the intermediate + // results of the compression function on the B-byte blocks (K0 ⊕ ipad) and + // (K0 ⊕ opad) can be precomputed once, at the time of generation of the key + // K, or before its first use. These intermediate results can be stored and + // then used to initialize H each time that a message needs to be + // authenticated using the same key. [...] These stored intermediate values + // shall be treated and protected in the same manner as secret keys." + marshalableInner, innerOK := h.inner.(marshalable) + if !innerOK { + return + } + marshalableOuter, outerOK := h.outer.(marshalable) + if !outerOK { + return + } + + imarshal, err := marshalableInner.MarshalBinary() + if err != nil { + return + } + + h.outer.Reset() + h.outer.Write(h.opad) + omarshal, err := marshalableOuter.MarshalBinary() + if err != nil { + return + } + + // Marshaling succeeded; save the marshaled state for later + h.ipad = imarshal + h.opad = omarshal + h.marshaled = true +} + +// New returns a new HMAC hash using the given [fips140.Hash] type and key. +func New[H fips140.Hash](h func() H, key []byte) *HMAC { + hm := &HMAC{keyLen: len(key)} + hm.outer = h() + hm.inner = h() + unique := true + func() { + defer func() { + // The comparison might panic if the underlying types are not comparable. + _ = recover() + }() + if hm.outer == hm.inner { + unique = false + } + }() + if !unique { + panic("crypto/hmac: hash generation function does not produce unique values") + } + blocksize := hm.inner.BlockSize() + hm.ipad = make([]byte, blocksize) + hm.opad = make([]byte, blocksize) + if len(key) > blocksize { + // If key is too big, hash it. + hm.outer.Write(key) + key = hm.outer.Sum(nil) + } + copy(hm.ipad, key) + copy(hm.opad, key) + for i := range hm.ipad { + hm.ipad[i] ^= 0x36 + } + for i := range hm.opad { + hm.opad[i] ^= 0x5c + } + hm.inner.Write(hm.ipad) + + return hm +} + +// MarkAsUsedInKDF records that this HMAC instance is used as part of a KDF. +func MarkAsUsedInKDF(h *HMAC) { + h.forHKDF = true +} diff --git a/crypto/internal/fips140/indicator.go b/crypto/internal/fips140/indicator.go new file mode 100644 index 00000000000..229e0715e73 --- /dev/null +++ b/crypto/internal/fips140/indicator.go @@ -0,0 +1,62 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140 + +import _ "unsafe" // for go:linkname + +// The service indicator lets users of the module query whether invoked services +// are approved. Three states are stored in a per-goroutine value by the +// runtime. The indicator starts at indicatorUnset after a reset. Invoking an +// approved service transitions to indicatorTrue. Invoking a non-approved +// service transitions to indicatorFalse, and it can't leave that state until a +// reset. The idea is that functions can "delegate" checks to inner functions, +// and if there's anything non-approved in the stack, the final result is +// negative. Finally, we expose indicatorUnset as negative to the user, so that +// we don't need to explicitly annotate fully non-approved services. + +//go:linkname getIndicator crypto/internal/fips140.getIndicator +func getIndicator() uint8 + +//go:linkname setIndicator crypto/internal/fips140.setIndicator +func setIndicator(uint8) + +const ( + indicatorUnset uint8 = iota + indicatorFalse + indicatorTrue +) + +// ResetServiceIndicator clears the service indicator for the running goroutine. +func ResetServiceIndicator() { + setIndicator(indicatorUnset) +} + +// ServiceIndicator returns true if and only if all services invoked by this +// goroutine since the last ResetServiceIndicator call are approved. +// +// If ResetServiceIndicator was not called before by this goroutine, its return +// value is undefined. +func ServiceIndicator() bool { + return getIndicator() == indicatorTrue +} + +// RecordApproved is an internal function that records the use of an approved +// service. It does not override RecordNonApproved calls in the same span. +// +// It should be called by exposed functions that perform a whole cryptographic +// alrgorithm (e.g. by Sum, not by New, unless a cryptographic Instantiate +// algorithm is performed) and should be called after any checks that may cause +// the function to error out or panic. +func RecordApproved() { + if getIndicator() == indicatorUnset { + setIndicator(indicatorTrue) + } +} + +// RecordNonApproved is an internal function that records the use of a +// non-approved service. It overrides any RecordApproved calls in the same span. +func RecordNonApproved() { + setIndicator(indicatorFalse) +} diff --git a/crypto/internal/fips140/mlkem/cast.go b/crypto/internal/fips140/mlkem/cast.go new file mode 100644 index 00000000000..8d2ed104cd2 --- /dev/null +++ b/crypto/internal/fips140/mlkem/cast.go @@ -0,0 +1,55 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mlkem + +import ( + "bytes" + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func init() { + fips140.CAST("ML-KEM-768", func() error { + var d = &[32]byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + var z = &[32]byte{ + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, + } + var m = &[32]byte{ + 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, + } + var K = []byte{ + 0x55, 0x01, 0xfc, 0x52, 0x3b, 0x74, 0x5f, 0x41, + 0x76, 0x2a, 0x18, 0x8d, 0xe4, 0x4a, 0x59, 0xb9, + 0x20, 0xf4, 0x30, 0x14, 0x62, 0x04, 0xee, 0x4e, + 0x79, 0x37, 0x32, 0x39, 0x6d, 0xf7, 0xaa, 0x48, + } + dk := &DecapsulationKey768{} + kemKeyGen(dk, d, z) + ek := dk.EncapsulationKey() + Ke, c := ek.EncapsulateInternal(m) + Kd, err := dk.Decapsulate(c) + if err != nil { + return err + } + if !bytes.Equal(Ke, K) || !bytes.Equal(Kd, K) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/mlkem/field.go b/crypto/internal/fips140/mlkem/field.go new file mode 100644 index 00000000000..007d87ea0e6 --- /dev/null +++ b/crypto/internal/fips140/mlkem/field.go @@ -0,0 +1,551 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mlkem + +import ( + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// fieldElement is an integer modulo q, an element of ℤ_q. It is always reduced. +type fieldElement uint16 + +// fieldCheckReduced checks that a value a is < q. +func fieldCheckReduced(a uint16) (fieldElement, error) { + if a >= q { + return 0, errors.New("unreduced field element") + } + return fieldElement(a), nil +} + +// fieldReduceOnce reduces a value a < 2q. +func fieldReduceOnce(a uint16) fieldElement { + x := a - q + // If x underflowed, then x >= 2¹⁶ - q > 2¹⁵, so the top bit is set. + x += (x >> 15) * q + return fieldElement(x) +} + +func fieldAdd(a, b fieldElement) fieldElement { + x := uint16(a + b) + return fieldReduceOnce(x) +} + +func fieldSub(a, b fieldElement) fieldElement { + x := uint16(a - b + q) + return fieldReduceOnce(x) +} + +const ( + barrettMultiplier = 5039 // 2¹² * 2¹² / q + barrettShift = 24 // log₂(2¹² * 2¹²) +) + +// fieldReduce reduces a value a < 2q² using Barrett reduction, to avoid +// potentially variable-time division. +func fieldReduce(a uint32) fieldElement { + quotient := uint32((uint64(a) * barrettMultiplier) >> barrettShift) + return fieldReduceOnce(uint16(a - quotient*q)) +} + +func fieldMul(a, b fieldElement) fieldElement { + x := uint32(a) * uint32(b) + return fieldReduce(x) +} + +// fieldMulSub returns a * (b - c). This operation is fused to save a +// fieldReduceOnce after the subtraction. +func fieldMulSub(a, b, c fieldElement) fieldElement { + x := uint32(a) * uint32(b-c+q) + return fieldReduce(x) +} + +// fieldAddMul returns a * b + c * d. This operation is fused to save a +// fieldReduceOnce and a fieldReduce. +func fieldAddMul(a, b, c, d fieldElement) fieldElement { + x := uint32(a) * uint32(b) + x += uint32(c) * uint32(d) + return fieldReduce(x) +} + +// compress maps a field element uniformly to the range 0 to 2ᵈ-1, according to +// FIPS 203, Definition 4.7. +func compress(x fieldElement, d uint8) uint16 { + // We want to compute (x * 2ᵈ) / q, rounded to nearest integer, with 1/2 + // rounding up (see FIPS 203, Section 2.3). + + // Barrett reduction produces a quotient and a remainder in the range [0, 2q), + // such that dividend = quotient * q + remainder. + dividend := uint32(x) << d // x * 2ᵈ + quotient := uint32(uint64(dividend) * barrettMultiplier >> barrettShift) + remainder := dividend - quotient*q + + // Since the remainder is in the range [0, 2q), not [0, q), we need to + // portion it into three spans for rounding. + // + // [ 0, q/2 ) -> round to 0 + // [ q/2, q + q/2 ) -> round to 1 + // [ q + q/2, 2q ) -> round to 2 + // + // We can convert that to the following logic: add 1 if remainder > q/2, + // then add 1 again if remainder > q + q/2. + // + // Note that if remainder > x, then ⌊x⌋ - remainder underflows, and the top + // bit of the difference will be set. + quotient += (q/2 - remainder) >> 31 & 1 + quotient += (q + q/2 - remainder) >> 31 & 1 + + // quotient might have overflowed at this point, so reduce it by masking. + var mask uint32 = (1 << d) - 1 + return uint16(quotient & mask) +} + +// decompress maps a number x between 0 and 2ᵈ-1 uniformly to the full range of +// field elements, according to FIPS 203, Definition 4.8. +func decompress(y uint16, d uint8) fieldElement { + // We want to compute (y * q) / 2ᵈ, rounded to nearest integer, with 1/2 + // rounding up (see FIPS 203, Section 2.3). + + dividend := uint32(y) * q + quotient := dividend >> d // (y * q) / 2ᵈ + + // The d'th least-significant bit of the dividend (the most significant bit + // of the remainder) is 1 for the top half of the values that divide to the + // same quotient, which are the ones that round up. + quotient += dividend >> (d - 1) & 1 + + // quotient is at most (2¹¹-1) * q / 2¹¹ + 1 = 3328, so it didn't overflow. + return fieldElement(quotient) +} + +// ringElement is a polynomial, an element of R_q, represented as an array +// according to FIPS 203, Section 2.4.4. +type ringElement [n]fieldElement + +// polyAdd adds two ringElements or nttElements. +func polyAdd[T ~[n]fieldElement](a, b T) (s T) { + for i := range s { + s[i] = fieldAdd(a[i], b[i]) + } + return s +} + +// polySub subtracts two ringElements or nttElements. +func polySub[T ~[n]fieldElement](a, b T) (s T) { + for i := range s { + s[i] = fieldSub(a[i], b[i]) + } + return s +} + +// polyByteEncode appends the 384-byte encoding of f to b. +// +// It implements ByteEncode₁₂, according to FIPS 203, Algorithm 5. +func polyByteEncode[T ~[n]fieldElement](b []byte, f T) []byte { + out, B := sliceForAppend(b, encodingSize12) + for i := 0; i < n; i += 2 { + x := uint32(f[i]) | uint32(f[i+1])<<12 + B[0] = uint8(x) + B[1] = uint8(x >> 8) + B[2] = uint8(x >> 16) + B = B[3:] + } + return out +} + +// polyByteDecode decodes the 384-byte encoding of a polynomial, checking that +// all the coefficients are properly reduced. This fulfills the "Modulus check" +// step of ML-KEM Encapsulation. +// +// It implements ByteDecode₁₂, according to FIPS 203, Algorithm 6. +func polyByteDecode[T ~[n]fieldElement](b []byte) (T, error) { + if len(b) != encodingSize12 { + return T{}, errors.New("mlkem: invalid encoding length") + } + var f T + for i := 0; i < n; i += 2 { + d := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 + const mask12 = 0b1111_1111_1111 + var err error + if f[i], err = fieldCheckReduced(uint16(d & mask12)); err != nil { + return T{}, errors.New("mlkem: invalid polynomial encoding") + } + if f[i+1], err = fieldCheckReduced(uint16(d >> 12)); err != nil { + return T{}, errors.New("mlkem: invalid polynomial encoding") + } + b = b[3:] + } + return f, nil +} + +// sliceForAppend takes a slice and a requested number of bytes. It returns a +// slice with the contents of the given slice followed by that many bytes and a +// second slice that aliases into it and contains only the extra bytes. If the +// original slice has sufficient capacity then no allocation is performed. +func sliceForAppend(in []byte, n int) (head, tail []byte) { + if total := len(in) + n; cap(in) >= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} + +// ringCompressAndEncode1 appends a 32-byte encoding of a ring element to s, +// compressing one coefficients per bit. +// +// It implements Compress₁, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₁, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode1(s []byte, f ringElement) []byte { + s, b := sliceForAppend(s, encodingSize1) + for i := range b { + b[i] = 0 + } + for i := range f { + b[i/8] |= uint8(compress(f[i], 1) << (i % 8)) + } + return s +} + +// ringDecodeAndDecompress1 decodes a 32-byte slice to a ring element where each +// bit is mapped to 0 or ⌈q/2⌋. +// +// It implements ByteDecode₁, according to FIPS 203, Algorithm 6, +// followed by Decompress₁, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress1(b *[encodingSize1]byte) ringElement { + var f ringElement + for i := range f { + b_i := b[i/8] >> (i % 8) & 1 + const halfQ = (q + 1) / 2 // ⌈q/2⌋, rounded up per FIPS 203, Section 2.3 + f[i] = fieldElement(b_i) * halfQ // 0 decompresses to 0, and 1 to ⌈q/2⌋ + } + return f +} + +// ringCompressAndEncode4 appends a 128-byte encoding of a ring element to s, +// compressing two coefficients per byte. +// +// It implements Compress₄, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₄, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode4(s []byte, f ringElement) []byte { + s, b := sliceForAppend(s, encodingSize4) + for i := 0; i < n; i += 2 { + b[i/2] = uint8(compress(f[i], 4) | compress(f[i+1], 4)<<4) + } + return s +} + +// ringDecodeAndDecompress4 decodes a 128-byte encoding of a ring element where +// each four bits are mapped to an equidistant distribution. +// +// It implements ByteDecode₄, according to FIPS 203, Algorithm 6, +// followed by Decompress₄, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress4(b *[encodingSize4]byte) ringElement { + var f ringElement + for i := 0; i < n; i += 2 { + f[i] = fieldElement(decompress(uint16(b[i/2]&0b1111), 4)) + f[i+1] = fieldElement(decompress(uint16(b[i/2]>>4), 4)) + } + return f +} + +// ringCompressAndEncode10 appends a 320-byte encoding of a ring element to s, +// compressing four coefficients per five bytes. +// +// It implements Compress₁₀, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₁₀, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode10(s []byte, f ringElement) []byte { + s, b := sliceForAppend(s, encodingSize10) + for i := 0; i < n; i += 4 { + var x uint64 + x |= uint64(compress(f[i], 10)) + x |= uint64(compress(f[i+1], 10)) << 10 + x |= uint64(compress(f[i+2], 10)) << 20 + x |= uint64(compress(f[i+3], 10)) << 30 + b[0] = uint8(x) + b[1] = uint8(x >> 8) + b[2] = uint8(x >> 16) + b[3] = uint8(x >> 24) + b[4] = uint8(x >> 32) + b = b[5:] + } + return s +} + +// ringDecodeAndDecompress10 decodes a 320-byte encoding of a ring element where +// each ten bits are mapped to an equidistant distribution. +// +// It implements ByteDecode₁₀, according to FIPS 203, Algorithm 6, +// followed by Decompress₁₀, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress10(bb *[encodingSize10]byte) ringElement { + b := bb[:] + var f ringElement + for i := 0; i < n; i += 4 { + x := uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 + b = b[5:] + f[i] = fieldElement(decompress(uint16(x>>0&0b11_1111_1111), 10)) + f[i+1] = fieldElement(decompress(uint16(x>>10&0b11_1111_1111), 10)) + f[i+2] = fieldElement(decompress(uint16(x>>20&0b11_1111_1111), 10)) + f[i+3] = fieldElement(decompress(uint16(x>>30&0b11_1111_1111), 10)) + } + return f +} + +// ringCompressAndEncode appends an encoding of a ring element to s, +// compressing each coefficient to d bits. +// +// It implements Compress, according to FIPS 203, Definition 4.7, +// followed by ByteEncode, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode(s []byte, f ringElement, d uint8) []byte { + var b byte + var bIdx uint8 + for i := 0; i < n; i++ { + c := compress(f[i], d) + var cIdx uint8 + for cIdx < d { + b |= byte(c>>cIdx) << bIdx + bits := min(8-bIdx, d-cIdx) + bIdx += bits + cIdx += bits + if bIdx == 8 { + s = append(s, b) + b = 0 + bIdx = 0 + } + } + } + if bIdx != 0 { + panic("mlkem: internal error: bitsFilled != 0") + } + return s +} + +// ringDecodeAndDecompress decodes an encoding of a ring element where +// each d bits are mapped to an equidistant distribution. +// +// It implements ByteDecode, according to FIPS 203, Algorithm 6, +// followed by Decompress, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress(b []byte, d uint8) ringElement { + var f ringElement + var bIdx uint8 + for i := 0; i < n; i++ { + var c uint16 + var cIdx uint8 + for cIdx < d { + c |= uint16(b[0]>>bIdx) << cIdx + c &= (1 << d) - 1 + bits := min(8-bIdx, d-cIdx) + bIdx += bits + cIdx += bits + if bIdx == 8 { + b = b[1:] + bIdx = 0 + } + } + f[i] = fieldElement(decompress(c, d)) + } + if len(b) != 0 { + panic("mlkem: internal error: leftover bytes") + } + return f +} + +// ringCompressAndEncode5 appends a 160-byte encoding of a ring element to s, +// compressing eight coefficients per five bytes. +// +// It implements Compress₅, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₅, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode5(s []byte, f ringElement) []byte { + return ringCompressAndEncode(s, f, 5) +} + +// ringDecodeAndDecompress5 decodes a 160-byte encoding of a ring element where +// each five bits are mapped to an equidistant distribution. +// +// It implements ByteDecode₅, according to FIPS 203, Algorithm 6, +// followed by Decompress₅, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress5(bb *[encodingSize5]byte) ringElement { + return ringDecodeAndDecompress(bb[:], 5) +} + +// ringCompressAndEncode11 appends a 352-byte encoding of a ring element to s, +// compressing eight coefficients per eleven bytes. +// +// It implements Compress₁₁, according to FIPS 203, Definition 4.7, +// followed by ByteEncode₁₁, according to FIPS 203, Algorithm 5. +func ringCompressAndEncode11(s []byte, f ringElement) []byte { + return ringCompressAndEncode(s, f, 11) +} + +// ringDecodeAndDecompress11 decodes a 352-byte encoding of a ring element where +// each eleven bits are mapped to an equidistant distribution. +// +// It implements ByteDecode₁₁, according to FIPS 203, Algorithm 6, +// followed by Decompress₁₁, according to FIPS 203, Definition 4.8. +func ringDecodeAndDecompress11(bb *[encodingSize11]byte) ringElement { + return ringDecodeAndDecompress(bb[:], 11) +} + +// samplePolyCBD draws a ringElement from the special Dη distribution given a +// stream of random bytes generated by the PRF function, according to FIPS 203, +// Algorithm 8 and Definition 4.3. +func samplePolyCBD(s []byte, b byte) ringElement { + prf := sha3.NewShake256() + prf.Write(s) + prf.Write([]byte{b}) + B := make([]byte, 64*2) // η = 2 + prf.Read(B) + + // SamplePolyCBD simply draws four (2η) bits for each coefficient, and adds + // the first two and subtracts the last two. + + var f ringElement + for i := 0; i < n; i += 2 { + b := B[i/2] + b_7, b_6, b_5, b_4 := b>>7, b>>6&1, b>>5&1, b>>4&1 + b_3, b_2, b_1, b_0 := b>>3&1, b>>2&1, b>>1&1, b&1 + f[i] = fieldSub(fieldElement(b_0+b_1), fieldElement(b_2+b_3)) + f[i+1] = fieldSub(fieldElement(b_4+b_5), fieldElement(b_6+b_7)) + } + return f +} + +// nttElement is an NTT representation, an element of T_q, represented as an +// array according to FIPS 203, Section 2.4.4. +type nttElement [n]fieldElement + +// gammas are the values ζ^2BitRev7(i)+1 mod q for each index i, according to +// FIPS 203, Appendix A (with negative values reduced to positive). +var gammas = [128]fieldElement{17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, 2110, 1219, 2935, 394, 885, 2444, 2154, 1175} + +// nttMul multiplies two nttElements. +// +// It implements MultiplyNTTs, according to FIPS 203, Algorithm 11. +func nttMul(f, g nttElement) nttElement { + var h nttElement + // We use i += 2 for bounds check elimination. See https://go.dev/issue/66826. + for i := 0; i < 256; i += 2 { + a0, a1 := f[i], f[i+1] + b0, b1 := g[i], g[i+1] + h[i] = fieldAddMul(a0, b0, fieldMul(a1, b1), gammas[i/2]) + h[i+1] = fieldAddMul(a0, b1, a1, b0) + } + return h +} + +// zetas are the values ζ^BitRev7(k) mod q for each index k, according to FIPS +// 203, Appendix A. +var zetas = [128]fieldElement{1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, 2786, 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, 1197, 2304, 2277, 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, 2474, 3110, 1227, 910, 17, 2761, 583, 2649, 1637, 723, 2288, 1100, 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, 1703, 1651, 2789, 1789, 1847, 952, 1461, 2687, 939, 2308, 2437, 2388, 733, 2337, 268, 641, 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154} + +// ntt maps a ringElement to its nttElement representation. +// +// It implements NTT, according to FIPS 203, Algorithm 9. +func ntt(f ringElement) nttElement { + k := 1 + for len := 128; len >= 2; len /= 2 { + for start := 0; start < 256; start += 2 * len { + zeta := zetas[k] + k++ + // Bounds check elimination hint. + f, flen := f[start:start+len], f[start+len:start+len+len] + for j := 0; j < len; j++ { + t := fieldMul(zeta, flen[j]) + flen[j] = fieldSub(f[j], t) + f[j] = fieldAdd(f[j], t) + } + } + } + return nttElement(f) +} + +// inverseNTT maps a nttElement back to the ringElement it represents. +// +// It implements NTT⁻¹, according to FIPS 203, Algorithm 10. +func inverseNTT(f nttElement) ringElement { + k := 127 + for len := 2; len <= 128; len *= 2 { + for start := 0; start < 256; start += 2 * len { + zeta := zetas[k] + k-- + // Bounds check elimination hint. + f, flen := f[start:start+len], f[start+len:start+len+len] + for j := 0; j < len; j++ { + t := f[j] + f[j] = fieldAdd(t, flen[j]) + flen[j] = fieldMulSub(zeta, flen[j], t) + } + } + } + for i := range f { + f[i] = fieldMul(f[i], 3303) // 3303 = 128⁻¹ mod q + } + return ringElement(f) +} + +// sampleNTT draws a uniformly random nttElement from a stream of uniformly +// random bytes generated by the XOF function, according to FIPS 203, +// Algorithm 7. +func sampleNTT(rho []byte, ii, jj byte) nttElement { + B := sha3.NewShake128() + B.Write(rho) + B.Write([]byte{ii, jj}) + + // SampleNTT essentially draws 12 bits at a time from r, interprets them in + // little-endian, and rejects values higher than q, until it drew 256 + // values. (The rejection rate is approximately 19%.) + // + // To do this from a bytes stream, it draws three bytes at a time, and + // splits them into two uint16 appropriately masked. + // + // r₀ r₁ r₂ + // |- - - - - - - -|- - - - - - - -|- - - - - - - -| + // + // Uint16(r₀ || r₁) + // |- - - - - - - - - - - - - - - -| + // |- - - - - - - - - - - -| + // d₁ + // + // Uint16(r₁ || r₂) + // |- - - - - - - - - - - - - - - -| + // |- - - - - - - - - - - -| + // d₂ + // + // Note that in little-endian, the rightmost bits are the most significant + // bits (dropped with a mask) and the leftmost bits are the least + // significant bits (dropped with a right shift). + + var a nttElement + var j int // index into a + var buf [24]byte // buffered reads from B + off := len(buf) // index into buf, starts in a "buffer fully consumed" state + for { + if off >= len(buf) { + B.Read(buf[:]) + off = 0 + } + d1 := byteorder.LEUint16(buf[off:]) & 0b1111_1111_1111 + d2 := byteorder.LEUint16(buf[off+1:]) >> 4 + off += 3 + if d1 < q { + a[j] = fieldElement(d1) + j++ + } + if j >= len(a) { + break + } + if d2 < q { + a[j] = fieldElement(d2) + j++ + } + if j >= len(a) { + break + } + } + return a +} diff --git a/crypto/internal/fips140/mlkem/field_test.go b/crypto/internal/fips140/mlkem/field_test.go new file mode 100644 index 00000000000..ccd1cbda597 --- /dev/null +++ b/crypto/internal/fips140/mlkem/field_test.go @@ -0,0 +1,270 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mlkem + +import ( + "bytes" + "math/big" + mathrand "math/rand/v2" + "strconv" + "testing" + + "crypto/rand" +) + +func TestFieldReduce(t *testing.T) { + for a := uint32(0); a < 2*q*q; a++ { + got := fieldReduce(a) + exp := fieldElement(a % q) + if got != exp { + t.Fatalf("reduce(%d) = %d, expected %d", a, got, exp) + } + } +} + +func TestFieldAdd(t *testing.T) { + for a := fieldElement(0); a < q; a++ { + for b := fieldElement(0); b < q; b++ { + got := fieldAdd(a, b) + exp := (a + b) % q + if got != exp { + t.Fatalf("%d + %d = %d, expected %d", a, b, got, exp) + } + } + } +} + +func TestFieldSub(t *testing.T) { + for a := fieldElement(0); a < q; a++ { + for b := fieldElement(0); b < q; b++ { + got := fieldSub(a, b) + exp := (a - b + q) % q + if got != exp { + t.Fatalf("%d - %d = %d, expected %d", a, b, got, exp) + } + } + } +} + +func TestFieldMul(t *testing.T) { + for a := fieldElement(0); a < q; a++ { + for b := fieldElement(0); b < q; b++ { + got := fieldMul(a, b) + exp := fieldElement((uint32(a) * uint32(b)) % q) + if got != exp { + t.Fatalf("%d * %d = %d, expected %d", a, b, got, exp) + } + } + } +} + +func TestDecompressCompress(t *testing.T) { + for _, bits := range []uint8{1, 4, 10} { + for a := uint16(0); a < 1<= q { + t.Fatalf("decompress(%d, %d) = %d >= q", a, bits, f) + } + got := compress(f, bits) + if got != a { + t.Fatalf("compress(decompress(%d, %d), %d) = %d", a, bits, bits, got) + } + } + + for a := fieldElement(0); a < q; a++ { + c := compress(a, bits) + if c >= 1<= 2^bits", a, bits, c) + } + got := decompress(c, bits) + diff := min(a-got, got-a, a-got+q, got-a+q) + ceil := q / (1 << bits) + if diff > fieldElement(ceil) { + t.Fatalf("decompress(compress(%d, %d), %d) = %d (diff %d, max diff %d)", + a, bits, bits, got, diff, ceil) + } + } + } +} + +func CompressRat(x fieldElement, d uint8) uint16 { + if x >= q { + panic("x out of range") + } + if d <= 0 || d >= 12 { + panic("d out of range") + } + + precise := big.NewRat((1<= 1<= 12 { + panic("d out of range") + } + + precise := big.NewRat(q*int64(y), 1<>7 != 0 { + panic("not 7 bits") + } + var r uint8 + r |= n >> 6 & 0b0000_0001 + r |= n >> 4 & 0b0000_0010 + r |= n >> 2 & 0b0000_0100 + r |= n /**/ & 0b0000_1000 + r |= n << 2 & 0b0001_0000 + r |= n << 4 & 0b0010_0000 + r |= n << 6 & 0b0100_0000 + return r +} + +func TestZetas(t *testing.T) { + ζ := big.NewInt(17) + q := big.NewInt(q) + for k, zeta := range zetas { + // ζ^BitRev7(k) mod q + exp := new(big.Int).Exp(ζ, big.NewInt(int64(BitRev7(uint8(k)))), q) + if big.NewInt(int64(zeta)).Cmp(exp) != 0 { + t.Errorf("zetas[%d] = %v, expected %v", k, zeta, exp) + } + } +} + +func TestGammas(t *testing.T) { + ζ := big.NewInt(17) + q := big.NewInt(q) + for k, gamma := range gammas { + // ζ^2BitRev7(i)+1 + exp := new(big.Int).Exp(ζ, big.NewInt(int64(BitRev7(uint8(k)))*2+1), q) + if big.NewInt(int64(gamma)).Cmp(exp) != 0 { + t.Errorf("gammas[%d] = %v, expected %v", k, gamma, exp) + } + } +} diff --git a/crypto/internal/fips140/mlkem/generate1024.go b/crypto/internal/fips140/mlkem/generate1024.go new file mode 100644 index 00000000000..9e38ad00df9 --- /dev/null +++ b/crypto/internal/fips140/mlkem/generate1024.go @@ -0,0 +1,128 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore + +package main + +import ( + "flag" + "go/ast" + "go/format" + "go/parser" + "go/token" + "log" + "os" + "strings" +) + +var replacements = map[string]string{ + "k": "k1024", + + "CiphertextSize768": "CiphertextSize1024", + "EncapsulationKeySize768": "EncapsulationKeySize1024", + "decapsulationKeySize768": "decapsulationKeySize1024", + + "encryptionKey": "encryptionKey1024", + "decryptionKey": "decryptionKey1024", + + "EncapsulationKey768": "EncapsulationKey1024", + "NewEncapsulationKey768": "NewEncapsulationKey1024", + "parseEK": "parseEK1024", + + "kemEncaps": "kemEncaps1024", + "pkeEncrypt": "pkeEncrypt1024", + + "DecapsulationKey768": "DecapsulationKey1024", + "NewDecapsulationKey768": "NewDecapsulationKey1024", + "TestingOnlyNewDecapsulationKey768": "TestingOnlyNewDecapsulationKey1024", + "newKeyFromSeed": "newKeyFromSeed1024", + "TestingOnlyExpandedBytes768": "TestingOnlyExpandedBytes1024", + + "kemDecaps": "kemDecaps1024", + "pkeDecrypt": "pkeDecrypt1024", + + "GenerateKey768": "GenerateKey1024", + "GenerateKeyInternal768": "GenerateKeyInternal1024", + "generateKey": "generateKey1024", + + "kemKeyGen": "kemKeyGen1024", + "kemPCT": "kemPCT1024", + + "encodingSize4": "encodingSize5", + "encodingSize10": "encodingSize11", + "ringCompressAndEncode4": "ringCompressAndEncode5", + "ringCompressAndEncode10": "ringCompressAndEncode11", + "ringDecodeAndDecompress4": "ringDecodeAndDecompress5", + "ringDecodeAndDecompress10": "ringDecodeAndDecompress11", +} + +func main() { + inputFile := flag.String("input", "", "") + outputFile := flag.String("output", "", "") + flag.Parse() + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, *inputFile, nil, parser.SkipObjectResolution|parser.ParseComments) + if err != nil { + log.Fatal(err) + } + cmap := ast.NewCommentMap(fset, f, f.Comments) + + // Drop header comments. + cmap[ast.Node(f)] = nil + + // Remove top-level consts used across the main and generated files. + var newDecls []ast.Decl + for _, decl := range f.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + if d.Tok == token.CONST { + continue // Skip const declarations + } + if d.Tok == token.IMPORT { + cmap[decl] = nil // Drop pre-import comments. + } + } + newDecls = append(newDecls, decl) + } + f.Decls = newDecls + + // Replace identifiers. + ast.Inspect(f, func(n ast.Node) bool { + switch x := n.(type) { + case *ast.Ident: + if replacement, ok := replacements[x.Name]; ok { + x.Name = replacement + } + } + return true + }) + + // Replace identifiers in comments. + for _, c := range f.Comments { + for _, l := range c.List { + for k, v := range replacements { + if k == "k" { + continue + } + l.Text = strings.ReplaceAll(l.Text, k, v) + } + } + } + + out, err := os.Create(*outputFile) + if err != nil { + log.Fatal(err) + } + defer out.Close() + + out.WriteString("// Code generated by generate1024.go. DO NOT EDIT.\n\n") + + f.Comments = cmap.Filter(f).Comments() + err = format.Node(out, fset, f) + if err != nil { + log.Fatal(err) + } +} diff --git a/crypto/internal/fips140/mlkem/mlkem1024.go b/crypto/internal/fips140/mlkem/mlkem1024.go new file mode 100644 index 00000000000..4b5dd803cb0 --- /dev/null +++ b/crypto/internal/fips140/mlkem/mlkem1024.go @@ -0,0 +1,458 @@ +// Code generated by generate1024.go. DO NOT EDIT. + +package mlkem + +import ( + "bytes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "errors" +) + +// A DecapsulationKey1024 is the secret key used to decapsulate a shared key from a +// ciphertext. It includes various precomputed values. +type DecapsulationKey1024 struct { + d [32]byte // decapsulation key seed + z [32]byte // implicit rejection sampling seed + + ρ [32]byte // sampleNTT seed for A, stored for the encapsulation key + h [32]byte // H(ek), stored for ML-KEM.Decaps_internal + + encryptionKey1024 + decryptionKey1024 +} + +// Bytes returns the decapsulation key as a 64-byte seed in the "d || z" form. +// +// The decapsulation key must be kept secret. +func (dk *DecapsulationKey1024) Bytes() []byte { + var b [SeedSize]byte + copy(b[:], dk.d[:]) + copy(b[32:], dk.z[:]) + return b[:] +} + +// TestingOnlyExpandedBytes1024 returns the decapsulation key as a byte slice +// using the full expanded NIST encoding. +// +// This should only be used for ACVP testing. For all other purposes prefer +// the Bytes method that returns the (much smaller) seed. +func TestingOnlyExpandedBytes1024(dk *DecapsulationKey1024) []byte { + b := make([]byte, 0, decapsulationKeySize1024) + + // ByteEncode₁₂(s) + for i := range dk.s { + b = polyByteEncode(b, dk.s[i]) + } + + // ByteEncode₁₂(t) || ρ + for i := range dk.t { + b = polyByteEncode(b, dk.t[i]) + } + b = append(b, dk.ρ[:]...) + + // H(ek) || z + b = append(b, dk.h[:]...) + b = append(b, dk.z[:]...) + + return b +} + +// EncapsulationKey returns the public encapsulation key necessary to produce +// ciphertexts. +func (dk *DecapsulationKey1024) EncapsulationKey() *EncapsulationKey1024 { + return &EncapsulationKey1024{ + ρ: dk.ρ, + h: dk.h, + encryptionKey1024: dk.encryptionKey1024, + } +} + +// An EncapsulationKey1024 is the public key used to produce ciphertexts to be +// decapsulated by the corresponding [DecapsulationKey1024]. +type EncapsulationKey1024 struct { + ρ [32]byte // sampleNTT seed for A + h [32]byte // H(ek) + encryptionKey1024 +} + +// Bytes returns the encapsulation key as a byte slice. +func (ek *EncapsulationKey1024) Bytes() []byte { + // The actual logic is in a separate function to outline this allocation. + b := make([]byte, 0, EncapsulationKeySize1024) + return ek.bytes(b) +} + +func (ek *EncapsulationKey1024) bytes(b []byte) []byte { + for i := range ek.t { + b = polyByteEncode(b, ek.t[i]) + } + b = append(b, ek.ρ[:]...) + return b +} + +// encryptionKey1024 is the parsed and expanded form of a PKE encryption key. +type encryptionKey1024 struct { + t [k1024]nttElement // ByteDecode₁₂(ek[:384k]) + a [k1024 * k1024]nttElement // A[i*k+j] = sampleNTT(ρ, j, i) +} + +// decryptionKey1024 is the parsed and expanded form of a PKE decryption key. +type decryptionKey1024 struct { + s [k1024]nttElement // ByteDecode₁₂(dk[:decryptionKey1024Size]) +} + +// GenerateKey1024 generates a new decapsulation key, drawing random bytes from +// a DRBG. The decapsulation key must be kept secret. +func GenerateKey1024() (*DecapsulationKey1024, error) { + // The actual logic is in a separate function to outline this allocation. + dk := &DecapsulationKey1024{} + return generateKey1024(dk) +} + +func generateKey1024(dk *DecapsulationKey1024) (*DecapsulationKey1024, error) { + var d [32]byte + drbg.Read(d[:]) + var z [32]byte + drbg.Read(z[:]) + kemKeyGen1024(dk, &d, &z) + if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil { + // This clearly can't happen, but FIPS 140-3 requires us to check. + panic(err) + } + fips140.RecordApproved() + return dk, nil +} + +// GenerateKeyInternal1024 is a derandomized version of GenerateKey1024, +// exclusively for use in tests. +func GenerateKeyInternal1024(d, z *[32]byte) *DecapsulationKey1024 { + dk := &DecapsulationKey1024{} + kemKeyGen1024(dk, d, z) + return dk +} + +// NewDecapsulationKey1024 parses a decapsulation key from a 64-byte +// seed in the "d || z" form. The seed must be uniformly random. +func NewDecapsulationKey1024(seed []byte) (*DecapsulationKey1024, error) { + // The actual logic is in a separate function to outline this allocation. + dk := &DecapsulationKey1024{} + return newKeyFromSeed1024(dk, seed) +} + +func newKeyFromSeed1024(dk *DecapsulationKey1024, seed []byte) (*DecapsulationKey1024, error) { + if len(seed) != SeedSize { + return nil, errors.New("mlkem: invalid seed length") + } + d := (*[32]byte)(seed[:32]) + z := (*[32]byte)(seed[32:]) + kemKeyGen1024(dk, d, z) + if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT1024(dk) }); err != nil { + // This clearly can't happen, but FIPS 140-3 requires us to check. + panic(err) + } + fips140.RecordApproved() + return dk, nil +} + +// TestingOnlyNewDecapsulationKey1024 parses a decapsulation key from its expanded NIST format. +// +// Bytes() must not be called on the returned key, as it will not produce the +// original seed. +// +// This function should only be used for ACVP testing. Prefer NewDecapsulationKey1024 for all +// other purposes. +func TestingOnlyNewDecapsulationKey1024(b []byte) (*DecapsulationKey1024, error) { + if len(b) != decapsulationKeySize1024 { + return nil, errors.New("mlkem: invalid NIST decapsulation key length") + } + + dk := &DecapsulationKey1024{} + for i := range dk.s { + var err error + dk.s[i], err = polyByteDecode[nttElement](b[:encodingSize12]) + if err != nil { + return nil, errors.New("mlkem: invalid secret key encoding") + } + b = b[encodingSize12:] + } + + ek, err := NewEncapsulationKey1024(b[:EncapsulationKeySize1024]) + if err != nil { + return nil, err + } + dk.ρ = ek.ρ + dk.h = ek.h + dk.encryptionKey1024 = ek.encryptionKey1024 + b = b[EncapsulationKeySize1024:] + + if !bytes.Equal(dk.h[:], b[:32]) { + return nil, errors.New("mlkem: inconsistent H(ek) in encoded bytes") + } + b = b[32:] + + copy(dk.z[:], b) + + // Generate a random d value for use in Bytes(). This is a safety mechanism + // that avoids returning a broken key vs a random key if this function is + // called in contravention of the TestingOnlyNewDecapsulationKey1024 function + // comment advising against it. + drbg.Read(dk.d[:]) + + return dk, nil +} + +// kemKeyGen1024 generates a decapsulation key. +// +// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and +// K-PKE.KeyGen according to FIPS 203, Algorithm 13. The two are merged to save +// copies and allocations. +func kemKeyGen1024(dk *DecapsulationKey1024, d, z *[32]byte) { + dk.d = *d + dk.z = *z + + g := sha3.New512() + g.Write(d[:]) + g.Write([]byte{k1024}) // Module dimension as a domain separator. + G := g.Sum(make([]byte, 0, 64)) + ρ, σ := G[:32], G[32:] + dk.ρ = [32]byte(ρ) + + A := &dk.a + for i := byte(0); i < k1024; i++ { + for j := byte(0); j < k1024; j++ { + A[i*k1024+j] = sampleNTT(ρ, j, i) + } + } + + var N byte + s := &dk.s + for i := range s { + s[i] = ntt(samplePolyCBD(σ, N)) + N++ + } + e := make([]nttElement, k1024) + for i := range e { + e[i] = ntt(samplePolyCBD(σ, N)) + N++ + } + + t := &dk.t + for i := range t { // t = A ◦ s + e + t[i] = e[i] + for j := range s { + t[i] = polyAdd(t[i], nttMul(A[i*k1024+j], s[j])) + } + } + + H := sha3.New256() + ek := dk.EncapsulationKey().Bytes() + H.Write(ek) + H.Sum(dk.h[:0]) +} + +// kemPCT1024 performs a Pairwise Consistency Test per FIPS 140-3 IG 10.3.A +// Additional Comment 1: "For key pairs generated for use with approved KEMs in +// FIPS 203, the PCT shall consist of applying the encapsulation key ek to +// encapsulate a shared secret K leading to ciphertext c, and then applying +// decapsulation key dk to retrieve the same shared secret K. The PCT passes if +// the two shared secret K values are equal. The PCT shall be performed either +// when keys are generated/imported, prior to the first exportation, or prior to +// the first operational use (if not exported before the first use)." +func kemPCT1024(dk *DecapsulationKey1024) error { + ek := dk.EncapsulationKey() + K, c := ek.Encapsulate() + K1, err := dk.Decapsulate(c) + if err != nil { + return err + } + if subtle.ConstantTimeCompare(K, K1) != 1 { + return errors.New("mlkem: PCT failed") + } + return nil +} + +// Encapsulate generates a shared key and an associated ciphertext from an +// encapsulation key, drawing random bytes from a DRBG. +// +// The shared key must be kept secret. +func (ek *EncapsulationKey1024) Encapsulate() (sharedKey, ciphertext []byte) { + // The actual logic is in a separate function to outline this allocation. + var cc [CiphertextSize1024]byte + return ek.encapsulate(&cc) +} + +func (ek *EncapsulationKey1024) encapsulate(cc *[CiphertextSize1024]byte) (sharedKey, ciphertext []byte) { + var m [messageSize]byte + drbg.Read(m[:]) + // Note that the modulus check (step 2 of the encapsulation key check from + // FIPS 203, Section 7.2) is performed by polyByteDecode in parseEK1024. + fips140.RecordApproved() + return kemEncaps1024(cc, ek, &m) +} + +// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for +// use in tests. +func (ek *EncapsulationKey1024) EncapsulateInternal(m *[32]byte) (sharedKey, ciphertext []byte) { + cc := &[CiphertextSize1024]byte{} + return kemEncaps1024(cc, ek, m) +} + +// kemEncaps1024 generates a shared key and an associated ciphertext. +// +// It implements ML-KEM.Encaps_internal according to FIPS 203, Algorithm 17. +func kemEncaps1024(cc *[CiphertextSize1024]byte, ek *EncapsulationKey1024, m *[messageSize]byte) (K, c []byte) { + g := sha3.New512() + g.Write(m[:]) + g.Write(ek.h[:]) + G := g.Sum(nil) + K, r := G[:SharedKeySize], G[SharedKeySize:] + c = pkeEncrypt1024(cc, &ek.encryptionKey1024, m, r) + return K, c +} + +// NewEncapsulationKey1024 parses an encapsulation key from its encoded form. +// If the encapsulation key is not valid, NewEncapsulationKey1024 returns an error. +func NewEncapsulationKey1024(encapsulationKey []byte) (*EncapsulationKey1024, error) { + // The actual logic is in a separate function to outline this allocation. + ek := &EncapsulationKey1024{} + return parseEK1024(ek, encapsulationKey) +} + +// parseEK1024 parses an encryption key from its encoded form. +// +// It implements the initial stages of K-PKE.Encrypt according to FIPS 203, +// Algorithm 14. +func parseEK1024(ek *EncapsulationKey1024, ekPKE []byte) (*EncapsulationKey1024, error) { + if len(ekPKE) != EncapsulationKeySize1024 { + return nil, errors.New("mlkem: invalid encapsulation key length") + } + + h := sha3.New256() + h.Write(ekPKE) + h.Sum(ek.h[:0]) + + for i := range ek.t { + var err error + ek.t[i], err = polyByteDecode[nttElement](ekPKE[:encodingSize12]) + if err != nil { + return nil, err + } + ekPKE = ekPKE[encodingSize12:] + } + copy(ek.ρ[:], ekPKE) + + for i := byte(0); i < k1024; i++ { + for j := byte(0); j < k1024; j++ { + ek.a[i*k1024+j] = sampleNTT(ek.ρ[:], j, i) + } + } + + return ek, nil +} + +// pkeEncrypt1024 encrypt a plaintext message. +// +// It implements K-PKE.Encrypt according to FIPS 203, Algorithm 14, although the +// computation of t and AT is done in parseEK1024. +func pkeEncrypt1024(cc *[CiphertextSize1024]byte, ex *encryptionKey1024, m *[messageSize]byte, rnd []byte) []byte { + var N byte + r, e1 := make([]nttElement, k1024), make([]ringElement, k1024) + for i := range r { + r[i] = ntt(samplePolyCBD(rnd, N)) + N++ + } + for i := range e1 { + e1[i] = samplePolyCBD(rnd, N) + N++ + } + e2 := samplePolyCBD(rnd, N) + + u := make([]ringElement, k1024) // NTT⁻¹(AT ◦ r) + e1 + for i := range u { + u[i] = e1[i] + for j := range r { + // Note that i and j are inverted, as we need the transposed of A. + u[i] = polyAdd(u[i], inverseNTT(nttMul(ex.a[j*k1024+i], r[j]))) + } + } + + μ := ringDecodeAndDecompress1(m) + + var vNTT nttElement // t⊺ ◦ r + for i := range ex.t { + vNTT = polyAdd(vNTT, nttMul(ex.t[i], r[i])) + } + v := polyAdd(polyAdd(inverseNTT(vNTT), e2), μ) + + c := cc[:0] + for _, f := range u { + c = ringCompressAndEncode11(c, f) + } + c = ringCompressAndEncode5(c, v) + + return c +} + +// Decapsulate generates a shared key from a ciphertext and a decapsulation key. +// If the ciphertext is not valid, Decapsulate returns an error. +// +// The shared key must be kept secret. +func (dk *DecapsulationKey1024) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) { + if len(ciphertext) != CiphertextSize1024 { + return nil, errors.New("mlkem: invalid ciphertext length") + } + c := (*[CiphertextSize1024]byte)(ciphertext) + // Note that the hash check (step 3 of the decapsulation input check from + // FIPS 203, Section 7.3) is foregone as a DecapsulationKey is always + // validly generated by ML-KEM.KeyGen_internal. + return kemDecaps1024(dk, c), nil +} + +// kemDecaps1024 produces a shared key from a ciphertext. +// +// It implements ML-KEM.Decaps_internal according to FIPS 203, Algorithm 18. +func kemDecaps1024(dk *DecapsulationKey1024, c *[CiphertextSize1024]byte) (K []byte) { + fips140.RecordApproved() + m := pkeDecrypt1024(&dk.decryptionKey1024, c) + g := sha3.New512() + g.Write(m[:]) + g.Write(dk.h[:]) + G := g.Sum(make([]byte, 0, 64)) + Kprime, r := G[:SharedKeySize], G[SharedKeySize:] + J := sha3.NewShake256() + J.Write(dk.z[:]) + J.Write(c[:]) + Kout := make([]byte, SharedKeySize) + J.Read(Kout) + var cc [CiphertextSize1024]byte + c1 := pkeEncrypt1024(&cc, &dk.encryptionKey1024, (*[32]byte)(m), r) + + subtle.ConstantTimeCopy(subtle.ConstantTimeCompare(c[:], c1), Kout, Kprime) + return Kout +} + +// pkeDecrypt1024 decrypts a ciphertext. +// +// It implements K-PKE.Decrypt according to FIPS 203, Algorithm 15, +// although s is retained from kemKeyGen1024. +func pkeDecrypt1024(dx *decryptionKey1024, c *[CiphertextSize1024]byte) []byte { + u := make([]ringElement, k1024) + for i := range u { + b := (*[encodingSize11]byte)(c[encodingSize11*i : encodingSize11*(i+1)]) + u[i] = ringDecodeAndDecompress11(b) + } + + b := (*[encodingSize5]byte)(c[encodingSize11*k1024:]) + v := ringDecodeAndDecompress5(b) + + var mask nttElement // s⊺ ◦ NTT(u) + for i := range dx.s { + mask = polyAdd(mask, nttMul(dx.s[i], ntt(u[i]))) + } + w := polySub(v, inverseNTT(mask)) + + return ringCompressAndEncode1(nil, w) +} diff --git a/crypto/internal/fips140/mlkem/mlkem768.go b/crypto/internal/fips140/mlkem/mlkem768.go new file mode 100644 index 00000000000..8c6e5dc3b08 --- /dev/null +++ b/crypto/internal/fips140/mlkem/mlkem768.go @@ -0,0 +1,518 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package mlkem implements the quantum-resistant key encapsulation method +// ML-KEM (formerly known as Kyber), as specified in [NIST FIPS 203]. +// +// [NIST FIPS 203]: https://doi.org/10.6028/NIST.FIPS.203 +package mlkem + +// This package targets security, correctness, simplicity, readability, and +// reviewability as its primary goals. All critical operations are performed in +// constant time. +// +// Variable and function names, as well as code layout, are selected to +// facilitate reviewing the implementation against the NIST FIPS 203 document. +// +// Reviewers unfamiliar with polynomials or linear algebra might find the +// background at https://words.filippo.io/kyber-math/ useful. +// +// This file implements the recommended parameter set ML-KEM-768. The ML-KEM-1024 +// parameter set implementation is auto-generated from this file. +// +//go:generate go run generate1024.go -input mlkem768.go -output mlkem1024.go + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" +) + +const ( + // ML-KEM global constants. + n = 256 + q = 3329 + + // encodingSizeX is the byte size of a ringElement or nttElement encoded + // by ByteEncode_X (FIPS 203, Algorithm 5). + encodingSize12 = n * 12 / 8 + encodingSize11 = n * 11 / 8 + encodingSize10 = n * 10 / 8 + encodingSize5 = n * 5 / 8 + encodingSize4 = n * 4 / 8 + encodingSize1 = n * 1 / 8 + + messageSize = encodingSize1 + + SharedKeySize = 32 + SeedSize = 32 + 32 +) + +// ML-KEM-768 parameters. +const ( + k = 3 + + CiphertextSize768 = k*encodingSize10 + encodingSize4 + EncapsulationKeySize768 = k*encodingSize12 + 32 + decapsulationKeySize768 = k*encodingSize12 + EncapsulationKeySize768 + 32 + 32 +) + +// ML-KEM-1024 parameters. +const ( + k1024 = 4 + + CiphertextSize1024 = k1024*encodingSize11 + encodingSize5 + EncapsulationKeySize1024 = k1024*encodingSize12 + 32 + decapsulationKeySize1024 = k1024*encodingSize12 + EncapsulationKeySize1024 + 32 + 32 +) + +// A DecapsulationKey768 is the secret key used to decapsulate a shared key from a +// ciphertext. It includes various precomputed values. +type DecapsulationKey768 struct { + d [32]byte // decapsulation key seed + z [32]byte // implicit rejection sampling seed + + ρ [32]byte // sampleNTT seed for A, stored for the encapsulation key + h [32]byte // H(ek), stored for ML-KEM.Decaps_internal + + encryptionKey + decryptionKey +} + +// Bytes returns the decapsulation key as a 64-byte seed in the "d || z" form. +// +// The decapsulation key must be kept secret. +func (dk *DecapsulationKey768) Bytes() []byte { + var b [SeedSize]byte + copy(b[:], dk.d[:]) + copy(b[32:], dk.z[:]) + return b[:] +} + +// TestingOnlyExpandedBytes768 returns the decapsulation key as a byte slice +// using the full expanded NIST encoding. +// +// This should only be used for ACVP testing. For all other purposes prefer +// the Bytes method that returns the (much smaller) seed. +func TestingOnlyExpandedBytes768(dk *DecapsulationKey768) []byte { + b := make([]byte, 0, decapsulationKeySize768) + + // ByteEncode₁₂(s) + for i := range dk.s { + b = polyByteEncode(b, dk.s[i]) + } + + // ByteEncode₁₂(t) || ρ + for i := range dk.t { + b = polyByteEncode(b, dk.t[i]) + } + b = append(b, dk.ρ[:]...) + + // H(ek) || z + b = append(b, dk.h[:]...) + b = append(b, dk.z[:]...) + + return b +} + +// EncapsulationKey returns the public encapsulation key necessary to produce +// ciphertexts. +func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768 { + return &EncapsulationKey768{ + ρ: dk.ρ, + h: dk.h, + encryptionKey: dk.encryptionKey, + } +} + +// An EncapsulationKey768 is the public key used to produce ciphertexts to be +// decapsulated by the corresponding [DecapsulationKey768]. +type EncapsulationKey768 struct { + ρ [32]byte // sampleNTT seed for A + h [32]byte // H(ek) + encryptionKey +} + +// Bytes returns the encapsulation key as a byte slice. +func (ek *EncapsulationKey768) Bytes() []byte { + // The actual logic is in a separate function to outline this allocation. + b := make([]byte, 0, EncapsulationKeySize768) + return ek.bytes(b) +} + +func (ek *EncapsulationKey768) bytes(b []byte) []byte { + for i := range ek.t { + b = polyByteEncode(b, ek.t[i]) + } + b = append(b, ek.ρ[:]...) + return b +} + +// encryptionKey is the parsed and expanded form of a PKE encryption key. +type encryptionKey struct { + t [k]nttElement // ByteDecode₁₂(ek[:384k]) + a [k * k]nttElement // A[i*k+j] = sampleNTT(ρ, j, i) +} + +// decryptionKey is the parsed and expanded form of a PKE decryption key. +type decryptionKey struct { + s [k]nttElement // ByteDecode₁₂(dk[:decryptionKeySize]) +} + +// GenerateKey768 generates a new decapsulation key, drawing random bytes from +// a DRBG. The decapsulation key must be kept secret. +func GenerateKey768() (*DecapsulationKey768, error) { + // The actual logic is in a separate function to outline this allocation. + dk := &DecapsulationKey768{} + return generateKey(dk) +} + +func generateKey(dk *DecapsulationKey768) (*DecapsulationKey768, error) { + var d [32]byte + drbg.Read(d[:]) + var z [32]byte + drbg.Read(z[:]) + kemKeyGen(dk, &d, &z) + if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { + // This clearly can't happen, but FIPS 140-3 requires us to check. + panic(err) + } + fips140.RecordApproved() + return dk, nil +} + +// GenerateKeyInternal768 is a derandomized version of GenerateKey768, +// exclusively for use in tests. +func GenerateKeyInternal768(d, z *[32]byte) *DecapsulationKey768 { + dk := &DecapsulationKey768{} + kemKeyGen(dk, d, z) + return dk +} + +// NewDecapsulationKey768 parses a decapsulation key from a 64-byte +// seed in the "d || z" form. The seed must be uniformly random. +func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error) { + // The actual logic is in a separate function to outline this allocation. + dk := &DecapsulationKey768{} + return newKeyFromSeed(dk, seed) +} + +func newKeyFromSeed(dk *DecapsulationKey768, seed []byte) (*DecapsulationKey768, error) { + if len(seed) != SeedSize { + return nil, errors.New("mlkem: invalid seed length") + } + d := (*[32]byte)(seed[:32]) + z := (*[32]byte)(seed[32:]) + kemKeyGen(dk, d, z) + if err := fips140.PCT("ML-KEM PCT", func() error { return kemPCT(dk) }); err != nil { + // This clearly can't happen, but FIPS 140-3 requires us to check. + panic(err) + } + fips140.RecordApproved() + return dk, nil +} + +// TestingOnlyNewDecapsulationKey768 parses a decapsulation key from its expanded NIST format. +// +// Bytes() must not be called on the returned key, as it will not produce the +// original seed. +// +// This function should only be used for ACVP testing. Prefer NewDecapsulationKey768 for all +// other purposes. +func TestingOnlyNewDecapsulationKey768(b []byte) (*DecapsulationKey768, error) { + if len(b) != decapsulationKeySize768 { + return nil, errors.New("mlkem: invalid NIST decapsulation key length") + } + + dk := &DecapsulationKey768{} + for i := range dk.s { + var err error + dk.s[i], err = polyByteDecode[nttElement](b[:encodingSize12]) + if err != nil { + return nil, errors.New("mlkem: invalid secret key encoding") + } + b = b[encodingSize12:] + } + + ek, err := NewEncapsulationKey768(b[:EncapsulationKeySize768]) + if err != nil { + return nil, err + } + dk.ρ = ek.ρ + dk.h = ek.h + dk.encryptionKey = ek.encryptionKey + b = b[EncapsulationKeySize768:] + + if !bytes.Equal(dk.h[:], b[:32]) { + return nil, errors.New("mlkem: inconsistent H(ek) in encoded bytes") + } + b = b[32:] + + copy(dk.z[:], b) + + // Generate a random d value for use in Bytes(). This is a safety mechanism + // that avoids returning a broken key vs a random key if this function is + // called in contravention of the TestingOnlyNewDecapsulationKey768 function + // comment advising against it. + drbg.Read(dk.d[:]) + + return dk, nil +} + +// kemKeyGen generates a decapsulation key. +// +// It implements ML-KEM.KeyGen_internal according to FIPS 203, Algorithm 16, and +// K-PKE.KeyGen according to FIPS 203, Algorithm 13. The two are merged to save +// copies and allocations. +func kemKeyGen(dk *DecapsulationKey768, d, z *[32]byte) { + dk.d = *d + dk.z = *z + + g := sha3.New512() + g.Write(d[:]) + g.Write([]byte{k}) // Module dimension as a domain separator. + G := g.Sum(make([]byte, 0, 64)) + ρ, σ := G[:32], G[32:] + dk.ρ = [32]byte(ρ) + + A := &dk.a + for i := byte(0); i < k; i++ { + for j := byte(0); j < k; j++ { + A[i*k+j] = sampleNTT(ρ, j, i) + } + } + + var N byte + s := &dk.s + for i := range s { + s[i] = ntt(samplePolyCBD(σ, N)) + N++ + } + e := make([]nttElement, k) + for i := range e { + e[i] = ntt(samplePolyCBD(σ, N)) + N++ + } + + t := &dk.t + for i := range t { // t = A ◦ s + e + t[i] = e[i] + for j := range s { + t[i] = polyAdd(t[i], nttMul(A[i*k+j], s[j])) + } + } + + H := sha3.New256() + ek := dk.EncapsulationKey().Bytes() + H.Write(ek) + H.Sum(dk.h[:0]) +} + +// kemPCT performs a Pairwise Consistency Test per FIPS 140-3 IG 10.3.A +// Additional Comment 1: "For key pairs generated for use with approved KEMs in +// FIPS 203, the PCT shall consist of applying the encapsulation key ek to +// encapsulate a shared secret K leading to ciphertext c, and then applying +// decapsulation key dk to retrieve the same shared secret K. The PCT passes if +// the two shared secret K values are equal. The PCT shall be performed either +// when keys are generated/imported, prior to the first exportation, or prior to +// the first operational use (if not exported before the first use)." +func kemPCT(dk *DecapsulationKey768) error { + ek := dk.EncapsulationKey() + K, c := ek.Encapsulate() + K1, err := dk.Decapsulate(c) + if err != nil { + return err + } + if subtle.ConstantTimeCompare(K, K1) != 1 { + return errors.New("mlkem: PCT failed") + } + return nil +} + +// Encapsulate generates a shared key and an associated ciphertext from an +// encapsulation key, drawing random bytes from a DRBG. +// +// The shared key must be kept secret. +func (ek *EncapsulationKey768) Encapsulate() (sharedKey, ciphertext []byte) { + // The actual logic is in a separate function to outline this allocation. + var cc [CiphertextSize768]byte + return ek.encapsulate(&cc) +} + +func (ek *EncapsulationKey768) encapsulate(cc *[CiphertextSize768]byte) (sharedKey, ciphertext []byte) { + var m [messageSize]byte + drbg.Read(m[:]) + // Note that the modulus check (step 2 of the encapsulation key check from + // FIPS 203, Section 7.2) is performed by polyByteDecode in parseEK. + fips140.RecordApproved() + return kemEncaps(cc, ek, &m) +} + +// EncapsulateInternal is a derandomized version of Encapsulate, exclusively for +// use in tests. +func (ek *EncapsulationKey768) EncapsulateInternal(m *[32]byte) (sharedKey, ciphertext []byte) { + cc := &[CiphertextSize768]byte{} + return kemEncaps(cc, ek, m) +} + +// kemEncaps generates a shared key and an associated ciphertext. +// +// It implements ML-KEM.Encaps_internal according to FIPS 203, Algorithm 17. +func kemEncaps(cc *[CiphertextSize768]byte, ek *EncapsulationKey768, m *[messageSize]byte) (K, c []byte) { + g := sha3.New512() + g.Write(m[:]) + g.Write(ek.h[:]) + G := g.Sum(nil) + K, r := G[:SharedKeySize], G[SharedKeySize:] + c = pkeEncrypt(cc, &ek.encryptionKey, m, r) + return K, c +} + +// NewEncapsulationKey768 parses an encapsulation key from its encoded form. +// If the encapsulation key is not valid, NewEncapsulationKey768 returns an error. +func NewEncapsulationKey768(encapsulationKey []byte) (*EncapsulationKey768, error) { + // The actual logic is in a separate function to outline this allocation. + ek := &EncapsulationKey768{} + return parseEK(ek, encapsulationKey) +} + +// parseEK parses an encryption key from its encoded form. +// +// It implements the initial stages of K-PKE.Encrypt according to FIPS 203, +// Algorithm 14. +func parseEK(ek *EncapsulationKey768, ekPKE []byte) (*EncapsulationKey768, error) { + if len(ekPKE) != EncapsulationKeySize768 { + return nil, errors.New("mlkem: invalid encapsulation key length") + } + + h := sha3.New256() + h.Write(ekPKE) + h.Sum(ek.h[:0]) + + for i := range ek.t { + var err error + ek.t[i], err = polyByteDecode[nttElement](ekPKE[:encodingSize12]) + if err != nil { + return nil, err + } + ekPKE = ekPKE[encodingSize12:] + } + copy(ek.ρ[:], ekPKE) + + for i := byte(0); i < k; i++ { + for j := byte(0); j < k; j++ { + ek.a[i*k+j] = sampleNTT(ek.ρ[:], j, i) + } + } + + return ek, nil +} + +// pkeEncrypt encrypt a plaintext message. +// +// It implements K-PKE.Encrypt according to FIPS 203, Algorithm 14, although the +// computation of t and AT is done in parseEK. +func pkeEncrypt(cc *[CiphertextSize768]byte, ex *encryptionKey, m *[messageSize]byte, rnd []byte) []byte { + var N byte + r, e1 := make([]nttElement, k), make([]ringElement, k) + for i := range r { + r[i] = ntt(samplePolyCBD(rnd, N)) + N++ + } + for i := range e1 { + e1[i] = samplePolyCBD(rnd, N) + N++ + } + e2 := samplePolyCBD(rnd, N) + + u := make([]ringElement, k) // NTT⁻¹(AT ◦ r) + e1 + for i := range u { + u[i] = e1[i] + for j := range r { + // Note that i and j are inverted, as we need the transposed of A. + u[i] = polyAdd(u[i], inverseNTT(nttMul(ex.a[j*k+i], r[j]))) + } + } + + μ := ringDecodeAndDecompress1(m) + + var vNTT nttElement // t⊺ ◦ r + for i := range ex.t { + vNTT = polyAdd(vNTT, nttMul(ex.t[i], r[i])) + } + v := polyAdd(polyAdd(inverseNTT(vNTT), e2), μ) + + c := cc[:0] + for _, f := range u { + c = ringCompressAndEncode10(c, f) + } + c = ringCompressAndEncode4(c, v) + + return c +} + +// Decapsulate generates a shared key from a ciphertext and a decapsulation key. +// If the ciphertext is not valid, Decapsulate returns an error. +// +// The shared key must be kept secret. +func (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) { + if len(ciphertext) != CiphertextSize768 { + return nil, errors.New("mlkem: invalid ciphertext length") + } + c := (*[CiphertextSize768]byte)(ciphertext) + // Note that the hash check (step 3 of the decapsulation input check from + // FIPS 203, Section 7.3) is foregone as a DecapsulationKey is always + // validly generated by ML-KEM.KeyGen_internal. + return kemDecaps(dk, c), nil +} + +// kemDecaps produces a shared key from a ciphertext. +// +// It implements ML-KEM.Decaps_internal according to FIPS 203, Algorithm 18. +func kemDecaps(dk *DecapsulationKey768, c *[CiphertextSize768]byte) (K []byte) { + fips140.RecordApproved() + m := pkeDecrypt(&dk.decryptionKey, c) + g := sha3.New512() + g.Write(m[:]) + g.Write(dk.h[:]) + G := g.Sum(make([]byte, 0, 64)) + Kprime, r := G[:SharedKeySize], G[SharedKeySize:] + J := sha3.NewShake256() + J.Write(dk.z[:]) + J.Write(c[:]) + Kout := make([]byte, SharedKeySize) + J.Read(Kout) + var cc [CiphertextSize768]byte + c1 := pkeEncrypt(&cc, &dk.encryptionKey, (*[32]byte)(m), r) + + subtle.ConstantTimeCopy(subtle.ConstantTimeCompare(c[:], c1), Kout, Kprime) + return Kout +} + +// pkeDecrypt decrypts a ciphertext. +// +// It implements K-PKE.Decrypt according to FIPS 203, Algorithm 15, +// although s is retained from kemKeyGen. +func pkeDecrypt(dx *decryptionKey, c *[CiphertextSize768]byte) []byte { + u := make([]ringElement, k) + for i := range u { + b := (*[encodingSize10]byte)(c[encodingSize10*i : encodingSize10*(i+1)]) + u[i] = ringDecodeAndDecompress10(b) + } + + b := (*[encodingSize4]byte)(c[encodingSize10*k:]) + v := ringDecodeAndDecompress4(b) + + var mask nttElement // s⊺ ◦ NTT(u) + for i := range dx.s { + mask = polyAdd(mask, nttMul(dx.s[i], ntt(u[i]))) + } + w := polySub(v, inverseNTT(mask)) + + return ringCompressAndEncode1(nil, w) +} diff --git a/crypto/internal/fips140/nistec/_asm/go.mod b/crypto/internal/fips140/nistec/_asm/go.mod new file mode 100644 index 00000000000..09daa240276 --- /dev/null +++ b/crypto/internal/fips140/nistec/_asm/go.mod @@ -0,0 +1,11 @@ +module crypto/internal/fips140/nistec/_asm + +go 1.24 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) diff --git a/crypto/internal/fips140/nistec/_asm/go.sum b/crypto/internal/fips140/nistec/_asm/go.sum new file mode 100644 index 00000000000..76af484b2eb --- /dev/null +++ b/crypto/internal/fips140/nistec/_asm/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/crypto/internal/nistec/_asm/p256_asm_amd64.go b/crypto/internal/fips140/nistec/_asm/p256_asm.go similarity index 90% rename from crypto/internal/nistec/_asm/p256_asm_amd64.go rename to crypto/internal/fips140/nistec/_asm/p256_asm.go index 4bc73c56c5b..c32e7edf74a 100644 --- a/crypto/internal/nistec/_asm/p256_asm_amd64.go +++ b/crypto/internal/fips140/nistec/_asm/p256_asm.go @@ -21,7 +21,7 @@ import ( . "github.com/mmcloughlin/avo/reg" ) -//go:generate go run . -out ../p256_asm_amd64.s -pkg nistec +//go:generate go run . -out ../p256_asm_amd64.s var ( res_ptr GPPhysical = RDI @@ -43,12 +43,8 @@ var ( ) func main() { - Package("github.com/runZeroInc/excrypto/crypto/internal/nistec") + Package("crypto/internal/fips140/nistec") ConstraintExpr("!purego") - p256OrdLittleToBig() - p256OrdBigToLittle() - p256LittleToBig() - p256BigToLittle() p256MovCond() p256NegCond() p256Sqr() @@ -58,100 +54,24 @@ func main() { p256SelectAffine() p256OrdMul() p256OrdSqr() - p256SubInternalExCrypto() - p256MulInternalExCrypto() - p256SqrInternalExCrypto() + p256SubInternal() + p256MulInternal() + p256SqrInternal() p256PointAddAffineAsm() - p256IsZeroExCrypto() + p256IsZero() p256PointAddAsm() p256PointDoubleAsm() Generate() internalFunctions := []string{ - "·p256SubInternalExCrypto", - "·p256MulInternalExCrypto", - "·p256SqrInternalExCrypto", - "·p256IsZeroExCrypto", + "·p256SubInternal", + "·p256MulInternal", + "·p256SqrInternal", + "·p256IsZero", } removePeskyUnicodeDot(internalFunctions, "../p256_asm_amd64.s") } -// Implements: -// -// func p256OrdLittleToBig(res *[32]byte, in *p256OrdElement) -func p256OrdLittleToBig() { - Implement("p256OrdLittleToBig") - Attributes(NOSPLIT) - // Hack to get Avo to output: - // JMP ·p256BigToLittle(SB) - Instruction(&ir.Instruction{ - Opcode: "JMP", - Operands: []Op{ - LabelRef("·p256BigToLittle(SB)"), - }, - }) -} - -// Implements: -// -// func p256OrdBigToLittle(res *p256OrdElement, in *[32]byte) -func p256OrdBigToLittle() { - Implement("p256OrdBigToLittle") - Attributes(NOSPLIT) - // Hack to get Avo to output: - // JMP ·p256BigToLittle(SB) - Instruction(&ir.Instruction{ - Opcode: "JMP", - Operands: []Op{ - LabelRef("·p256BigToLittle(SB)"), - }, - }) -} - -// Implements -// -// func p256LittleToBig(res *[32]byte, in *p256Element) -func p256LittleToBig() { - Implement("p256LittleToBig") - Attributes(NOSPLIT) - // Hack to get Avo to output: - // JMP ·p256BigToLittle(SB) - Instruction(&ir.Instruction{ - Opcode: "JMP", - Operands: []Op{ - LabelRef("·p256BigToLittle(SB)"), - }, - }) -} - -// Implements: -// -// func p256BigToLittle(res *p256Element, in *[32]byte) -func p256BigToLittle() { - Implement("p256BigToLittle") - Attributes(NOSPLIT) - - Load(Param("res"), res_ptr) - Load(Param("in"), x_ptr) - - MOVQ(Mem{Base: x_ptr}.Offset(8*0), acc0_v1) - MOVQ(Mem{Base: x_ptr}.Offset(8*1), acc1_v1) - MOVQ(Mem{Base: x_ptr}.Offset(8*2), acc2_v1) - MOVQ(Mem{Base: x_ptr}.Offset(8*3), acc3_v1) - - BSWAPQ(acc0_v1) - BSWAPQ(acc1_v1) - BSWAPQ(acc2_v1) - BSWAPQ(acc3_v1) - - MOVQ(acc3_v1, Mem{Base: res_ptr}.Offset(8*0)) - MOVQ(acc2_v1, Mem{Base: res_ptr}.Offset(8*1)) - MOVQ(acc1_v1, Mem{Base: res_ptr}.Offset(8*2)) - MOVQ(acc0_v1, Mem{Base: res_ptr}.Offset(8*3)) - - RET() -} - // Implements: // // func p256MovCond(res, a, b *P256Point, cond int) @@ -1509,8 +1429,8 @@ var ( hlp_v2 = RBP ) -func p256SubInternalExCrypto() { - Function("p256SubInternalExCrypto") +func p256SubInternal() { + Function("p256SubInternal") Attributes(NOSPLIT) XORQ(mul0_v2, mul0_v2) @@ -1541,8 +1461,8 @@ func p256SubInternalExCrypto() { RET() } -func p256MulInternalExCrypto() { - Function("p256MulInternalExCrypto") +func p256MulInternal() { + Function("p256MulInternal") Attributes(NOSPLIT) MOVQ(acc4_v2, mul0_v2) @@ -1738,8 +1658,8 @@ func p256MulInternalExCrypto() { RET() } -func p256SqrInternalExCrypto() { - Function("p256SqrInternalExCrypto") +func p256SqrInternal() { + Function("p256SqrInternal") Attributes(NOSPLIT) MOVQ(acc4_v2, mul0_v2) @@ -2119,57 +2039,57 @@ func p256PointAddAffineAsm() { Comment("Begin point add") LDacc(z1in_v1) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // z1ˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // z1ˆ2 ST(z1sqr_v1) LDt(x2in_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // x2 * z1ˆ2 + CALL(LabelRef("p256MulInternal(SB)")) // x2 * z1ˆ2 LDt(x1in_v1) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) // h = u2 - u1) + CALL(LabelRef("p256SubInternal(SB)")) // h = u2 - u1) ST(h_v1) LDt(z1in_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // z3 = h * z1 + CALL(LabelRef("p256MulInternal(SB)")) // z3 = h * z1 ST(zout_v1) LDacc(z1sqr_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // z1ˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // z1ˆ3 LDt(y2in_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // s2 = y2 * z1ˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // s2 = y2 * z1ˆ3 ST(s2_v1) LDt(y1in_v1) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) // r = s2 - s1) + CALL(LabelRef("p256SubInternal(SB)")) // r = s2 - s1) ST(r_v1) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // rsqr = rˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // rsqr = rˆ2 ST(rsqr_v1) LDacc(h_v1) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // hsqr = hˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // hsqr = hˆ2 ST(hsqr_v1) LDt(h_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // hcub = hˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // hcub = hˆ3 ST(hcub_v1) LDt(y1in_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // y1 * hˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // y1 * hˆ3 ST(s2_v1) LDacc(x1in_v1) LDt(hsqr_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // u1 * hˆ2 + CALL(LabelRef("p256MulInternal(SB)")) // u1 * hˆ2 ST(h_v1) p256MulBy2Inline() // u1 * hˆ2 * 2, inline LDacc(rsqr_v1) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) // rˆ2 - u1 * hˆ2 * 2) + CALL(LabelRef("p256SubInternal(SB)")) // rˆ2 - u1 * hˆ2 * 2) LDt(hcub_v1) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) ST(xout_v1) MOVQ(acc4_v2, t0_v2) @@ -2177,13 +2097,13 @@ func p256PointAddAffineAsm() { MOVQ(acc6_v2, t2_v2) MOVQ(acc7_v2, t3_v2) LDacc(h_v1) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) LDt(r_v1) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) LDt(s2_v1) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) ST(yout_v1) Comment("Load stored values from stack") @@ -2299,10 +2219,10 @@ func p256PointAddAffineAsm() { RET() } -// p256IsZeroExCrypto returns 1 in AX if [acc4..acc7] represents zero and zero +// p256IsZero returns 1 in AX if [acc4..acc7] represents zero and zero // otherwise. It writes to [acc4..acc7], t0 and t1. -func p256IsZeroExCrypto() { - Function("p256IsZeroExCrypto") +func p256IsZero() { + Function("p256IsZero") Attributes(NOSPLIT) Comment("AX contains a flag that is set if the input is zero.") @@ -2409,79 +2329,79 @@ func p256PointAddAsm() { Comment("Begin point add") LDacc(z2in_v2) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // z2ˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // z2ˆ2 ST(z2sqr_v2) LDt(z2in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // z2ˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // z2ˆ3 LDt(y1in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // s1 = z2ˆ3*y1 + CALL(LabelRef("p256MulInternal(SB)")) // s1 = z2ˆ3*y1 ST(s1_v2) LDacc(z1in_v2) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // z1ˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // z1ˆ2 ST(z1sqr_v2) LDt(z1in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // z1ˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // z1ˆ3 LDt(y2in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // s2 = z1ˆ3*y2 + CALL(LabelRef("p256MulInternal(SB)")) // s2 = z1ˆ3*y2 ST(s2_v2) LDt(s1_v2) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) // r = s2 - s1 + CALL(LabelRef("p256SubInternal(SB)")) // r = s2 - s1 ST(r_v2) - CALL(LabelRef("p256IsZeroExCrypto(SB)")) + CALL(LabelRef("p256IsZero(SB)")) MOVQ(RAX, points_eq_v2) LDacc(z2sqr_v2) LDt(x1in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // u1 = x1 * z2ˆ2 + CALL(LabelRef("p256MulInternal(SB)")) // u1 = x1 * z2ˆ2 ST(u1_v2) LDacc(z1sqr_v2) LDt(x2in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // u2 = x2 * z1ˆ2 + CALL(LabelRef("p256MulInternal(SB)")) // u2 = x2 * z1ˆ2 ST(u2_v2) LDt(u1_v2) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) // h = u2 - u1 + CALL(LabelRef("p256SubInternal(SB)")) // h = u2 - u1 ST(h_v2) - CALL(LabelRef("p256IsZeroExCrypto(SB)")) + CALL(LabelRef("p256IsZero(SB)")) ANDQ(points_eq_v2, RAX) MOVQ(RAX, points_eq_v2) LDacc(r_v2) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // rsqr = rˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // rsqr = rˆ2 ST(rsqr_v2) LDacc(h_v2) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) // hsqr = hˆ2 + CALL(LabelRef("p256SqrInternal(SB)")) // hsqr = hˆ2 ST(hsqr_v2) LDt(h_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // hcub = hˆ3 + CALL(LabelRef("p256MulInternal(SB)")) // hcub = hˆ3 ST(hcub_v2) LDt(s1_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) ST(s2_v2) LDacc(z1in_v2) LDt(z2in_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // z1 * z2 + CALL(LabelRef("p256MulInternal(SB)")) // z1 * z2 LDt(h_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // z1 * z2 * h + CALL(LabelRef("p256MulInternal(SB)")) // z1 * z2 * h ST(zout_v2) LDacc(hsqr_v2) LDt(u1_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) // hˆ2 * u1 + CALL(LabelRef("p256MulInternal(SB)")) // hˆ2 * u1 ST(u2_v2) p256MulBy2Inline() // u1 * hˆ2 * 2, inline LDacc(rsqr_v2) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) // rˆ2 - u1 * hˆ2 * 2 + CALL(LabelRef("p256SubInternal(SB)")) // rˆ2 - u1 * hˆ2 * 2 LDt(hcub_v2) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) ST(xout_v2) MOVQ(acc4_v2, t0_v2) @@ -2489,13 +2409,13 @@ func p256PointAddAsm() { MOVQ(acc6_v2, t2_v2) MOVQ(acc7_v2, t3_v2) LDacc(u2_v2) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) LDt(r_v2) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) LDt(s2_v2) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) ST(yout_v2) MOVOU(xout_v2(16*0), X0) @@ -2563,7 +2483,7 @@ func p256PointDoubleAsm() { Comment("Begin point double") LDacc(z) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) + CALL(LabelRef("p256SqrInternal(SB)")) ST(zsqr) LDt(x) @@ -2572,7 +2492,7 @@ func p256PointDoubleAsm() { LDacc(z) LDt(y) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) p256MulBy2Inline() MOVQ(rptr_v3, RAX) @@ -2584,9 +2504,9 @@ func p256PointDoubleAsm() { LDacc(x) LDt(zsqr) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) LDt(m) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) ST(m) Comment("Multiply by 3") @@ -2598,9 +2518,9 @@ func p256PointDoubleAsm() { LDacc(y) p256MulBy2Inline() t2acc() - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) + CALL(LabelRef("p256SqrInternal(SB)")) ST(s) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) + CALL(LabelRef("p256SqrInternal(SB)")) Comment("Divide by 2") XORQ(mul0_v2, mul0_v2) @@ -2632,15 +2552,15 @@ func p256PointDoubleAsm() { Comment("/////////////////////////") LDacc(x) LDt(s) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) ST(s) p256MulBy2Inline() STt(tmp) LDacc(m) - CALL(LabelRef("p256SqrInternalExCrypto(SB)")) + CALL(LabelRef("p256SqrInternal(SB)")) LDt(tmp) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) MOVQ(rptr_v3, RAX) @@ -2652,13 +2572,13 @@ func p256PointDoubleAsm() { acc2t() LDacc(s) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) LDt(m) - CALL(LabelRef("p256MulInternalExCrypto(SB)")) + CALL(LabelRef("p256MulInternal(SB)")) LDt(y) - CALL(LabelRef("p256SubInternalExCrypto(SB)")) + CALL(LabelRef("p256SubInternal(SB)")) MOVQ(rptr_v3, RAX) Comment("Store y") diff --git a/crypto/internal/fips140/nistec/benchmark_test.go b/crypto/internal/fips140/nistec/benchmark_test.go new file mode 100644 index 00000000000..4d5fcff8197 --- /dev/null +++ b/crypto/internal/fips140/nistec/benchmark_test.go @@ -0,0 +1,73 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package nistec_test + +import ( + "testing" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" +) + +type nistPoint[T any] interface { + Bytes() []byte + SetGenerator() T + SetBytes([]byte) (T, error) + Add(T, T) T + Double(T) T + ScalarMult(T, []byte) (T, error) + ScalarBaseMult([]byte) (T, error) +} + +func BenchmarkScalarMult(b *testing.B) { + b.Run("P224", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP224Point().SetGenerator(), 28) + }) + b.Run("P256", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP256Point().SetGenerator(), 32) + }) + b.Run("P384", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP384Point().SetGenerator(), 48) + }) + b.Run("P521", func(b *testing.B) { + benchmarkScalarMult(b, nistec.NewP521Point().SetGenerator(), 66) + }) +} + +func benchmarkScalarMult[P nistPoint[P]](b *testing.B, p P, scalarSize int) { + scalar := make([]byte, scalarSize) + rand.Read(scalar) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + p.ScalarMult(p, scalar) + } +} + +func BenchmarkScalarBaseMult(b *testing.B) { + b.Run("P224", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP224Point().SetGenerator(), 28) + }) + b.Run("P256", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP256Point().SetGenerator(), 32) + }) + b.Run("P384", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP384Point().SetGenerator(), 48) + }) + b.Run("P521", func(b *testing.B) { + benchmarkScalarBaseMult(b, nistec.NewP521Point().SetGenerator(), 66) + }) +} + +func benchmarkScalarBaseMult[P nistPoint[P]](b *testing.B, p P, scalarSize int) { + scalar := make([]byte, scalarSize) + rand.Read(scalar) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + p.ScalarBaseMult(scalar) + } +} diff --git a/crypto/internal/nistec/fiat/Dockerfile b/crypto/internal/fips140/nistec/fiat/Dockerfile similarity index 100% rename from crypto/internal/nistec/fiat/Dockerfile rename to crypto/internal/fips140/nistec/fiat/Dockerfile diff --git a/crypto/internal/nistec/fiat/README b/crypto/internal/fips140/nistec/fiat/README similarity index 100% rename from crypto/internal/nistec/fiat/README rename to crypto/internal/fips140/nistec/fiat/README diff --git a/crypto/internal/nistec/fiat/fiat_test.go b/crypto/internal/fips140/nistec/fiat/benchmark_test.go similarity index 94% rename from crypto/internal/nistec/fiat/fiat_test.go rename to crypto/internal/fips140/nistec/fiat/benchmark_test.go index 86d06081105..8033cfa3225 100644 --- a/crypto/internal/nistec/fiat/fiat_test.go +++ b/crypto/internal/fips140/nistec/fiat/benchmark_test.go @@ -5,8 +5,9 @@ package fiat_test import ( - "github.com/runZeroInc/excrypto/crypto/internal/nistec/fiat" "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec/fiat" ) func BenchmarkMul(b *testing.B) { diff --git a/crypto/internal/fips140/nistec/fiat/cast.go b/crypto/internal/fips140/nistec/fiat/cast.go new file mode 100644 index 00000000000..41c00563e06 --- /dev/null +++ b/crypto/internal/fips140/nistec/fiat/cast.go @@ -0,0 +1,7 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fiat + +import _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" diff --git a/crypto/internal/nistec/fiat/generate.go b/crypto/internal/fips140/nistec/fiat/generate.go similarity index 99% rename from crypto/internal/nistec/fiat/generate.go rename to crypto/internal/fips140/nistec/fiat/generate.go index 0183dc77a88..5e4e013b45d 100644 --- a/crypto/internal/nistec/fiat/generate.go +++ b/crypto/internal/fips140/nistec/fiat/generate.go @@ -152,7 +152,7 @@ const tmplWrapper = `// Copyright 2021 The Go Authors. All rights reserved. package fiat import ( - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" ) diff --git a/crypto/internal/nistec/fiat/p224.go b/crypto/internal/fips140/nistec/fiat/p224.go similarity index 98% rename from crypto/internal/nistec/fiat/p224.go rename to crypto/internal/fips140/nistec/fiat/p224.go index 80d793432ad..67c13ab6e9e 100644 --- a/crypto/internal/nistec/fiat/p224.go +++ b/crypto/internal/fips140/nistec/fiat/p224.go @@ -7,7 +7,7 @@ package fiat import ( - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" ) diff --git a/crypto/internal/nistec/fiat/p224_fiat64.go b/crypto/internal/fips140/nistec/fiat/p224_fiat64.go similarity index 100% rename from crypto/internal/nistec/fiat/p224_fiat64.go rename to crypto/internal/fips140/nistec/fiat/p224_fiat64.go diff --git a/crypto/internal/nistec/fiat/p224_invert.go b/crypto/internal/fips140/nistec/fiat/p224_invert.go similarity index 100% rename from crypto/internal/nistec/fiat/p224_invert.go rename to crypto/internal/fips140/nistec/fiat/p224_invert.go diff --git a/crypto/internal/nistec/fiat/p256.go b/crypto/internal/fips140/nistec/fiat/p256.go similarity index 98% rename from crypto/internal/nistec/fiat/p256.go rename to crypto/internal/fips140/nistec/fiat/p256.go index d01e2d459c6..8758589aea0 100644 --- a/crypto/internal/nistec/fiat/p256.go +++ b/crypto/internal/fips140/nistec/fiat/p256.go @@ -7,7 +7,7 @@ package fiat import ( - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" ) diff --git a/crypto/internal/nistec/fiat/p256_fiat64.go b/crypto/internal/fips140/nistec/fiat/p256_fiat64.go similarity index 100% rename from crypto/internal/nistec/fiat/p256_fiat64.go rename to crypto/internal/fips140/nistec/fiat/p256_fiat64.go diff --git a/crypto/internal/nistec/fiat/p256_invert.go b/crypto/internal/fips140/nistec/fiat/p256_invert.go similarity index 100% rename from crypto/internal/nistec/fiat/p256_invert.go rename to crypto/internal/fips140/nistec/fiat/p256_invert.go diff --git a/crypto/internal/nistec/fiat/p384.go b/crypto/internal/fips140/nistec/fiat/p384.go similarity index 98% rename from crypto/internal/nistec/fiat/p384.go rename to crypto/internal/fips140/nistec/fiat/p384.go index fb34d1e1984..093fdda194c 100644 --- a/crypto/internal/nistec/fiat/p384.go +++ b/crypto/internal/fips140/nistec/fiat/p384.go @@ -7,7 +7,7 @@ package fiat import ( - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" ) diff --git a/crypto/internal/nistec/fiat/p384_fiat64.go b/crypto/internal/fips140/nistec/fiat/p384_fiat64.go similarity index 100% rename from crypto/internal/nistec/fiat/p384_fiat64.go rename to crypto/internal/fips140/nistec/fiat/p384_fiat64.go diff --git a/crypto/internal/nistec/fiat/p384_invert.go b/crypto/internal/fips140/nistec/fiat/p384_invert.go similarity index 100% rename from crypto/internal/nistec/fiat/p384_invert.go rename to crypto/internal/fips140/nistec/fiat/p384_invert.go diff --git a/crypto/internal/nistec/fiat/p521.go b/crypto/internal/fips140/nistec/fiat/p521.go similarity index 98% rename from crypto/internal/nistec/fiat/p521.go rename to crypto/internal/fips140/nistec/fiat/p521.go index 00ebd60c89d..fa8d05e67f5 100644 --- a/crypto/internal/nistec/fiat/p521.go +++ b/crypto/internal/fips140/nistec/fiat/p521.go @@ -7,7 +7,7 @@ package fiat import ( - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" ) diff --git a/crypto/internal/nistec/fiat/p521_fiat64.go b/crypto/internal/fips140/nistec/fiat/p521_fiat64.go similarity index 100% rename from crypto/internal/nistec/fiat/p521_fiat64.go rename to crypto/internal/fips140/nistec/fiat/p521_fiat64.go diff --git a/crypto/internal/nistec/fiat/p521_invert.go b/crypto/internal/fips140/nistec/fiat/p521_invert.go similarity index 100% rename from crypto/internal/nistec/fiat/p521_invert.go rename to crypto/internal/fips140/nistec/fiat/p521_invert.go diff --git a/crypto/internal/nistec/generate.go b/crypto/internal/fips140/nistec/generate.go similarity index 97% rename from crypto/internal/nistec/generate.go rename to crypto/internal/fips140/nistec/generate.go index c95dfb461da..a53a2548bb2 100644 --- a/crypto/internal/nistec/generate.go +++ b/crypto/internal/fips140/nistec/generate.go @@ -14,7 +14,6 @@ package main import ( "bytes" "fmt" - "github.com/runZeroInc/excrypto/crypto/elliptic" "go/format" "io" "log" @@ -23,25 +22,20 @@ import ( "os/exec" "strings" "text/template" + + "github.com/runZeroInc/excrypto/crypto/elliptic" ) var curves = []struct { - P string - Element string - Params *elliptic.CurveParams - BuildTags string + P string + Element string + Params *elliptic.CurveParams }{ { P: "P224", Element: "fiat.P224Element", Params: elliptic.P224().Params(), }, - { - P: "P256", - Element: "fiat.P256Element", - Params: elliptic.P256().Params(), - BuildTags: "(!amd64 && !arm64 && !ppc64le && !s390x) || purego", - }, { P: "P384", Element: "fiat.P384Element", @@ -86,7 +80,6 @@ func main() { if err := t.Execute(buf, map[string]interface{}{ "P": c.P, "p": p, "B": B, "Gx": Gx, "Gy": Gy, "Element": c.Element, "ElementLen": elementLen, - "BuildTags": c.BuildTags, }); err != nil { log.Fatal(err) } @@ -145,15 +138,11 @@ const tmplNISTEC = `// Copyright 2022 The Go Authors. All rights reserved. // Code generated by generate.go. DO NOT EDIT. -{{ if .BuildTags }} -//go:build {{ .BuildTags }} -{{ end }} - package nistec import ( - "github.com/runZeroInc/excrypto/crypto/internal/nistec/fiat" - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec/fiat" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" "sync" ) diff --git a/crypto/internal/nistec/nistec.go b/crypto/internal/fips140/nistec/nistec.go similarity index 81% rename from crypto/internal/nistec/nistec.go rename to crypto/internal/fips140/nistec/nistec.go index d898d409ca7..e9f74eaddb8 100644 --- a/crypto/internal/nistec/nistec.go +++ b/crypto/internal/fips140/nistec/nistec.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package nistec implements the NIST P elliptic curves from FIPS 186-4. +// Package nistec implements the elliptic curves from NIST SP 800-186. // // This package uses fiat-crypto or specialized assembly and Go code for its // backend field arithmetic (not math/big) and exposes constant-time, heap @@ -12,4 +12,6 @@ // can't be represented. package nistec +import _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + //go:generate go run generate.go diff --git a/crypto/internal/nistec/p224.go b/crypto/internal/fips140/nistec/p224.go similarity index 99% rename from crypto/internal/nistec/p224.go rename to crypto/internal/fips140/nistec/p224.go index f5e22eea2d0..b70402c6912 100644 --- a/crypto/internal/nistec/p224.go +++ b/crypto/internal/fips140/nistec/p224.go @@ -7,8 +7,8 @@ package nistec import ( - "github.com/runZeroInc/excrypto/crypto/internal/nistec/fiat" - "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec/fiat" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" "errors" "sync" ) diff --git a/crypto/internal/nistec/p224_sqrt.go b/crypto/internal/fips140/nistec/p224_sqrt.go similarity index 97% rename from crypto/internal/nistec/p224_sqrt.go rename to crypto/internal/fips140/nistec/p224_sqrt.go index adab127f411..6ec30b1ebcb 100644 --- a/crypto/internal/nistec/p224_sqrt.go +++ b/crypto/internal/fips140/nistec/p224_sqrt.go @@ -5,8 +5,9 @@ package nistec import ( - "github.com/runZeroInc/excrypto/crypto/internal/nistec/fiat" "sync" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec/fiat" ) var p224GG *[96]fiat.P224Element diff --git a/crypto/internal/fips140/nistec/p256.go b/crypto/internal/fips140/nistec/p256.go new file mode 100644 index 00000000000..835b475a16c --- /dev/null +++ b/crypto/internal/fips140/nistec/p256.go @@ -0,0 +1,706 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !arm64 && !ppc64le && !s390x) || purego + +package nistec + +import ( + "errors" + "math/bits" + "sync" + "unsafe" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec/fiat" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" +) + +// P256Point is a P-256 point. The zero value is NOT valid. +type P256Point struct { + // The point is represented in projective coordinates (X:Y:Z), where x = X/Z + // and y = Y/Z. Infinity is (0:1:0). + // + // fiat.P256Element is a base field element in [0, P-1] in the Montgomery + // domain (with R 2²⁵⁶ and P 2²⁵⁶ - 2²²⁴ + 2¹⁹² + 2⁹⁶ - 1) as four limbs in + // little-endian order value. + x, y, z fiat.P256Element +} + +// NewP256Point returns a new P256Point representing the point at infinity point. +func NewP256Point() *P256Point { + p := &P256Point{} + p.y.One() + return p +} + +// SetGenerator sets p to the canonical generator and returns p. +func (p *P256Point) SetGenerator() *P256Point { + p.x.SetBytes([]byte{0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2, 0x77, 0x3, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96}) + p.y.SetBytes([]byte{0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb, 0x4a, 0x7c, 0xf, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce, 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5}) + p.z.One() + return p +} + +// Set sets p = q and returns p. +func (p *P256Point) Set(q *P256Point) *P256Point { + p.x.Set(&q.x) + p.y.Set(&q.y) + p.z.Set(&q.z) + return p +} + +const p256ElementLength = 32 +const p256UncompressedLength = 1 + 2*p256ElementLength +const p256CompressedLength = 1 + p256ElementLength + +// SetBytes sets p to the compressed, uncompressed, or infinity value encoded in +// b, as specified in SEC 1, Version 2.0, Section 2.3.4. If the point is not on +// the curve, it returns nil and an error, and the receiver is unchanged. +// Otherwise, it returns p. +func (p *P256Point) SetBytes(b []byte) (*P256Point, error) { + switch { + // Point at infinity. + case len(b) == 1 && b[0] == 0: + return p.Set(NewP256Point()), nil + + // Uncompressed form. + case len(b) == p256UncompressedLength && b[0] == 4: + x, err := new(fiat.P256Element).SetBytes(b[1 : 1+p256ElementLength]) + if err != nil { + return nil, err + } + y, err := new(fiat.P256Element).SetBytes(b[1+p256ElementLength:]) + if err != nil { + return nil, err + } + if err := p256CheckOnCurve(x, y); err != nil { + return nil, err + } + p.x.Set(x) + p.y.Set(y) + p.z.One() + return p, nil + + // Compressed form. + case len(b) == p256CompressedLength && (b[0] == 2 || b[0] == 3): + x, err := new(fiat.P256Element).SetBytes(b[1:]) + if err != nil { + return nil, err + } + + // y² = x³ - 3x + b + y := p256Polynomial(new(fiat.P256Element), x) + if !p256Sqrt(y, y) { + return nil, errors.New("invalid P256 compressed point encoding") + } + + // Select the positive or negative root, as indicated by the least + // significant bit, based on the encoding type byte. + otherRoot := new(fiat.P256Element) + otherRoot.Sub(otherRoot, y) + cond := y.Bytes()[p256ElementLength-1]&1 ^ b[0]&1 + y.Select(otherRoot, y, int(cond)) + + p.x.Set(x) + p.y.Set(y) + p.z.One() + return p, nil + + default: + return nil, errors.New("invalid P256 point encoding") + } +} + +var _p256B *fiat.P256Element +var _p256BOnce sync.Once + +func p256B() *fiat.P256Element { + _p256BOnce.Do(func() { + _p256B, _ = new(fiat.P256Element).SetBytes([]byte{0x5a, 0xc6, 0x35, 0xd8, 0xaa, 0x3a, 0x93, 0xe7, 0xb3, 0xeb, 0xbd, 0x55, 0x76, 0x98, 0x86, 0xbc, 0x65, 0x1d, 0x6, 0xb0, 0xcc, 0x53, 0xb0, 0xf6, 0x3b, 0xce, 0x3c, 0x3e, 0x27, 0xd2, 0x60, 0x4b}) + }) + return _p256B +} + +// p256Polynomial sets y2 to x³ - 3x + b, and returns y2. +func p256Polynomial(y2, x *fiat.P256Element) *fiat.P256Element { + y2.Square(x) + y2.Mul(y2, x) + + threeX := new(fiat.P256Element).Add(x, x) + threeX.Add(threeX, x) + y2.Sub(y2, threeX) + + return y2.Add(y2, p256B()) +} + +func p256CheckOnCurve(x, y *fiat.P256Element) error { + // y² = x³ - 3x + b + rhs := p256Polynomial(new(fiat.P256Element), x) + lhs := new(fiat.P256Element).Square(y) + if rhs.Equal(lhs) != 1 { + return errors.New("P256 point not on curve") + } + return nil +} + +// Bytes returns the uncompressed or infinity encoding of p, as specified in +// SEC 1, Version 2.0, Section 2.3.3. Note that the encoding of the point at +// infinity is shorter than all other encodings. +func (p *P256Point) Bytes() []byte { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. + var out [p256UncompressedLength]byte + return p.bytes(&out) +} + +func (p *P256Point) bytes(out *[p256UncompressedLength]byte) []byte { + // The SEC 1 representation of the point at infinity is a single zero byte, + // and only infinity has z = 0. + if p.z.IsZero() == 1 { + return append(out[:0], 0) + } + + zinv := new(fiat.P256Element).Invert(&p.z) + x := new(fiat.P256Element).Mul(&p.x, zinv) + y := new(fiat.P256Element).Mul(&p.y, zinv) + + buf := append(out[:0], 4) + buf = append(buf, x.Bytes()...) + buf = append(buf, y.Bytes()...) + return buf +} + +// BytesX returns the encoding of the x-coordinate of p, as specified in SEC 1, +// Version 2.0, Section 2.3.5, or an error if p is the point at infinity. +func (p *P256Point) BytesX() ([]byte, error) { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. + var out [p256ElementLength]byte + return p.bytesX(&out) +} + +func (p *P256Point) bytesX(out *[p256ElementLength]byte) ([]byte, error) { + if p.z.IsZero() == 1 { + return nil, errors.New("P256 point is the point at infinity") + } + + zinv := new(fiat.P256Element).Invert(&p.z) + x := new(fiat.P256Element).Mul(&p.x, zinv) + + return append(out[:0], x.Bytes()...), nil +} + +// BytesCompressed returns the compressed or infinity encoding of p, as +// specified in SEC 1, Version 2.0, Section 2.3.3. Note that the encoding of the +// point at infinity is shorter than all other encodings. +func (p *P256Point) BytesCompressed() []byte { + // This function is outlined to make the allocations inline in the caller + // rather than happen on the heap. + var out [p256CompressedLength]byte + return p.bytesCompressed(&out) +} + +func (p *P256Point) bytesCompressed(out *[p256CompressedLength]byte) []byte { + if p.z.IsZero() == 1 { + return append(out[:0], 0) + } + + zinv := new(fiat.P256Element).Invert(&p.z) + x := new(fiat.P256Element).Mul(&p.x, zinv) + y := new(fiat.P256Element).Mul(&p.y, zinv) + + // Encode the sign of the y coordinate (indicated by the least significant + // bit) as the encoding type (2 or 3). + buf := append(out[:0], 2) + buf[0] |= y.Bytes()[p256ElementLength-1] & 1 + buf = append(buf, x.Bytes()...) + return buf +} + +// Add sets q = p1 + p2, and returns q. The points may overlap. +func (q *P256Point) Add(p1, p2 *P256Point) *P256Point { + // Complete addition formula for a = -3 from "Complete addition formulas for + // prime order elliptic curves" (https://eprint.iacr.org/2015/1060), §A.2. + + t0 := new(fiat.P256Element).Mul(&p1.x, &p2.x) // t0 := X1 * X2 + t1 := new(fiat.P256Element).Mul(&p1.y, &p2.y) // t1 := Y1 * Y2 + t2 := new(fiat.P256Element).Mul(&p1.z, &p2.z) // t2 := Z1 * Z2 + t3 := new(fiat.P256Element).Add(&p1.x, &p1.y) // t3 := X1 + Y1 + t4 := new(fiat.P256Element).Add(&p2.x, &p2.y) // t4 := X2 + Y2 + t3.Mul(t3, t4) // t3 := t3 * t4 + t4.Add(t0, t1) // t4 := t0 + t1 + t3.Sub(t3, t4) // t3 := t3 - t4 + t4.Add(&p1.y, &p1.z) // t4 := Y1 + Z1 + x3 := new(fiat.P256Element).Add(&p2.y, &p2.z) // X3 := Y2 + Z2 + t4.Mul(t4, x3) // t4 := t4 * X3 + x3.Add(t1, t2) // X3 := t1 + t2 + t4.Sub(t4, x3) // t4 := t4 - X3 + x3.Add(&p1.x, &p1.z) // X3 := X1 + Z1 + y3 := new(fiat.P256Element).Add(&p2.x, &p2.z) // Y3 := X2 + Z2 + x3.Mul(x3, y3) // X3 := X3 * Y3 + y3.Add(t0, t2) // Y3 := t0 + t2 + y3.Sub(x3, y3) // Y3 := X3 - Y3 + z3 := new(fiat.P256Element).Mul(p256B(), t2) // Z3 := b * t2 + x3.Sub(y3, z3) // X3 := Y3 - Z3 + z3.Add(x3, x3) // Z3 := X3 + X3 + x3.Add(x3, z3) // X3 := X3 + Z3 + z3.Sub(t1, x3) // Z3 := t1 - X3 + x3.Add(t1, x3) // X3 := t1 + X3 + y3.Mul(p256B(), y3) // Y3 := b * Y3 + t1.Add(t2, t2) // t1 := t2 + t2 + t2.Add(t1, t2) // t2 := t1 + t2 + y3.Sub(y3, t2) // Y3 := Y3 - t2 + y3.Sub(y3, t0) // Y3 := Y3 - t0 + t1.Add(y3, y3) // t1 := Y3 + Y3 + y3.Add(t1, y3) // Y3 := t1 + Y3 + t1.Add(t0, t0) // t1 := t0 + t0 + t0.Add(t1, t0) // t0 := t1 + t0 + t0.Sub(t0, t2) // t0 := t0 - t2 + t1.Mul(t4, y3) // t1 := t4 * Y3 + t2.Mul(t0, y3) // t2 := t0 * Y3 + y3.Mul(x3, z3) // Y3 := X3 * Z3 + y3.Add(y3, t2) // Y3 := Y3 + t2 + x3.Mul(t3, x3) // X3 := t3 * X3 + x3.Sub(x3, t1) // X3 := X3 - t1 + z3.Mul(t4, z3) // Z3 := t4 * Z3 + t1.Mul(t3, t0) // t1 := t3 * t0 + z3.Add(z3, t1) // Z3 := Z3 + t1 + + q.x.Set(x3) + q.y.Set(y3) + q.z.Set(z3) + return q +} + +// Double sets q = p + p, and returns q. The points may overlap. +func (q *P256Point) Double(p *P256Point) *P256Point { + // Complete addition formula for a = -3 from "Complete addition formulas for + // prime order elliptic curves" (https://eprint.iacr.org/2015/1060), §A.2. + + t0 := new(fiat.P256Element).Square(&p.x) // t0 := X ^ 2 + t1 := new(fiat.P256Element).Square(&p.y) // t1 := Y ^ 2 + t2 := new(fiat.P256Element).Square(&p.z) // t2 := Z ^ 2 + t3 := new(fiat.P256Element).Mul(&p.x, &p.y) // t3 := X * Y + t3.Add(t3, t3) // t3 := t3 + t3 + z3 := new(fiat.P256Element).Mul(&p.x, &p.z) // Z3 := X * Z + z3.Add(z3, z3) // Z3 := Z3 + Z3 + y3 := new(fiat.P256Element).Mul(p256B(), t2) // Y3 := b * t2 + y3.Sub(y3, z3) // Y3 := Y3 - Z3 + x3 := new(fiat.P256Element).Add(y3, y3) // X3 := Y3 + Y3 + y3.Add(x3, y3) // Y3 := X3 + Y3 + x3.Sub(t1, y3) // X3 := t1 - Y3 + y3.Add(t1, y3) // Y3 := t1 + Y3 + y3.Mul(x3, y3) // Y3 := X3 * Y3 + x3.Mul(x3, t3) // X3 := X3 * t3 + t3.Add(t2, t2) // t3 := t2 + t2 + t2.Add(t2, t3) // t2 := t2 + t3 + z3.Mul(p256B(), z3) // Z3 := b * Z3 + z3.Sub(z3, t2) // Z3 := Z3 - t2 + z3.Sub(z3, t0) // Z3 := Z3 - t0 + t3.Add(z3, z3) // t3 := Z3 + Z3 + z3.Add(z3, t3) // Z3 := Z3 + t3 + t3.Add(t0, t0) // t3 := t0 + t0 + t0.Add(t3, t0) // t0 := t3 + t0 + t0.Sub(t0, t2) // t0 := t0 - t2 + t0.Mul(t0, z3) // t0 := t0 * Z3 + y3.Add(y3, t0) // Y3 := Y3 + t0 + t0.Mul(&p.y, &p.z) // t0 := Y * Z + t0.Add(t0, t0) // t0 := t0 + t0 + z3.Mul(t0, z3) // Z3 := t0 * Z3 + x3.Sub(x3, z3) // X3 := X3 - Z3 + z3.Mul(t0, t1) // Z3 := t0 * t1 + z3.Add(z3, z3) // Z3 := Z3 + Z3 + z3.Add(z3, z3) // Z3 := Z3 + Z3 + + q.x.Set(x3) + q.y.Set(y3) + q.z.Set(z3) + return q +} + +// p256AffinePoint is a point in affine coordinates (x, y). x and y are still +// Montgomery domain elements. The point can't be the point at infinity. +type p256AffinePoint struct { + x, y fiat.P256Element +} + +func (p *p256AffinePoint) Projective() *P256Point { + pp := &P256Point{x: p.x, y: p.y} + pp.z.One() + return pp +} + +// AddAffine sets q = p1 + p2, if infinity == 0, and to p1 if infinity == 1. +// p2 can't be the point at infinity as it can't be represented in affine +// coordinates, instead callers can set p2 to an arbitrary point and set +// infinity to 1. +func (q *P256Point) AddAffine(p1 *P256Point, p2 *p256AffinePoint, infinity int) *P256Point { + // Complete mixed addition formula for a = -3 from "Complete addition + // formulas for prime order elliptic curves" + // (https://eprint.iacr.org/2015/1060), Algorithm 5. + + t0 := new(fiat.P256Element).Mul(&p1.x, &p2.x) // t0 ← X1 · X2 + t1 := new(fiat.P256Element).Mul(&p1.y, &p2.y) // t1 ← Y1 · Y2 + t3 := new(fiat.P256Element).Add(&p2.x, &p2.y) // t3 ← X2 + Y2 + t4 := new(fiat.P256Element).Add(&p1.x, &p1.y) // t4 ← X1 + Y1 + t3.Mul(t3, t4) // t3 ← t3 · t4 + t4.Add(t0, t1) // t4 ← t0 + t1 + t3.Sub(t3, t4) // t3 ← t3 − t4 + t4.Mul(&p2.y, &p1.z) // t4 ← Y2 · Z1 + t4.Add(t4, &p1.y) // t4 ← t4 + Y1 + y3 := new(fiat.P256Element).Mul(&p2.x, &p1.z) // Y3 ← X2 · Z1 + y3.Add(y3, &p1.x) // Y3 ← Y3 + X1 + z3 := new(fiat.P256Element).Mul(p256B(), &p1.z) // Z3 ← b · Z1 + x3 := new(fiat.P256Element).Sub(y3, z3) // X3 ← Y3 − Z3 + z3.Add(x3, x3) // Z3 ← X3 + X3 + x3.Add(x3, z3) // X3 ← X3 + Z3 + z3.Sub(t1, x3) // Z3 ← t1 − X3 + x3.Add(t1, x3) // X3 ← t1 + X3 + y3.Mul(p256B(), y3) // Y3 ← b · Y3 + t1.Add(&p1.z, &p1.z) // t1 ← Z1 + Z1 + t2 := new(fiat.P256Element).Add(t1, &p1.z) // t2 ← t1 + Z1 + y3.Sub(y3, t2) // Y3 ← Y3 − t2 + y3.Sub(y3, t0) // Y3 ← Y3 − t0 + t1.Add(y3, y3) // t1 ← Y3 + Y3 + y3.Add(t1, y3) // Y3 ← t1 + Y3 + t1.Add(t0, t0) // t1 ← t0 + t0 + t0.Add(t1, t0) // t0 ← t1 + t0 + t0.Sub(t0, t2) // t0 ← t0 − t2 + t1.Mul(t4, y3) // t1 ← t4 · Y3 + t2.Mul(t0, y3) // t2 ← t0 · Y3 + y3.Mul(x3, z3) // Y3 ← X3 · Z3 + y3.Add(y3, t2) // Y3 ← Y3 + t2 + x3.Mul(t3, x3) // X3 ← t3 · X3 + x3.Sub(x3, t1) // X3 ← X3 − t1 + z3.Mul(t4, z3) // Z3 ← t4 · Z3 + t1.Mul(t3, t0) // t1 ← t3 · t0 + z3.Add(z3, t1) // Z3 ← Z3 + t1 + + q.x.Select(&p1.x, x3, infinity) + q.y.Select(&p1.y, y3, infinity) + q.z.Select(&p1.z, z3, infinity) + return q +} + +// Select sets q to p1 if cond == 1, and to p2 if cond == 0. +func (q *P256Point) Select(p1, p2 *P256Point, cond int) *P256Point { + q.x.Select(&p1.x, &p2.x, cond) + q.y.Select(&p1.y, &p2.y, cond) + q.z.Select(&p1.z, &p2.z, cond) + return q +} + +// p256OrdElement is a P-256 scalar field element in [0, ord(G)-1] in the +// Montgomery domain (with R 2²⁵⁶) as four uint64 limbs in little-endian order. +type p256OrdElement [4]uint64 + +// SetBytes sets s to the big-endian value of x, reducing it as necessary. +func (s *p256OrdElement) SetBytes(x []byte) (*p256OrdElement, error) { + if len(x) != 32 { + return nil, errors.New("invalid scalar length") + } + + s[0] = byteorder.BEUint64(x[24:]) + s[1] = byteorder.BEUint64(x[16:]) + s[2] = byteorder.BEUint64(x[8:]) + s[3] = byteorder.BEUint64(x[:]) + + // Ensure s is in the range [0, ord(G)-1]. Since 2 * ord(G) > 2²⁵⁶, we can + // just conditionally subtract ord(G), keeping the result if it doesn't + // underflow. + t0, b := bits.Sub64(s[0], 0xf3b9cac2fc632551, 0) + t1, b := bits.Sub64(s[1], 0xbce6faada7179e84, b) + t2, b := bits.Sub64(s[2], 0xffffffffffffffff, b) + t3, b := bits.Sub64(s[3], 0xffffffff00000000, b) + tMask := b - 1 // zero if subtraction underflowed + s[0] ^= (t0 ^ s[0]) & tMask + s[1] ^= (t1 ^ s[1]) & tMask + s[2] ^= (t2 ^ s[2]) & tMask + s[3] ^= (t3 ^ s[3]) & tMask + + return s, nil +} + +func (s *p256OrdElement) Bytes() []byte { + var out [32]byte + byteorder.BEPutUint64(out[24:], s[0]) + byteorder.BEPutUint64(out[16:], s[1]) + byteorder.BEPutUint64(out[8:], s[2]) + byteorder.BEPutUint64(out[:], s[3]) + return out[:] +} + +// Rsh returns the 64 least significant bits of x >> n. n must be lower +// than 256. The value of n leaks through timing side-channels. +func (s *p256OrdElement) Rsh(n int) uint64 { + i := n / 64 + n = n % 64 + res := s[i] >> n + // Shift in the more significant limb, if present. + if i := i + 1; i < len(s) { + res |= s[i] << (64 - n) + } + return res +} + +// p256Table is a table of the first 16 multiples of a point. Points are stored +// at an index offset of -1 so [8]P is at index 7, P is at 0, and [16]P is at 15. +// [0]P is the point at infinity and it's not stored. +type p256Table [16]P256Point + +// Select selects the n-th multiple of the table base point into p. It works in +// constant time. n must be in [0, 16]. If n is 0, p is set to the identity point. +func (table *p256Table) Select(p *P256Point, n uint8) { + if n > 16 { + panic("nistec: internal error: p256Table called with out-of-bounds value") + } + p.Set(NewP256Point()) + for i := uint8(1); i <= 16; i++ { + cond := subtle.ConstantTimeByteEq(i, n) + p.Select(&table[i-1], p, cond) + } +} + +// Compute populates the table to the first 16 multiples of q. +func (table *p256Table) Compute(q *P256Point) *p256Table { + table[0].Set(q) + for i := 1; i < 16; i += 2 { + table[i].Double(&table[i/2]) + if i+1 < 16 { + table[i+1].Add(&table[i], q) + } + } + return table +} + +func boothW5(in uint64) (uint8, int) { + s := ^((in >> 5) - 1) + d := (1 << 6) - in - 1 + d = (d & s) | (in & (^s)) + d = (d >> 1) + (d & 1) + return uint8(d), int(s & 1) +} + +// ScalarMult sets r = scalar * q, where scalar is a 32-byte big endian value, +// and returns r. If scalar is not 32 bytes long, ScalarMult returns an error +// and the receiver is unchanged. +func (p *P256Point) ScalarMult(q *P256Point, scalar []byte) (*P256Point, error) { + s, err := new(p256OrdElement).SetBytes(scalar) + if err != nil { + return nil, err + } + + // Start scanning the window from the most significant bits. We move by + // 5 bits at a time and need to finish at -1, so -1 + 5 * 51 = 254. + index := 254 + + sel, sign := boothW5(s.Rsh(index)) + // sign is always zero because the boothW5 input here is at + // most two bits long, so the top bit is never set. + _ = sign + + // Neither Select nor Add have exceptions for the point at infinity / + // selector zero, so we don't need to check for it here or in the loop. + table := new(p256Table).Compute(q) + table.Select(p, sel) + + t := NewP256Point() + for index >= 4 { + index -= 5 + + p.Double(p) + p.Double(p) + p.Double(p) + p.Double(p) + p.Double(p) + + if index >= 0 { + sel, sign = boothW5(s.Rsh(index) & 0b111111) + } else { + // Booth encoding considers a virtual zero bit at index -1, + // so we shift left the least significant limb. + wvalue := (s[0] << 1) & 0b111111 + sel, sign = boothW5(wvalue) + } + + table.Select(t, sel) + t.Negate(sign) + p.Add(p, t) + } + + return p, nil +} + +// Negate sets p to -p, if cond == 1, and to p if cond == 0. +func (p *P256Point) Negate(cond int) *P256Point { + negY := new(fiat.P256Element) + negY.Sub(negY, &p.y) + p.y.Select(negY, &p.y, cond) + return p +} + +// p256AffineTable is a table of the first 32 multiples of a point. Points are +// stored at an index offset of -1 like in p256Table, and [0]P is not stored. +type p256AffineTable [32]p256AffinePoint + +// Select selects the n-th multiple of the table base point into p. It works in +// constant time. n can be in [0, 32], but (unlike p256Table.Select) if n is 0, +// p is set to an undefined value. +func (table *p256AffineTable) Select(p *p256AffinePoint, n uint8) { + if n > 32 { + panic("nistec: internal error: p256AffineTable.Select called with out-of-bounds value") + } + for i := uint8(1); i <= 32; i++ { + cond := subtle.ConstantTimeByteEq(i, n) + p.x.Select(&table[i-1].x, &p.x, cond) + p.y.Select(&table[i-1].y, &p.y, cond) + } +} + +// p256GeneratorTables is a series of precomputed multiples of G, the canonical +// generator. The first p256AffineTable contains multiples of G. The second one +// multiples of [2⁶]G, the third one of [2¹²]G, and so on, where each successive +// table is the previous table doubled six times. Six is the width of the +// sliding window used in ScalarBaseMult, and having each table already +// pre-doubled lets us avoid the doublings between windows entirely. This table +// aliases into p256PrecomputedEmbed. +var p256GeneratorTables *[43]p256AffineTable + +func init() { + p256GeneratorTablesPtr := unsafe.Pointer(&p256PrecomputedEmbed) + if cpu.BigEndian { + var newTable [43 * 32 * 2 * 4]uint64 + for i, x := range (*[43 * 32 * 2 * 4][8]byte)(p256GeneratorTablesPtr) { + newTable[i] = byteorder.LEUint64(x[:]) + } + p256GeneratorTablesPtr = unsafe.Pointer(&newTable) + } + p256GeneratorTables = (*[43]p256AffineTable)(p256GeneratorTablesPtr) +} + +func boothW6(in uint64) (uint8, int) { + s := ^((in >> 6) - 1) + d := (1 << 7) - in - 1 + d = (d & s) | (in & (^s)) + d = (d >> 1) + (d & 1) + return uint8(d), int(s & 1) +} + +// ScalarBaseMult sets p = scalar * generator, where scalar is a 32-byte big +// endian value, and returns r. If scalar is not 32 bytes long, ScalarBaseMult +// returns an error and the receiver is unchanged. +func (p *P256Point) ScalarBaseMult(scalar []byte) (*P256Point, error) { + // This function works like ScalarMult above, but the table is fixed and + // "pre-doubled" for each iteration, so instead of doubling we move to the + // next table at each iteration. + + s, err := new(p256OrdElement).SetBytes(scalar) + if err != nil { + return nil, err + } + + // Start scanning the window from the most significant bits. We move by + // 6 bits at a time and need to finish at -1, so -1 + 6 * 42 = 251. + index := 251 + + sel, sign := boothW6(s.Rsh(index)) + // sign is always zero because the boothW6 input here is at + // most five bits long, so the top bit is never set. + _ = sign + + t := &p256AffinePoint{} + table := &p256GeneratorTables[(index+1)/6] + table.Select(t, sel) + + // Select's output is undefined if the selector is zero, when it should be + // the point at infinity (because infinity can't be represented in affine + // coordinates). Here we conditionally set p to the infinity if sel is zero. + // In the loop, that's handled by AddAffine. + selIsZero := subtle.ConstantTimeByteEq(sel, 0) + p.Select(NewP256Point(), t.Projective(), selIsZero) + + for index >= 5 { + index -= 6 + + if index >= 0 { + sel, sign = boothW6(s.Rsh(index) & 0b1111111) + } else { + // Booth encoding considers a virtual zero bit at index -1, + // so we shift left the least significant limb. + wvalue := (s[0] << 1) & 0b1111111 + sel, sign = boothW6(wvalue) + } + + table := &p256GeneratorTables[(index+1)/6] + table.Select(t, sel) + t.Negate(sign) + selIsZero := subtle.ConstantTimeByteEq(sel, 0) + p.AddAffine(p, t, selIsZero) + } + + return p, nil +} + +// Negate sets p to -p, if cond == 1, and to p if cond == 0. +func (p *p256AffinePoint) Negate(cond int) *p256AffinePoint { + negY := new(fiat.P256Element) + negY.Sub(negY, &p.y) + p.y.Select(negY, &p.y, cond) + return p +} + +// p256Sqrt sets e to a square root of x. If x is not a square, p256Sqrt returns +// false and e is unchanged. e and x can overlap. +func p256Sqrt(e, x *fiat.P256Element) (isSquare bool) { + t0, t1 := new(fiat.P256Element), new(fiat.P256Element) + + // Since p = 3 mod 4, exponentiation by (p + 1) / 4 yields a square root candidate. + // + // The sequence of 7 multiplications and 253 squarings is derived from the + // following addition chain generated with github.com/mmcloughlin/addchain v0.4.0. + // + // _10 = 2*1 + // _11 = 1 + _10 + // _1100 = _11 << 2 + // _1111 = _11 + _1100 + // _11110000 = _1111 << 4 + // _11111111 = _1111 + _11110000 + // x16 = _11111111 << 8 + _11111111 + // x32 = x16 << 16 + x16 + // return ((x32 << 32 + 1) << 96 + 1) << 94 + // + p256Square(t0, x, 1) + t0.Mul(x, t0) + p256Square(t1, t0, 2) + t0.Mul(t0, t1) + p256Square(t1, t0, 4) + t0.Mul(t0, t1) + p256Square(t1, t0, 8) + t0.Mul(t0, t1) + p256Square(t1, t0, 16) + t0.Mul(t0, t1) + p256Square(t0, t0, 32) + t0.Mul(x, t0) + p256Square(t0, t0, 96) + t0.Mul(x, t0) + p256Square(t0, t0, 94) + + // Check if the candidate t0 is indeed a square root of x. + t1.Square(t0) + if t1.Equal(x) != 1 { + return false + } + e.Set(t0) + return true +} + +// p256Square sets e to the square of x, repeated n times > 1. +func p256Square(e, x *fiat.P256Element, n int) { + e.Square(x) + for i := 1; i < n; i++ { + e.Square(e) + } +} diff --git a/crypto/internal/nistec/p256_asm.go b/crypto/internal/fips140/nistec/p256_asm.go similarity index 94% rename from crypto/internal/nistec/p256_asm.go rename to crypto/internal/fips140/nistec/p256_asm.go index 7f6c03d3224..6e79082a550 100644 --- a/crypto/internal/nistec/p256_asm.go +++ b/crypto/internal/fips140/nistec/p256_asm.go @@ -15,12 +15,12 @@ package nistec import ( - _ "embed" "errors" - "github.com/runZeroInc/excrypto/internal/byteorder" "math/bits" "runtime" "unsafe" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" ) // p256Element is a P-256 base field element in [0, P-1] in the Montgomery @@ -178,6 +178,28 @@ func p256LessThanP(x *p256Element) int { return int(b) } +func p256BigToLittle(l *p256Element, b *[32]byte) { + bytesToLimbs((*[4]uint64)(l), b) +} + +func bytesToLimbs(l *[4]uint64, b *[32]byte) { + l[0] = byteorder.BEUint64(b[24:]) + l[1] = byteorder.BEUint64(b[16:]) + l[2] = byteorder.BEUint64(b[8:]) + l[3] = byteorder.BEUint64(b[:]) +} + +func p256LittleToBig(b *[32]byte, l *p256Element) { + limbsToBytes(b, (*[4]uint64)(l)) +} + +func limbsToBytes(b *[32]byte, l *[4]uint64) { + byteorder.BEPutUint64(b[24:], l[0]) + byteorder.BEPutUint64(b[16:], l[1]) + byteorder.BEPutUint64(b[8:], l[2]) + byteorder.BEPutUint64(b[:], l[3]) +} + // p256Add sets res = x + y. func p256Add(res, x, y *p256Element) { var c, b uint64 @@ -277,18 +299,6 @@ func p256NegCond(val *p256Element, cond int) //go:noescape func p256MovCond(res, a, b *P256Point, cond int) -//go:noescape -func p256BigToLittle(res *p256Element, in *[32]byte) - -//go:noescape -func p256LittleToBig(res *[32]byte, in *p256Element) - -//go:noescape -func p256OrdBigToLittle(res *p256OrdElement, in *[32]byte) - -//go:noescape -func p256OrdLittleToBig(res *[32]byte, in *p256OrdElement) - // p256Table is a table of the first 16 multiples of a point. Points are stored // at an index offset of -1 so [8]P is at index 7, P is at 0, and [16]P is at 15. // [0]P is the point at infinity and it's not stored. @@ -314,25 +324,21 @@ type p256AffineTable [32]p256AffinePoint // generator. The first p256AffineTable contains multiples of G. The second one // multiples of [2⁶]G, the third one of [2¹²]G, and so on, where each successive // table is the previous table doubled six times. Six is the width of the -// sliding window used in p256ScalarMult, and having each table already +// sliding window used in p256ScalarBaseMult, and having each table already // pre-doubled lets us avoid the doublings between windows entirely. This table -// MUST NOT be modified, as it aliases into p256PrecomputedEmbed below. +// aliases into p256PrecomputedEmbed. var p256Precomputed *[43]p256AffineTable -//go:embed p256_asm_table.bin -var p256PrecomputedEmbed string - func init() { - p256PrecomputedPtr := (*unsafe.Pointer)(unsafe.Pointer(&p256PrecomputedEmbed)) + p256PrecomputedPtr := unsafe.Pointer(&p256PrecomputedEmbed) if runtime.GOARCH == "s390x" { var newTable [43 * 32 * 2 * 4]uint64 - for i, x := range (*[43 * 32 * 2 * 4][8]byte)(*p256PrecomputedPtr) { - newTable[i] = byteorder.LeUint64(x[:]) + for i, x := range (*[43 * 32 * 2 * 4][8]byte)(p256PrecomputedPtr) { + newTable[i] = byteorder.LEUint64(x[:]) } - newTablePtr := unsafe.Pointer(&newTable) - p256PrecomputedPtr = &newTablePtr + p256PrecomputedPtr = unsafe.Pointer(&newTable) } - p256Precomputed = (*[43]p256AffineTable)(*p256PrecomputedPtr) + p256Precomputed = (*[43]p256AffineTable)(p256PrecomputedPtr) } // p256SelectAffine sets res to the point at index idx in the table. @@ -379,6 +385,14 @@ func p256OrdReduce(s *p256OrdElement) { s[3] ^= (t3 ^ s[3]) & tMask } +func p256OrdLittleToBig(b *[32]byte, l *p256OrdElement) { + limbsToBytes(b, (*[4]uint64)(l)) +} + +func p256OrdBigToLittle(l *p256OrdElement, b *[32]byte) { + bytesToLimbs((*[4]uint64)(l), b) +} + // Add sets q = p1 + p2, and returns q. The points may overlap. func (q *P256Point) Add(r1, r2 *P256Point) *P256Point { var sum, double P256Point diff --git a/crypto/internal/nistec/p256_asm_amd64.s b/crypto/internal/fips140/nistec/p256_asm_amd64.s similarity index 93% rename from crypto/internal/nistec/p256_asm_amd64.s rename to crypto/internal/fips140/nistec/p256_asm_amd64.s index 06676bbc785..64894891e98 100644 --- a/crypto/internal/nistec/p256_asm_amd64.s +++ b/crypto/internal/fips140/nistec/p256_asm_amd64.s @@ -1,39 +1,9 @@ -// Code generated by command: go run p256_asm_amd64.go -out ../p256_asm_amd64.s -pkg nistec. DO NOT EDIT. +// Code generated by command: go run p256_asm.go -out ../p256_asm_amd64.s. DO NOT EDIT. //go:build !purego #include "textflag.h" -// func p256OrdLittleToBig(res *[32]byte, in *p256OrdElement) -TEXT ·p256OrdLittleToBig(SB), NOSPLIT, $0-16 - JMP ·p256BigToLittle(SB) - -// func p256OrdBigToLittle(res *p256OrdElement, in *[32]byte) -TEXT ·p256OrdBigToLittle(SB), NOSPLIT, $0-16 - JMP ·p256BigToLittle(SB) - -// func p256LittleToBig(res *[32]byte, in *p256Element) -TEXT ·p256LittleToBig(SB), NOSPLIT, $0-16 - JMP ·p256BigToLittle(SB) - -// func p256BigToLittle(res *p256Element, in *[32]byte) -TEXT ·p256BigToLittle(SB), NOSPLIT, $0-16 - MOVQ res+0(FP), DI - MOVQ in+8(FP), SI - MOVQ (SI), R8 - MOVQ 8(SI), R9 - MOVQ 16(SI), R10 - MOVQ 24(SI), R11 - BSWAPQ R8 - BSWAPQ R9 - BSWAPQ R10 - BSWAPQ R11 - MOVQ R11, (DI) - MOVQ R10, 8(DI) - MOVQ R9, 16(DI) - MOVQ R8, 24(DI) - RET - // func p256MovCond(res *P256Point, a *P256Point, b *P256Point, cond int) // Requires: SSE2 TEXT ·p256MovCond(SB), NOSPLIT, $0-32 @@ -1173,9 +1143,9 @@ ordSqrLoop: JNE ordSqrLoop RET -// func p256SubInternalExCrypto() +// func p256SubInternal() // Requires: CMOV -TEXT p256SubInternalExCrypto(SB), NOSPLIT, $0 +TEXT p256SubInternal(SB), NOSPLIT, $0 XORQ AX, AX SUBQ R14, R10 SBBQ R15, R11 @@ -1197,9 +1167,9 @@ TEXT p256SubInternalExCrypto(SB), NOSPLIT, $0 CMOVQEQ R9, R13 RET -// func p256MulInternalExCrypto() +// func p256MulInternal() // Requires: CMOV -TEXT p256MulInternalExCrypto(SB), NOSPLIT, $8 +TEXT p256MulInternal(SB), NOSPLIT, $8 MOVQ R10, AX MULQ R14 MOVQ AX, BX @@ -1374,9 +1344,9 @@ TEXT p256MulInternalExCrypto(SB), NOSPLIT, $8 CMOVQCS R9, R13 RET -// func p256SqrInternalExCrypto() +// func p256SqrInternal() // Requires: CMOV -TEXT p256SqrInternalExCrypto(SB), NOSPLIT, $8 +TEXT p256SqrInternal(SB), NOSPLIT, $8 MOVQ R10, AX MULQ R11 MOVQ AX, CX @@ -1600,7 +1570,7 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 72(SP), R11 MOVQ 80(SP), R12 MOVQ 88(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 288(SP) MOVQ R11, 296(SP) MOVQ R12, 304(SP) @@ -1609,12 +1579,12 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 104(SP), R15 MOVQ 112(SP), DI MOVQ 120(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ (SP), R14 MOVQ 8(SP), R15 MOVQ 16(SP), DI MOVQ 24(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 320(SP) MOVQ R11, 328(SP) MOVQ R12, 336(SP) @@ -1623,7 +1593,7 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 72(SP), R15 MOVQ 80(SP), DI MOVQ 88(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 224(SP) MOVQ R11, 232(SP) MOVQ R12, 240(SP) @@ -1632,12 +1602,12 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 296(SP), R11 MOVQ 304(SP), R12 MOVQ 312(SP), R13 - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 128(SP), R14 MOVQ 136(SP), R15 MOVQ 144(SP), DI MOVQ 152(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 256(SP) MOVQ R11, 264(SP) MOVQ R12, 272(SP) @@ -1646,12 +1616,12 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 40(SP), R15 MOVQ 48(SP), DI MOVQ 56(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 352(SP) MOVQ R11, 360(SP) MOVQ R12, 368(SP) MOVQ R13, 376(SP) - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 416(SP) MOVQ R11, 424(SP) MOVQ R12, 432(SP) @@ -1660,7 +1630,7 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 328(SP), R11 MOVQ 336(SP), R12 MOVQ 344(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 384(SP) MOVQ R11, 392(SP) MOVQ R12, 400(SP) @@ -1669,7 +1639,7 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 328(SP), R15 MOVQ 336(SP), DI MOVQ 344(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 448(SP) MOVQ R11, 456(SP) MOVQ R12, 464(SP) @@ -1678,7 +1648,7 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 40(SP), R15 MOVQ 48(SP), DI MOVQ 56(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 256(SP) MOVQ R11, 264(SP) MOVQ R12, 272(SP) @@ -1691,7 +1661,7 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 392(SP), R15 MOVQ 400(SP), DI MOVQ 408(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 320(SP) MOVQ R11, 328(SP) MOVQ R12, 336(SP) @@ -1719,12 +1689,12 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 424(SP), R11 MOVQ 432(SP), R12 MOVQ 440(SP), R13 - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 448(SP), R14 MOVQ 456(SP), R15 MOVQ 464(SP), DI MOVQ 472(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 160(SP) MOVQ R11, 168(SP) MOVQ R12, 176(SP) @@ -1737,17 +1707,17 @@ TEXT ·p256PointAddAffineAsm(SB), $512-48 MOVQ 328(SP), R11 MOVQ 336(SP), R12 MOVQ 344(SP), R13 - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 352(SP), R14 MOVQ 360(SP), R15 MOVQ 368(SP), DI MOVQ 376(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 256(SP), R14 MOVQ 264(SP), R15 MOVQ 272(SP), DI MOVQ 280(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 192(SP) MOVQ R11, 200(SP) MOVQ R12, 208(SP) @@ -1845,9 +1815,9 @@ DATA p256one<>+16(SB)/8, $0xffffffffffffffff DATA p256one<>+24(SB)/8, $0x00000000fffffffe GLOBL p256one<>(SB), RODATA, $32 -// func p256IsZeroExCrypto() +// func p256IsZero() // Requires: CMOV -TEXT p256IsZeroExCrypto(SB), NOSPLIT, $0 +TEXT p256IsZero(SB), NOSPLIT, $0 // AX contains a flag that is set if the input is zero. XORQ AX, AX MOVQ $0x00000001, R15 @@ -1914,7 +1884,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 168(SP), R11 MOVQ 176(SP), R12 MOVQ 184(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 448(SP) MOVQ R11, 456(SP) MOVQ R12, 464(SP) @@ -1923,12 +1893,12 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 168(SP), R15 MOVQ 176(SP), DI MOVQ 184(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 32(SP), R14 MOVQ 40(SP), R15 MOVQ 48(SP), DI MOVQ 56(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 352(SP) MOVQ R11, 360(SP) MOVQ R12, 368(SP) @@ -1937,7 +1907,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 72(SP), R11 MOVQ 80(SP), R12 MOVQ 88(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 416(SP) MOVQ R11, 424(SP) MOVQ R12, 432(SP) @@ -1946,12 +1916,12 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 72(SP), R15 MOVQ 80(SP), DI MOVQ 88(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 128(SP), R14 MOVQ 136(SP), R15 MOVQ 144(SP), DI MOVQ 152(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 384(SP) MOVQ R11, 392(SP) MOVQ R12, 400(SP) @@ -1960,12 +1930,12 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 360(SP), R15 MOVQ 368(SP), DI MOVQ 376(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 512(SP) MOVQ R11, 520(SP) MOVQ R12, 528(SP) MOVQ R13, 536(SP) - CALL p256IsZeroExCrypto(SB) + CALL p256IsZero(SB) MOVQ AX, 648(SP) MOVQ 448(SP), R10 MOVQ 456(SP), R11 @@ -1975,7 +1945,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 8(SP), R15 MOVQ 16(SP), DI MOVQ 24(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 288(SP) MOVQ R11, 296(SP) MOVQ R12, 304(SP) @@ -1988,7 +1958,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 104(SP), R15 MOVQ 112(SP), DI MOVQ 120(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 320(SP) MOVQ R11, 328(SP) MOVQ R12, 336(SP) @@ -1997,19 +1967,19 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 296(SP), R15 MOVQ 304(SP), DI MOVQ 312(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 480(SP) MOVQ R11, 488(SP) MOVQ R12, 496(SP) MOVQ R13, 504(SP) - CALL p256IsZeroExCrypto(SB) + CALL p256IsZero(SB) ANDQ 648(SP), AX MOVQ AX, 648(SP) MOVQ 512(SP), R10 MOVQ 520(SP), R11 MOVQ 528(SP), R12 MOVQ 536(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 576(SP) MOVQ R11, 584(SP) MOVQ R12, 592(SP) @@ -2018,7 +1988,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 488(SP), R11 MOVQ 496(SP), R12 MOVQ 504(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 544(SP) MOVQ R11, 552(SP) MOVQ R12, 560(SP) @@ -2027,7 +1997,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 488(SP), R15 MOVQ 496(SP), DI MOVQ 504(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 608(SP) MOVQ R11, 616(SP) MOVQ R12, 624(SP) @@ -2036,7 +2006,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 360(SP), R15 MOVQ 368(SP), DI MOVQ 376(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 384(SP) MOVQ R11, 392(SP) MOVQ R12, 400(SP) @@ -2049,12 +2019,12 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 168(SP), R15 MOVQ 176(SP), DI MOVQ 184(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 480(SP), R14 MOVQ 488(SP), R15 MOVQ 496(SP), DI MOVQ 504(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 256(SP) MOVQ R11, 264(SP) MOVQ R12, 272(SP) @@ -2067,7 +2037,7 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 296(SP), R15 MOVQ 304(SP), DI MOVQ 312(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 320(SP) MOVQ R11, 328(SP) MOVQ R12, 336(SP) @@ -2095,12 +2065,12 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 584(SP), R11 MOVQ 592(SP), R12 MOVQ 600(SP), R13 - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 608(SP), R14 MOVQ 616(SP), R15 MOVQ 624(SP), DI MOVQ 632(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 192(SP) MOVQ R11, 200(SP) MOVQ R12, 208(SP) @@ -2113,17 +2083,17 @@ TEXT ·p256PointAddAsm(SB), $680-32 MOVQ 328(SP), R11 MOVQ 336(SP), R12 MOVQ 344(SP), R13 - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 512(SP), R14 MOVQ 520(SP), R15 MOVQ 528(SP), DI MOVQ 536(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 384(SP), R14 MOVQ 392(SP), R15 MOVQ 400(SP), DI MOVQ 408(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ R10, 224(SP) MOVQ R11, 232(SP) MOVQ R12, 240(SP) @@ -2174,7 +2144,7 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ 72(SP), R11 MOVQ 80(SP), R12 MOVQ 88(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 160(SP) MOVQ R11, 168(SP) MOVQ R12, 176(SP) @@ -2214,7 +2184,7 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ 40(SP), R15 MOVQ 48(SP), DI MOVQ 56(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) XORQ AX, AX ADDQ R10, R10 ADCQ R11, R11 @@ -2249,12 +2219,12 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ 168(SP), R15 MOVQ 176(SP), DI MOVQ 184(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 128(SP), R14 MOVQ 136(SP), R15 MOVQ 144(SP), DI MOVQ 152(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 128(SP) MOVQ R11, 136(SP) MOVQ R12, 144(SP) @@ -2336,12 +2306,12 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ R15, R11 MOVQ DI, R12 MOVQ SI, R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ R10, 96(SP) MOVQ R11, 104(SP) MOVQ R12, 112(SP) MOVQ R13, 120(SP) - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) // Divide by 2 XORQ AX, AX @@ -2378,7 +2348,7 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ 104(SP), R15 MOVQ 112(SP), DI MOVQ 120(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ R10, 96(SP) MOVQ R11, 104(SP) MOVQ R12, 112(SP) @@ -2410,12 +2380,12 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ 136(SP), R11 MOVQ 144(SP), R12 MOVQ 152(SP), R13 - CALL p256SqrInternalExCrypto(SB) + CALL p256SqrInternal(SB) MOVQ 192(SP), R14 MOVQ 200(SP), R15 MOVQ 208(SP), DI MOVQ 216(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 224(SP), AX // Store x @@ -2431,17 +2401,17 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $256-16 MOVQ 104(SP), R11 MOVQ 112(SP), R12 MOVQ 120(SP), R13 - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 128(SP), R14 MOVQ 136(SP), R15 MOVQ 144(SP), DI MOVQ 152(SP), SI - CALL p256MulInternalExCrypto(SB) + CALL p256MulInternal(SB) MOVQ 32(SP), R14 MOVQ 40(SP), R15 MOVQ 48(SP), DI MOVQ 56(SP), SI - CALL p256SubInternalExCrypto(SB) + CALL p256SubInternal(SB) MOVQ 224(SP), AX // Store y diff --git a/crypto/internal/nistec/p256_asm_arm64.s b/crypto/internal/fips140/nistec/p256_asm_arm64.s similarity index 88% rename from crypto/internal/nistec/p256_asm_arm64.s rename to crypto/internal/fips140/nistec/p256_asm_arm64.s index e7c8689d07a..33da24508e2 100644 --- a/crypto/internal/nistec/p256_asm_arm64.s +++ b/crypto/internal/fips140/nistec/p256_asm_arm64.s @@ -65,35 +65,6 @@ GLOBL p256ordK0<>(SB), 8, $8 GLOBL p256ord<>(SB), 8, $32 GLOBL p256one<>(SB), 8, $32 -/* ---------------------------------------*/ -// func p256OrdLittleToBig(res *[32]byte, in *p256OrdElement) -TEXT ·p256OrdLittleToBig(SB),NOSPLIT,$0 - JMP ·p256BigToLittle(SB) -/* ---------------------------------------*/ -// func p256OrdBigToLittle(res *p256OrdElement, in *[32]byte) -TEXT ·p256OrdBigToLittle(SB),NOSPLIT,$0 - JMP ·p256BigToLittle(SB) -/* ---------------------------------------*/ -// func p256LittleToBig(res *[32]byte, in *p256Element) -TEXT ·p256LittleToBig(SB),NOSPLIT,$0 - JMP ·p256BigToLittle(SB) -/* ---------------------------------------*/ -// func p256BigToLittle(res *p256Element, in *[32]byte) -TEXT ·p256BigToLittle(SB),NOSPLIT,$0 - MOVD res+0(FP), res_ptr - MOVD in+8(FP), a_ptr - - LDP 0*16(a_ptr), (acc0, acc1) - LDP 1*16(a_ptr), (acc2, acc3) - - REV acc0, acc0 - REV acc1, acc1 - REV acc2, acc2 - REV acc3, acc3 - - STP (acc3, acc2), 0*16(res_ptr) - STP (acc1, acc0), 1*16(res_ptr) - RET /* ---------------------------------------*/ // func p256MovCond(res, a, b *P256Point, cond int) // If cond == 0 res=b, else res=a @@ -185,7 +156,7 @@ TEXT ·p256Sqr(SB),NOSPLIT,$0 sqrLoop: SUB $1, b_ptr - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) MOVD y0, x0 MOVD y1, x1 MOVD y2, x2 @@ -211,7 +182,7 @@ TEXT ·p256Mul(SB),NOSPLIT,$0 LDP 0*16(b_ptr), (y0, y1) LDP 1*16(b_ptr), (y2, y3) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) STP (y0, y1), 0*16(res_ptr) STP (y2, y3), 1*16(res_ptr) @@ -791,7 +762,7 @@ TEXT ·p256OrdMul(SB),NOSPLIT,$0 RET /* ---------------------------------------*/ -TEXT p256SubInternalExCrypto<>(SB),NOSPLIT,$0 +TEXT p256SubInternal<>(SB),NOSPLIT,$0 SUBS x0, y0, acc0 SBCS x1, y1, acc1 SBCS x2, y2, acc2 @@ -811,7 +782,7 @@ TEXT p256SubInternalExCrypto<>(SB),NOSPLIT,$0 RET /* ---------------------------------------*/ -TEXT p256SqrInternalExCrypto<>(SB),NOSPLIT,$0 +TEXT p256SqrInternal<>(SB),NOSPLIT,$0 // x[1:] * x[0] MUL x0, x1, acc1 UMULH x0, x1, acc2 @@ -920,7 +891,7 @@ TEXT p256SqrInternalExCrypto<>(SB),NOSPLIT,$0 CSEL CS, t3, acc3, y3 RET /* ---------------------------------------*/ -TEXT p256MulInternalExCrypto<>(SB),NOSPLIT,$0 +TEXT p256MulInternal<>(SB),NOSPLIT,$0 // y[0] * x MUL y0, x0, acc0 UMULH y0, x0, acc1 @@ -1147,18 +1118,18 @@ TEXT ·p256PointAddAffineAsm(SB),0,$264-48 STy(y2in) // Begin point add LDx(z1in) - CALL p256SqrInternalExCrypto<>(SB) // z1ˆ2 + CALL p256SqrInternal<>(SB) // z1ˆ2 STy(z1sqr) LDx(x2in) - CALL p256MulInternalExCrypto<>(SB) // x2 * z1ˆ2 + CALL p256MulInternal<>(SB) // x2 * z1ˆ2 LDx(x1in) - CALL p256SubInternalExCrypto<>(SB) // h = u2 - u1 + CALL p256SubInternal<>(SB) // h = u2 - u1 STx(h) LDy(z1in) - CALL p256MulInternalExCrypto<>(SB) // z3 = h * z1 + CALL p256MulInternal<>(SB) // z3 = h * z1 LDP 4*16(a_ptr), (acc0, acc1)// iff select[0] == 0, z3 = z1 LDP 5*16(a_ptr), (acc2, acc3) @@ -1180,49 +1151,49 @@ TEXT ·p256PointAddAffineAsm(SB),0,$264-48 STP (y2, y3), 5*16(t0) LDy(z1sqr) - CALL p256MulInternalExCrypto<>(SB) // z1 ^ 3 + CALL p256MulInternal<>(SB) // z1 ^ 3 LDx(y2in) - CALL p256MulInternalExCrypto<>(SB) // s2 = y2 * z1ˆ3 + CALL p256MulInternal<>(SB) // s2 = y2 * z1ˆ3 STy(s2) LDx(y1in) - CALL p256SubInternalExCrypto<>(SB) // r = s2 - s1 + CALL p256SubInternal<>(SB) // r = s2 - s1 STx(r) - CALL p256SqrInternalExCrypto<>(SB) // rsqr = rˆ2 + CALL p256SqrInternal<>(SB) // rsqr = rˆ2 STy (rsqr) LDx(h) - CALL p256SqrInternalExCrypto<>(SB) // hsqr = hˆ2 + CALL p256SqrInternal<>(SB) // hsqr = hˆ2 STy(hsqr) - CALL p256MulInternalExCrypto<>(SB) // hcub = hˆ3 + CALL p256MulInternal<>(SB) // hcub = hˆ3 STy(hcub) LDx(y1in) - CALL p256MulInternalExCrypto<>(SB) // y1 * hˆ3 + CALL p256MulInternal<>(SB) // y1 * hˆ3 STy(s2) LDP hsqr(0*8), (x0, x1) LDP hsqr(2*8), (x2, x3) LDP 0*16(a_ptr), (y0, y1) LDP 1*16(a_ptr), (y2, y3) - CALL p256MulInternalExCrypto<>(SB) // u1 * hˆ2 + CALL p256MulInternal<>(SB) // u1 * hˆ2 STP (y0, y1), h(0*8) STP (y2, y3), h(2*8) p256MulBy2Inline // u1 * hˆ2 * 2, inline LDy(rsqr) - CALL p256SubInternalExCrypto<>(SB) // rˆ2 - u1 * hˆ2 * 2 + CALL p256SubInternal<>(SB) // rˆ2 - u1 * hˆ2 * 2 MOVD x0, y0 MOVD x1, y1 MOVD x2, y2 MOVD x3, y3 LDx(hcub) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) LDP 0*16(a_ptr), (acc0, acc1) LDP 1*16(a_ptr), (acc2, acc3) @@ -1244,15 +1215,15 @@ TEXT ·p256PointAddAffineAsm(SB),0,$264-48 LDP h(0*8), (y0, y1) LDP h(2*8), (y2, y3) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) LDP r(0*8), (y0, y1) LDP r(2*8), (y2, y3) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) LDP s2(0*8), (x0, x1) LDP s2(2*8), (x2, x3) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) LDP 2*16(a_ptr), (acc0, acc1) LDP 3*16(a_ptr), (acc2, acc3) ANDS $1, hlp1, ZR // iff select[0] == 0, y3 = y1 @@ -1305,7 +1276,7 @@ TEXT ·p256PointDoubleAsm(SB),NOSPLIT,$136-16 // Begin point double LDP 4*16(a_ptr), (x0, x1) LDP 5*16(a_ptr), (x2, x3) - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) STP (y0, y1), zsqr(0*8) STP (y2, y3), zsqr(2*8) @@ -1316,15 +1287,15 @@ TEXT ·p256PointDoubleAsm(SB),NOSPLIT,$136-16 LDx(z1in) LDy(y1in) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) p256MulBy2Inline STx(z3out) LDy(x1in) LDx(zsqr) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) LDy(m) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // Multiply by 3 p256MulBy2Inline @@ -1333,13 +1304,13 @@ TEXT ·p256PointDoubleAsm(SB),NOSPLIT,$136-16 LDy(y1in) p256MulBy2Inline - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) STy(s) MOVD y0, x0 MOVD y1, x1 MOVD y2, x2 MOVD y3, x3 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // Divide by 2 ADDS $-1, y0, t0 @@ -1363,26 +1334,26 @@ TEXT ·p256PointDoubleAsm(SB),NOSPLIT,$136-16 LDx(x1in) LDy(s) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) STy(s) p256MulBy2Inline STx(tmp) LDx(m) - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) LDx(tmp) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) STx(x3out) LDy(s) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) LDy(m) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) LDx(y3out) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) STx(y3out) RET /* ---------------------------------------*/ @@ -1406,26 +1377,26 @@ TEXT ·p256PointAddAsm(SB),0,$392-32 // Begin point add LDx(z2in) - CALL p256SqrInternalExCrypto<>(SB) // z2^2 + CALL p256SqrInternal<>(SB) // z2^2 STy(z2sqr) - CALL p256MulInternalExCrypto<>(SB) // z2^3 + CALL p256MulInternal<>(SB) // z2^3 LDx(y1in) - CALL p256MulInternalExCrypto<>(SB) // s1 = z2ˆ3*y1 + CALL p256MulInternal<>(SB) // s1 = z2ˆ3*y1 STy(s1) LDx(z1in) - CALL p256SqrInternalExCrypto<>(SB) // z1^2 + CALL p256SqrInternal<>(SB) // z1^2 STy(z1sqr) - CALL p256MulInternalExCrypto<>(SB) // z1^3 + CALL p256MulInternal<>(SB) // z1^3 LDx(y2in) - CALL p256MulInternalExCrypto<>(SB) // s2 = z1ˆ3*y2 + CALL p256MulInternal<>(SB) // s2 = z1ˆ3*y2 LDx(s1) - CALL p256SubInternalExCrypto<>(SB) // r = s2 - s1 + CALL p256SubInternal<>(SB) // r = s2 - s1 STx(r) MOVD $1, t2 @@ -1447,16 +1418,16 @@ TEXT ·p256PointAddAsm(SB),0,$392-32 LDx(z2sqr) LDy(x1in) - CALL p256MulInternalExCrypto<>(SB) // u1 = x1 * z2ˆ2 + CALL p256MulInternal<>(SB) // u1 = x1 * z2ˆ2 STy(u1) LDx(z1sqr) LDy(x2in) - CALL p256MulInternalExCrypto<>(SB) // u2 = x2 * z1ˆ2 + CALL p256MulInternal<>(SB) // u2 = x2 * z1ˆ2 STy(u2) LDx(u1) - CALL p256SubInternalExCrypto<>(SB) // h = u2 - u1 + CALL p256SubInternal<>(SB) // h = u2 - u1 STx(h) MOVD $1, t2 @@ -1479,54 +1450,54 @@ TEXT ·p256PointAddAsm(SB),0,$392-32 AND hlp0, hlp1, hlp1 LDx(r) - CALL p256SqrInternalExCrypto<>(SB) // rsqr = rˆ2 + CALL p256SqrInternal<>(SB) // rsqr = rˆ2 STy(rsqr) LDx(h) - CALL p256SqrInternalExCrypto<>(SB) // hsqr = hˆ2 + CALL p256SqrInternal<>(SB) // hsqr = hˆ2 STy(hsqr) LDx(h) - CALL p256MulInternalExCrypto<>(SB) // hcub = hˆ3 + CALL p256MulInternal<>(SB) // hcub = hˆ3 STy(hcub) LDx(s1) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) STy(s2) LDx(z1in) LDy(z2in) - CALL p256MulInternalExCrypto<>(SB) // z1 * z2 + CALL p256MulInternal<>(SB) // z1 * z2 LDx(h) - CALL p256MulInternalExCrypto<>(SB) // z1 * z2 * h + CALL p256MulInternal<>(SB) // z1 * z2 * h MOVD res+0(FP), b_ptr STy(z3out) LDx(hsqr) LDy(u1) - CALL p256MulInternalExCrypto<>(SB) // hˆ2 * u1 + CALL p256MulInternal<>(SB) // hˆ2 * u1 STy(u2) p256MulBy2Inline // u1 * hˆ2 * 2, inline LDy(rsqr) - CALL p256SubInternalExCrypto<>(SB) // rˆ2 - u1 * hˆ2 * 2 + CALL p256SubInternal<>(SB) // rˆ2 - u1 * hˆ2 * 2 MOVD x0, y0 MOVD x1, y1 MOVD x2, y2 MOVD x3, y3 LDx(hcub) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) STx(x3out) LDy(u2) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) LDy(r) - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) LDx(s2) - CALL p256SubInternalExCrypto<>(SB) + CALL p256SubInternal<>(SB) STx(y3out) MOVD hlp1, R0 diff --git a/crypto/internal/nistec/p256_asm_ppc64le.s b/crypto/internal/fips140/nistec/p256_asm_ppc64le.s similarity index 94% rename from crypto/internal/nistec/p256_asm_ppc64le.s rename to crypto/internal/fips140/nistec/p256_asm_ppc64le.s index d1120031140..7efaa6ac187 100644 --- a/crypto/internal/nistec/p256_asm_ppc64le.s +++ b/crypto/internal/fips140/nistec/p256_asm_ppc64le.s @@ -126,14 +126,23 @@ GLOBL p256mul<>(SB), 8, $160 #define PH V31 #define CAR1 V6 + +#define SEL V8 +#define ZER V9 + // func p256NegCond(val *p256Point, cond int) TEXT ·p256NegCond(SB), NOSPLIT, $0-16 MOVD val+0(FP), P1ptr MOVD $16, R16 - MOVD cond+8(FP), R6 - CMP $0, R6 - BC 12, 2, LR // just return if cond == 0 + // Copy cond into SEL (cond is R1 + 8 (cond offset) + 32) + MOVD $40, R17 + LXVDSX (R1)(R17), SEL + // Zeroize ZER + VSPLTISB $0, ZER + // SEL controls whether to return the original value (Y1H/Y1L) + // or the negated value (T1H/T1L). + VCMPEQUD SEL, ZER, SEL MOVD $p256mul<>+0x00(SB), CPOOL @@ -150,6 +159,9 @@ TEXT ·p256NegCond(SB), NOSPLIT, $0-16 VSUBUQM PL, Y1L, T1L // subtract part2 giving result VSUBEUQM PH, Y1H, CAR1, T1H // subtract part1 using carry from part2 + VSEL T1H, Y1H, SEL, T1H + VSEL T1L, Y1L, SEL, T1L + XXPERMDI T1H, T1H, $2, T1H XXPERMDI T1L, T1L, $2, T1L @@ -166,6 +178,8 @@ TEXT ·p256NegCond(SB), NOSPLIT, $0-16 #undef PL #undef PH #undef CAR1 +#undef SEL +#undef ZER #define P3ptr R3 #define P1ptr R4 @@ -291,7 +305,7 @@ TEXT ·p256Select(SB), NOSPLIT, $0-24 VSPLTB $7, SEL1, IDX // splat byte VSPLTISB $1, ONE // VREPIB $1, ONE VSPLTISB $1, SEL2 // VREPIB $1, SEL2 - MOVD $17, COUNT + MOVD $16, COUNT // len(p256Table) MOVD COUNT, CTR // set up ctr VSPLTISB $0, X1H // VZERO X1H @@ -362,50 +376,6 @@ loop_select: #undef SEL1 #undef SEL2 -// The following functions all reverse the byte order. - -//func p256BigToLittle(res *p256Element, in *[32]byte) -TEXT ·p256BigToLittle(SB), NOSPLIT, $0-16 - MOVD res+0(FP), R3 - MOVD in+8(FP), R4 - BR p256InternalEndianSwap<>(SB) - -//func p256LittleToBig(res *[32]byte, in *p256Element) -TEXT ·p256LittleToBig(SB), NOSPLIT, $0-16 - MOVD res+0(FP), R3 - MOVD in+8(FP), R4 - BR p256InternalEndianSwap<>(SB) - -//func p256OrdBigToLittle(res *p256OrdElement, in *[32]byte) -TEXT ·p256OrdBigToLittle(SB), NOSPLIT, $0-16 - MOVD res+0(FP), R3 - MOVD in+8(FP), R4 - BR p256InternalEndianSwap<>(SB) - -//func p256OrdLittleToBig(res *[32]byte, in *p256OrdElement) -TEXT ·p256OrdLittleToBig(SB), NOSPLIT, $0-16 - MOVD res+0(FP), R3 - MOVD in+8(FP), R4 - BR p256InternalEndianSwap<>(SB) - -TEXT p256InternalEndianSwap<>(SB), NOSPLIT, $0-0 - // Index registers needed for BR movs - MOVD $8, R9 - MOVD $16, R10 - MOVD $24, R14 - - MOVDBR (R0)(R4), R5 - MOVDBR (R9)(R4), R6 - MOVDBR (R10)(R4), R7 - MOVDBR (R14)(R4), R8 - - MOVD R8, 0(R3) - MOVD R7, 8(R3) - MOVD R6, 16(R3) - MOVD R5, 24(R3) - - RET - #define P3ptr R3 #define P1ptr R4 #define COUNT R5 @@ -441,7 +411,7 @@ TEXT ·p256SelectAffine(SB), NOSPLIT, $0-24 VSPLTISB $1, ONE // Vector with byte 1s VSPLTISB $1, SEL2 // Vector with byte 1s - MOVD $64, COUNT + MOVD $32, COUNT // len(p256AffineTable) MOVD COUNT, CTR // loop count VSPLTISB $0, X1H // VZERO X1H @@ -637,7 +607,7 @@ TEXT ·p256FromMont(SB), NOSPLIT, $0-16 #undef PH // --------------------------------------- -// p256MulInternalExCrypto +// p256MulInternal // V0-V3 V30,V31 - Not Modified // V4-V15 V27-V29 - Volatile @@ -786,7 +756,7 @@ TEXT ·p256FromMont(SB), NOSPLIT, $0-16 * * Last 'group' needs to RED2||RED1 shifted less */ -TEXT p256MulInternalExCrypto<>(SB), NOSPLIT, $0-16 +TEXT p256MulInternal<>(SB), NOSPLIT, $0-16 // CPOOL loaded from caller MOVD $16, R16 MOVD $32, R17 @@ -1065,7 +1035,7 @@ TEXT p256MulInternalExCrypto<>(SB), NOSPLIT, $0-16 #undef TMP1 #undef TMP2 -#define p256SubInternalExCrypto(T1, T0, X1, X0, Y1, Y0) \ +#define p256SubInternal(T1, T0, X1, X0, Y1, Y0) \ VSPLTISB $0, ZER \ // VZERO VSUBCUQ X0, Y0, CAR1 \ VSUBUQM X0, Y0, T0 \ @@ -1164,7 +1134,7 @@ TEXT ·p256Mul(SB), NOSPLIT, $0-24 LXVD2X (R16)(CPOOL), P1 LXVD2X (R0)(CPOOL), P0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) MOVD $p256mul<>+0x00(SB), CPOOL @@ -1198,7 +1168,7 @@ sqrLoop: LXVD2X (R16)(CPOOL), P1 LXVD2X (R0)(CPOOL), P0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) MOVD n+16(FP), N ADD $-1, N @@ -1331,7 +1301,7 @@ SUB(T(SB) + CALL p256MulInternal<>(SB) // X=T ; Y- ; MUL; T2=T // T2 = T1*Z1 T1 T2 VOR T0, T0, X0 VOR T1, T1, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T2L VOR T1, T1, T2H @@ -1400,7 +1370,7 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $16-48 LXVD2X (R16)(P2ptr), Y1 // X2L XXPERMDI Y0, Y0, $2, Y0 XXPERMDI Y1, Y1, $2, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T1L VOR T1, T1, T1H @@ -1409,7 +1379,7 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $16-48 VOR T2H, T2H, X1 VOR Y2L, Y2L, Y0 VOR Y2H, Y2H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T2(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, Z3L VOR T1, T1, Z3H @@ -1439,12 +1409,12 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $16-48 // X=Y; Y- ; MUL; X=T // T3 = T1*T1 T2 VOR Y0, Y0, X0 VOR Y1, Y1, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, X0 VOR T1, T1, X1 // X- ; Y- ; MUL; T4=T // T4 = T3*T1 T2 T4 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T4L VOR T1, T1, T4H @@ -1454,7 +1424,7 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $16-48 LXVD2X (R16)(P1ptr), Y1 // X1L XXPERMDI Y1, Y1, $2, Y1 XXPERMDI Y0, Y0, $2, Y0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T3L VOR T1, T1, T3H @@ -1466,21 +1436,21 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $16-48 VOR T2H, T2H, X1 VOR T2L, T2L, Y0 VOR T2H, T2H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T3L VOR T1, T1, T3H @@ -1492,10 +1462,10 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $16-48 LXVD2X (R18)(P1ptr), Y1 // Y1L XXPERMDI Y0, Y0, $2, Y0 XXPERMDI Y1, Y1, $2, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T(SB) + CALL p256MulInternal<>(SB) // SUB(X(SB) + CALL p256MulInternal<>(SB) // ADD(T2(SB) + CALL p256MulInternal<>(SB) // Leave T0, T1 as is. XXPERMDI T0, T0, $2, TT0 @@ -1767,7 +1737,7 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $0-16 // X- ; Y=X ; MUL; T- // Y3 = Y3² VOR X0, X0, Y0 VOR X1, X1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // X=T ; Y=X1; MUL; T3=T // T3 = Y3*X1 VOR T0, T0, X0 @@ -1776,14 +1746,14 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $0-16 LXVD2X (R16)(P1ptr), Y1 XXPERMDI Y0, Y0, $2, Y0 XXPERMDI Y1, Y1, $2, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T3L VOR T1, T1, T3H // X- ; Y=X ; MUL; T- // Y3 = Y3² VOR X0, X0, Y0 VOR X1, X1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // HAL(Y3(SB) + CALL p256MulInternal<>(SB) // ADD(T1(SB) + CALL p256MulInternal<>(SB) // SUB(Y3(SB) + CALL p256MulInternal<>(SB) // X- ; Y=T ; MUL; R=T // R = Z1*T1 VOR T0, T0, Y0 VOR T1, T1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, RL // SAVE: RL VOR T1, T1, RH // SAVE: RH @@ -2003,7 +1973,7 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 LXVD2X (R16)(P2ptr), X1 // X2H XXPERMDI X0, X0, $2, X0 XXPERMDI X1, X1, $2, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, HL // SAVE: HL VOR T1, T1, HH // SAVE: HH @@ -2015,12 +1985,12 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 XXPERMDI X1, X1, $2, X1 VOR X0, X0, Y0 VOR X1, X1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // X- ; Y=T ; MUL; S1=T // S1 = Z2*T2 VOR T0, T0, Y0 VOR T1, T1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, S1L // SAVE: S1L VOR T1, T1, S1H // SAVE: S1H @@ -2030,12 +2000,12 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 LXVD2X (R16)(P1ptr), X1 // X1H XXPERMDI X0, X0, $2, X0 XXPERMDI X1, X1, $2, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, U1L // SAVE: U1L VOR T1, T1, U1H // SAVE: U1H // SUB(H(SB) + CALL p256MulInternal<>(SB) // X=T ; Y=H ; MUL; Z3:=T// Z3 = Z3*H VOR T0, T0, X0 VOR T1, T1, X1 VOR HL, HL, Y0 VOR HH, HH, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) MOVD res+0(FP), P3ptr XXPERMDI T1, T1, $2, TT1 XXPERMDI T0, T0, $2, TT0 @@ -2089,7 +2059,7 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 XXPERMDI X1, X1, $2, X1 VOR S1L, S1L, Y0 VOR S1H, S1H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, S1L VOR T1, T1, S1H @@ -2103,10 +2073,10 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 // VOR RH, RH, Y1 RH was saved above in D2X format LXVD2X (R1)(R17), Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(R(SB) + CALL p256MulInternal<>(SB) // X- ; Y=T ; MUL; T2=T // T2 = H*T1 VOR T0, T0, Y0 VOR T1, T1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, T2L VOR T1, T1, T2H // X=U1; Y- ; MUL; U1=T // U1 = U1*T1 VOR U1L, U1L, X0 VOR U1H, U1H, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, U1L VOR T1, T1, U1H @@ -2164,16 +2134,16 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 VOR X1, X1, Y1 // VOR RH, RH, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T(SB) + CALL p256MulInternal<>(SB) VOR T0, T0, U1L VOR T1, T1, U1H @@ -2197,10 +2167,10 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $16-32 VOR S1H, S1H, X1 VOR T2L, T2L, Y0 VOR T2H, T2H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T(SB), 8, $32 GLOBL p256<>(SB), 8, $96 GLOBL p256mul<>(SB), 8, $160 -// func p256OrdLittleToBig(res *[32]byte, in *p256OrdElement) -TEXT ·p256OrdLittleToBig(SB), NOSPLIT, $0 - JMP ·p256BigToLittle(SB) - -// func p256OrdBigToLittle(res *p256OrdElement, in *[32]byte) -TEXT ·p256OrdBigToLittle(SB), NOSPLIT, $0 - JMP ·p256BigToLittle(SB) - -// --------------------------------------- -// func p256LittleToBig(res *[32]byte, in *p256Element) -TEXT ·p256LittleToBig(SB), NOSPLIT, $0 - JMP ·p256BigToLittle(SB) - -// func p256BigToLittle(res *p256Element, in *[32]byte) -#define res_ptr R1 -#define in_ptr R2 -#define T1L V2 -#define T1H V3 - -TEXT ·p256BigToLittle(SB), NOSPLIT, $0 - MOVD res+0(FP), res_ptr - MOVD in+8(FP), in_ptr - - VL 0(in_ptr), T1H - VL 16(in_ptr), T1L - - VPDI $0x4, T1L, T1L, T1L - VPDI $0x4, T1H, T1H, T1H - - VST T1L, 0(res_ptr) - VST T1H, 16(res_ptr) - RET - -#undef res_ptr -#undef in_ptr -#undef T1L -#undef T1H - // --------------------------------------- // iff cond == 1 val <- -val // func p256NegCond(val *p256Element, cond int) @@ -508,7 +470,7 @@ loop_select: VAB SEL2, ONE, SEL2 ADDW $1, COUNT ADD $64, P1ptr - CMPW COUNT, $65 + CMPW COUNT, $33 // len(p256AffineTable) + 1 BLT loop_select VST X1H, 0(P3ptr) VST X1L, 16(P3ptr) @@ -925,7 +887,7 @@ TEXT ·p256OrdMul<>(SB), NOSPLIT, $0 #undef K0 // --------------------------------------- -// p256MulInternalExCrypto +// p256MulInternal // V0-V3,V30,V31 - Not Modified // V4-V15 - Volatile @@ -1068,7 +1030,7 @@ TEXT ·p256OrdMul<>(SB), NOSPLIT, $0 * * Last 'group' needs to RED2||RED1 shifted less */ -TEXT p256MulInternalExCrypto<>(SB), NOSPLIT, $0-0 +TEXT p256MulInternal<>(SB), NOSPLIT, $0-0 VL 32(CPOOL), SEL1 VL 48(CPOOL), SEL2 VL 64(CPOOL), SEL3 @@ -1321,17 +1283,17 @@ TEXT p256MulInternalExCrypto<>(SB), NOSPLIT, $0-0 #define Y0 V2 #define Y1 V3 -TEXT p256SqrInternalExCrypto<>(SB), NOFRAME|NOSPLIT, $0 +TEXT p256SqrInternal<>(SB), NOFRAME|NOSPLIT, $0 VLR X0, Y0 VLR X1, Y1 - BR p256MulInternalExCrypto<>(SB) + BR p256MulInternal<>(SB) #undef X0 #undef X1 #undef Y0 #undef Y1 -#define p256SubInternalExCrypto(T1, T0, X1, X0, Y1, Y0) \ +#define p256SubInternal(T1, T0, X1, X0, Y1, Y0) \ VZERO ZER \ VSCBIQ Y0, X0, CAR1 \ VSQ Y0, X0, T0 \ @@ -1422,7 +1384,7 @@ TEXT ·p256Mul(SB), NOSPLIT, $0 VL 16(CPOOL), P0 VL 0(CPOOL), P1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VPDI $0x4, T0, T0, T0 VST T0, (0*16)(res_ptr) @@ -1478,7 +1440,7 @@ TEXT ·p256Sqr(SB), NOSPLIT, $0 VL 0(CPOOL), P1 loop: - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) VLR T0, X0 VLR T1, X1 ADDW $1, COUNT @@ -1648,12 +1610,12 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 VPDI $0x4, X0, X0, X0 VLR X0, Y0 VLR X1, Y1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // X=T ; Y- ; MUL; T2=T // T2 = T1*Z1 T1 T2 VLR T0, X0 VLR T1, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, T2L VLR T1, T2H @@ -1662,7 +1624,7 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 VPDI $0x4, Y1, Y1, Y1 VL 0(P2ptr), Y0 // X2L VPDI $0x4, Y0, Y0, Y0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, T1L VLR T1, T1H @@ -1671,28 +1633,28 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 VLR T2H, X1 VLR Y2L, Y0 VLR Y2H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T2(SB) + CALL p256MulInternal<>(SB) // VST T1, 64(P3ptr) // VST T0, 80(P3ptr) @@ -1702,12 +1664,12 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 // X=Y; Y- ; MUL; X=T // T3 = T1*T1 T2 VLR Y0, X0 VLR Y1, X1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) VLR T0, X0 VLR T1, X1 // X- ; Y- ; MUL; T4=T // T4 = T3*T1 T2 T4 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, T4L VLR T1, T4H @@ -1716,7 +1678,7 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 VPDI $0x4, Y1, Y1, Y1 VL 0(P1ptr), Y0 // X1L VPDI $0x4, Y0, Y0, Y0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, T3L VLR T1, T3H @@ -1728,21 +1690,21 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 VLR T2H, X1 VLR T2L, Y0 VLR T2H, Y1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // SUB(T(SB) + CALL p256MulInternal<>(SB) VLR T0, T3L VLR T1, T3H @@ -1753,10 +1715,10 @@ TEXT ·p256PointAddAffineAsm(SB), NOSPLIT, $0 VPDI $0x4, Y1, Y1, Y1 VL 32(P1ptr), Y0 // Y1L VPDI $0x4, Y0, Y0, Y0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T(SB) + CALL p256SqrInternal<>(SB) // SUB(X(SB) + CALL p256MulInternal<>(SB) // ADD(T2(SB) + CALL p256MulInternal<>(SB) VPDI $0x4, T1, T1, TT1 VST TT1, 80(P3ptr) VPDI $0x4, T0, T0, TT0 @@ -2017,7 +1979,7 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $0 // X- ; Y=X ; MUL; T- // Y3 = Y3² VLR X0, Y0 VLR X1, Y1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // X=T ; Y=X1; MUL; T3=T // T3 = Y3*X1 VLR T0, X0 @@ -2026,14 +1988,14 @@ TEXT ·p256PointDoubleAsm(SB), NOSPLIT, $0 VPDI $0x4, Y1, Y1, Y1 VL 0(P1ptr), Y0 VPDI $0x4, Y0, Y0, Y0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, T3L VLR T1, T3H // X- ; Y=X ; MUL; T- // Y3 = Y3² VLR X0, Y0 VLR X1, Y1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // HAL(Y3(SB) + CALL p256SqrInternal<>(SB) // ADD(T1(SB) + CALL p256MulInternal<>(SB) // SUB(Y3(SB) + CALL p256SqrInternal<>(SB) // X- ; Y=T ; MUL; R=T // R = Z1*T1 VLR T0, Y0 VLR T1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, RL VLR T1, RH @@ -2244,7 +2206,7 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VPDI $0x4, X1, X1, X1 VL 0(P2ptr), X0 // X2L VPDI $0x4, X0, X0, X0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, HL VLR T1, HH @@ -2255,12 +2217,12 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VPDI $0x4, X0, X0, X0 VLR X0, Y0 VLR X1, Y1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // X- ; Y=T ; MUL; S1=T // S1 = Z2*T2 VLR T0, Y0 VLR T1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, S1L VLR T1, S1H @@ -2269,12 +2231,12 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VPDI $0x4, X1, X1, X1 VL 0(P1ptr), X0 // X1L VPDI $0x4, X0, X0, X0 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, U1L VLR T1, U1H // SUB(H(SB) + CALL p256MulInternal<>(SB) // X=T ; Y=H ; MUL; Z3:=T// Z3 = Z3*H VLR T0, X0 VLR T1, X1 VLR HL, Y0 VLR HH, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VPDI $0x4, T1, T1, TT1 VST TT1, 80(P3ptr) VPDI $0x4, T0, T0, TT0 @@ -2320,7 +2282,7 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VPDI $0x4, X0, X0, X0 VLR S1L, Y0 VLR S1H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, S1L VLR T1, S1H @@ -2331,10 +2293,10 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VPDI $0x4, X0, X0, X0 VLR RL, Y0 VLR RH, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(R(SB) + CALL p256SqrInternal<>(SB) // X- ; Y=T ; MUL; T2=T // T2 = H*T1 VLR T0, Y0 VLR T1, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, T2L VLR T1, T2H // X=U1; Y- ; MUL; U1=T // U1 = U1*T1 VLR U1L, X0 VLR U1H, X1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) VLR T0, U1L VLR T1, U1H @@ -2378,28 +2340,28 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VLR RH, X1 VLR RL, Y0 VLR RH, Y1 - CALL p256SqrInternalExCrypto<>(SB) + CALL p256SqrInternal<>(SB) // SUB(T(SB) + CALL p256MulInternal<>(SB) VLR T0, U1L VLR T1, U1H @@ -2408,10 +2370,10 @@ TEXT ·p256PointAddAsm(SB), NOSPLIT, $0 VLR S1H, X1 VLR T2L, Y0 VLR T2H, Y1 - CALL p256MulInternalExCrypto<>(SB) + CALL p256MulInternal<>(SB) // SUB(T maxBlocks { + return nil, errors.New("pbkdf2: keyLength too long") + } + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLength], nil +} + +func setServiceIndicator(salt []byte, keyLength int) { + // The HMAC construction will handle the hash function considerations for the service + // indicator. The remaining PBKDF2 considerations outlined by SP 800-132 pertain to + // salt and keyLength. + + // The length of the randomly-generated portion of the salt shall be at least 128 bits. + if len(salt) < 128/8 { + fips140.RecordNonApproved() + } + + // Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for + // legacy use (i.e. verification only) and we don't support that. + if keyLength < 112/8 { + fips140.RecordNonApproved() + } + + fips140.RecordApproved() +} diff --git a/crypto/internal/fips140/rsa/cast.go b/crypto/internal/fips140/rsa/cast.go new file mode 100644 index 00000000000..0acacd03184 --- /dev/null +++ b/crypto/internal/fips140/rsa/cast.go @@ -0,0 +1,236 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "bytes" + "errors" + "sync" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" +) + +func testPrivateKey() *PrivateKey { + // https://www.rfc-editor.org/rfc/rfc9500.html#section-2.1 + N, _ := bigmod.NewModulus([]byte{ + 0xB0, 0xF9, 0xE8, 0x19, 0x43, 0xA7, 0xAE, 0x98, + 0x92, 0xAA, 0xDE, 0x17, 0xCA, 0x7C, 0x40, 0xF8, + 0x74, 0x4F, 0xED, 0x2F, 0x81, 0x48, 0xE6, 0xC8, + 0xEA, 0xA2, 0x7B, 0x7D, 0x00, 0x15, 0x48, 0xFB, + 0x51, 0x92, 0xAB, 0x28, 0xB5, 0x6C, 0x50, 0x60, + 0xB1, 0x18, 0xCC, 0xD1, 0x31, 0xE5, 0x94, 0x87, + 0x4C, 0x6C, 0xA9, 0x89, 0xB5, 0x6C, 0x27, 0x29, + 0x6F, 0x09, 0xFB, 0x93, 0xA0, 0x34, 0xDF, 0x32, + 0xE9, 0x7C, 0x6F, 0xF0, 0x99, 0x8C, 0xFD, 0x8E, + 0x6F, 0x42, 0xDD, 0xA5, 0x8A, 0xCD, 0x1F, 0xA9, + 0x79, 0x86, 0xF1, 0x44, 0xF3, 0xD1, 0x54, 0xD6, + 0x76, 0x50, 0x17, 0x5E, 0x68, 0x54, 0xB3, 0xA9, + 0x52, 0x00, 0x3B, 0xC0, 0x68, 0x87, 0xB8, 0x45, + 0x5A, 0xC2, 0xB1, 0x9F, 0x7B, 0x2F, 0x76, 0x50, + 0x4E, 0xBC, 0x98, 0xEC, 0x94, 0x55, 0x71, 0xB0, + 0x78, 0x92, 0x15, 0x0D, 0xDC, 0x6A, 0x74, 0xCA, + 0x0F, 0xBC, 0xD3, 0x54, 0x97, 0xCE, 0x81, 0x53, + 0x4D, 0xAF, 0x94, 0x18, 0x84, 0x4B, 0x13, 0xAE, + 0xA3, 0x1F, 0x9D, 0x5A, 0x6B, 0x95, 0x57, 0xBB, + 0xDF, 0x61, 0x9E, 0xFD, 0x4E, 0x88, 0x7F, 0x2D, + 0x42, 0xB8, 0xDD, 0x8B, 0xC9, 0x87, 0xEA, 0xE1, + 0xBF, 0x89, 0xCA, 0xB8, 0x5E, 0xE2, 0x1E, 0x35, + 0x63, 0x05, 0xDF, 0x6C, 0x07, 0xA8, 0x83, 0x8E, + 0x3E, 0xF4, 0x1C, 0x59, 0x5D, 0xCC, 0xE4, 0x3D, + 0xAF, 0xC4, 0x91, 0x23, 0xEF, 0x4D, 0x8A, 0xBB, + 0xA9, 0x3D, 0x39, 0x05, 0xE4, 0x02, 0x8D, 0x7B, + 0xA9, 0x14, 0x84, 0xA2, 0x75, 0x96, 0xE0, 0x7B, + 0x4B, 0x6E, 0xD9, 0x92, 0xF0, 0x77, 0xB5, 0x24, + 0xD3, 0xDC, 0xFE, 0x7D, 0xDD, 0x55, 0x49, 0xBE, + 0x7C, 0xCE, 0x8D, 0xA0, 0x35, 0xCF, 0xA0, 0xB3, + 0xFB, 0x8F, 0x9E, 0x46, 0xF7, 0x32, 0xB2, 0xA8, + 0x6B, 0x46, 0x01, 0x65, 0xC0, 0x8F, 0x53, 0x13}) + d, _ := bigmod.NewNat().SetBytes([]byte{ + 0x41, 0x18, 0x8B, 0x20, 0xCF, 0xDB, 0xDB, 0xC2, + 0xCF, 0x1F, 0xFE, 0x75, 0x2D, 0xCB, 0xAA, 0x72, + 0x39, 0x06, 0x35, 0x2E, 0x26, 0x15, 0xD4, 0x9D, + 0xCE, 0x80, 0x59, 0x7F, 0xCF, 0x0A, 0x05, 0x40, + 0x3B, 0xEF, 0x00, 0xFA, 0x06, 0x51, 0x82, 0xF7, + 0x2D, 0xEC, 0xFB, 0x59, 0x6F, 0x4B, 0x0C, 0xE8, + 0xFF, 0x59, 0x70, 0xBA, 0xF0, 0x7A, 0x89, 0xA5, + 0x19, 0xEC, 0xC8, 0x16, 0xB2, 0xF4, 0xFF, 0xAC, + 0x50, 0x69, 0xAF, 0x1B, 0x06, 0xBF, 0xEF, 0x7B, + 0xF6, 0xBC, 0xD7, 0x9E, 0x4E, 0x81, 0xC8, 0xC5, + 0xA3, 0xA7, 0xD9, 0x13, 0x0D, 0xC3, 0xCF, 0xBA, + 0xDA, 0xE5, 0xF6, 0xD2, 0x88, 0xF9, 0xAE, 0xE3, + 0xF6, 0xFF, 0x92, 0xFA, 0xE0, 0xF8, 0x1A, 0xF5, + 0x97, 0xBE, 0xC9, 0x6A, 0xE9, 0xFA, 0xB9, 0x40, + 0x2C, 0xD5, 0xFE, 0x41, 0xF7, 0x05, 0xBE, 0xBD, + 0xB4, 0x7B, 0xB7, 0x36, 0xD3, 0xFE, 0x6C, 0x5A, + 0x51, 0xE0, 0xE2, 0x07, 0x32, 0xA9, 0x7B, 0x5E, + 0x46, 0xC1, 0xCB, 0xDB, 0x26, 0xD7, 0x48, 0x54, + 0xC6, 0xB6, 0x60, 0x4A, 0xED, 0x46, 0x37, 0x35, + 0xFF, 0x90, 0x76, 0x04, 0x65, 0x57, 0xCA, 0xF9, + 0x49, 0xBF, 0x44, 0x88, 0x95, 0xC2, 0x04, 0x32, + 0xC1, 0xE0, 0x9C, 0x01, 0x4E, 0xA7, 0x56, 0x60, + 0x43, 0x4F, 0x1A, 0x0F, 0x3B, 0xE2, 0x94, 0xBA, + 0xBC, 0x5D, 0x53, 0x0E, 0x6A, 0x10, 0x21, 0x3F, + 0x53, 0xB6, 0x03, 0x75, 0xFC, 0x84, 0xA7, 0x57, + 0x3F, 0x2A, 0xF1, 0x21, 0x55, 0x84, 0xF5, 0xB4, + 0xBD, 0xA6, 0xD4, 0xE8, 0xF9, 0xE1, 0x7A, 0x78, + 0xD9, 0x7E, 0x77, 0xB8, 0x6D, 0xA4, 0xA1, 0x84, + 0x64, 0x75, 0x31, 0x8A, 0x7A, 0x10, 0xA5, 0x61, + 0x01, 0x4E, 0xFF, 0xA2, 0x3A, 0x81, 0xEC, 0x56, + 0xE9, 0xE4, 0x10, 0x9D, 0xEF, 0x8C, 0xB3, 0xF7, + 0x97, 0x22, 0x3F, 0x7D, 0x8D, 0x0D, 0x43, 0x51}, N) + p, _ := bigmod.NewModulus([]byte{ + 0xDD, 0x10, 0x57, 0x02, 0x38, 0x2F, 0x23, 0x2B, + 0x36, 0x81, 0xF5, 0x37, 0x91, 0xE2, 0x26, 0x17, + 0xC7, 0xBF, 0x4E, 0x9A, 0xCB, 0x81, 0xED, 0x48, + 0xDA, 0xF6, 0xD6, 0x99, 0x5D, 0xA3, 0xEA, 0xB6, + 0x42, 0x83, 0x9A, 0xFF, 0x01, 0x2D, 0x2E, 0xA6, + 0x28, 0xB9, 0x0A, 0xF2, 0x79, 0xFD, 0x3E, 0x6F, + 0x7C, 0x93, 0xCD, 0x80, 0xF0, 0x72, 0xF0, 0x1F, + 0xF2, 0x44, 0x3B, 0x3E, 0xE8, 0xF2, 0x4E, 0xD4, + 0x69, 0xA7, 0x96, 0x13, 0xA4, 0x1B, 0xD2, 0x40, + 0x20, 0xF9, 0x2F, 0xD1, 0x10, 0x59, 0xBD, 0x1D, + 0x0F, 0x30, 0x1B, 0x5B, 0xA7, 0xA9, 0xD3, 0x63, + 0x7C, 0xA8, 0xD6, 0x5C, 0x1A, 0x98, 0x15, 0x41, + 0x7D, 0x8E, 0xAB, 0x73, 0x4B, 0x0B, 0x4F, 0x3A, + 0x2C, 0x66, 0x1D, 0x9A, 0x1A, 0x82, 0xF3, 0xAC, + 0x73, 0x4C, 0x40, 0x53, 0x06, 0x69, 0xAB, 0x8E, + 0x47, 0x30, 0x45, 0xA5, 0x8E, 0x65, 0x53, 0x9D}) + q, _ := bigmod.NewModulus([]byte{ + 0xCC, 0xF1, 0xE5, 0xBB, 0x90, 0xC8, 0xE9, 0x78, + 0x1E, 0xA7, 0x5B, 0xEB, 0xF1, 0x0B, 0xC2, 0x52, + 0xE1, 0x1E, 0xB0, 0x23, 0xA0, 0x26, 0x0F, 0x18, + 0x87, 0x55, 0x2A, 0x56, 0x86, 0x3F, 0x4A, 0x64, + 0x21, 0xE8, 0xC6, 0x00, 0xBF, 0x52, 0x3D, 0x6C, + 0xB1, 0xB0, 0xAD, 0xBD, 0xD6, 0x5B, 0xFE, 0xE4, + 0xA8, 0x8A, 0x03, 0x7E, 0x3D, 0x1A, 0x41, 0x5E, + 0x5B, 0xB9, 0x56, 0x48, 0xDA, 0x5A, 0x0C, 0xA2, + 0x6B, 0x54, 0xF4, 0xA6, 0x39, 0x48, 0x52, 0x2C, + 0x3D, 0x5F, 0x89, 0xB9, 0x4A, 0x72, 0xEF, 0xFF, + 0x95, 0x13, 0x4D, 0x59, 0x40, 0xCE, 0x45, 0x75, + 0x8F, 0x30, 0x89, 0x80, 0x90, 0x89, 0x56, 0x58, + 0x8E, 0xEF, 0x57, 0x5B, 0x3E, 0x4B, 0xC4, 0xC3, + 0x68, 0xCF, 0xE8, 0x13, 0xEE, 0x9C, 0x25, 0x2C, + 0x2B, 0x02, 0xE0, 0xDF, 0x91, 0xF1, 0xAA, 0x01, + 0x93, 0x8D, 0x38, 0x68, 0x5D, 0x60, 0xBA, 0x6F}) + qInv, _ := bigmod.NewNat().SetBytes([]byte{ + 0x0A, 0x81, 0xD8, 0xA6, 0x18, 0x31, 0x4A, 0x80, + 0x3A, 0xF6, 0x1C, 0x06, 0x71, 0x1F, 0x2C, 0x39, + 0xB2, 0x66, 0xFF, 0x41, 0x4D, 0x53, 0x47, 0x6D, + 0x1D, 0xA5, 0x2A, 0x43, 0x18, 0xAA, 0xFE, 0x4B, + 0x96, 0xF0, 0xDA, 0x07, 0x15, 0x5F, 0x8A, 0x51, + 0x34, 0xDA, 0xB8, 0x8E, 0xE2, 0x9E, 0x81, 0x68, + 0x07, 0x6F, 0xCD, 0x78, 0xCA, 0x79, 0x1A, 0xC6, + 0x34, 0x42, 0xA8, 0x1C, 0xD0, 0x69, 0x39, 0x27, + 0xD8, 0x08, 0xE3, 0x35, 0xE8, 0xD8, 0xCB, 0xF2, + 0x12, 0x19, 0x07, 0x50, 0x9A, 0x57, 0x75, 0x9B, + 0x4F, 0x9A, 0x18, 0xFA, 0x3A, 0x7B, 0x33, 0x37, + 0x79, 0xED, 0xDE, 0x7A, 0x45, 0x93, 0x84, 0xF8, + 0x44, 0x4A, 0xDA, 0xEC, 0xFF, 0xEC, 0x95, 0xFD, + 0x55, 0x2B, 0x0C, 0xFC, 0xB6, 0xC7, 0xF6, 0x92, + 0x62, 0x6D, 0xDE, 0x1E, 0xF2, 0x68, 0xA4, 0x0D, + 0x2F, 0x67, 0xB5, 0xC8, 0xAA, 0x38, 0x7F, 0xF7}, p) + dP := []byte{ + 0x09, 0xED, 0x54, 0xEA, 0xED, 0x98, 0xF8, 0x4C, + 0x55, 0x7B, 0x4A, 0x86, 0xBF, 0x4F, 0x57, 0x84, + 0x93, 0xDC, 0xBC, 0x6B, 0xE9, 0x1D, 0xA1, 0x89, + 0x37, 0x04, 0x04, 0xA9, 0x08, 0x72, 0x76, 0xF4, + 0xCE, 0x51, 0xD8, 0xA1, 0x00, 0xED, 0x85, 0x7D, + 0xC2, 0xB0, 0x64, 0x94, 0x74, 0xF3, 0xF1, 0x5C, + 0xD2, 0x4C, 0x54, 0xDB, 0x28, 0x71, 0x10, 0xE5, + 0x6E, 0x5C, 0xB0, 0x08, 0x68, 0x2F, 0x91, 0x68, + 0xAA, 0x81, 0xF3, 0x14, 0x58, 0xB7, 0x43, 0x1E, + 0xCC, 0x1C, 0x44, 0x90, 0x6F, 0xDA, 0x87, 0xCA, + 0x89, 0x47, 0x10, 0xC3, 0x71, 0xE9, 0x07, 0x6C, + 0x1D, 0x49, 0xFB, 0xAE, 0x51, 0x27, 0x69, 0x34, + 0xF2, 0xAD, 0x78, 0x77, 0x89, 0xF4, 0x2D, 0x0F, + 0xA0, 0xB4, 0xC9, 0x39, 0x85, 0x5D, 0x42, 0x12, + 0x09, 0x6F, 0x70, 0x28, 0x0A, 0x4E, 0xAE, 0x7C, + 0x8A, 0x27, 0xD9, 0xC8, 0xD0, 0x77, 0x2E, 0x65} + dQ := []byte{ + 0x8C, 0xB6, 0x85, 0x7A, 0x7B, 0xD5, 0x46, 0x5F, + 0x80, 0x04, 0x7E, 0x9B, 0x87, 0xBC, 0x00, 0x27, + 0x31, 0x84, 0x05, 0x81, 0xE0, 0x62, 0x61, 0x39, + 0x01, 0x2A, 0x5B, 0x50, 0x5F, 0x0A, 0x33, 0x84, + 0x7E, 0xB7, 0xB8, 0xC3, 0x28, 0x99, 0x49, 0xAD, + 0x48, 0x6F, 0x3B, 0x4B, 0x3D, 0x53, 0x9A, 0xB5, + 0xDA, 0x76, 0x30, 0x21, 0xCB, 0xC8, 0x2C, 0x1B, + 0xA2, 0x34, 0xA5, 0x66, 0x8D, 0xED, 0x08, 0x01, + 0xB8, 0x59, 0xF3, 0x43, 0xF1, 0xCE, 0x93, 0x04, + 0xE6, 0xFA, 0xA2, 0xB0, 0x02, 0xCA, 0xD9, 0xB7, + 0x8C, 0xDE, 0x5C, 0xDC, 0x2C, 0x1F, 0xB4, 0x17, + 0x1C, 0x42, 0x42, 0x16, 0x70, 0xA6, 0xAB, 0x0F, + 0x50, 0xCC, 0x4A, 0x19, 0x4E, 0xB3, 0x6D, 0x1C, + 0x91, 0xE9, 0x35, 0xBA, 0x01, 0xB9, 0x59, 0xD8, + 0x72, 0x8B, 0x9E, 0x64, 0x42, 0x6B, 0x3F, 0xC3, + 0xA7, 0x50, 0x6D, 0xEB, 0x52, 0x39, 0xA8, 0xA7} + return &PrivateKey{ + pub: PublicKey{ + N: N, E: 65537, + }, + d: d, p: p, q: q, qInv: qInv, dP: dP, dQ: dQ, + fipsApproved: true, + } + +} + +var fipsSelfTest = sync.OnceFunc(func() { + fips140.CAST("RSASSA-PKCS-v1.5 2048-bit sign and verify", func() error { + k := testPrivateKey() + hash := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + want := []byte{ + 0x16, 0x98, 0x33, 0xc7, 0x30, 0x2c, 0x0a, 0xdc, + 0x0a, 0x8d, 0x02, 0x58, 0xeb, 0xf9, 0x7d, 0xb6, + 0x2a, 0xad, 0xee, 0x63, 0x72, 0xaa, 0x37, 0x2c, + 0xb3, 0x06, 0x04, 0xdf, 0xdb, 0x2b, 0xbc, 0xb1, + 0x76, 0x3e, 0xeb, 0x87, 0xef, 0x91, 0xef, 0x74, + 0x69, 0x62, 0x27, 0xf3, 0x24, 0xf8, 0xe7, 0x0e, + 0xb2, 0x15, 0x3f, 0xa2, 0x4d, 0xe2, 0x0c, 0xd4, + 0xdc, 0x2d, 0xc1, 0x1a, 0x84, 0x7c, 0x88, 0x80, + 0xb9, 0xa9, 0x23, 0x67, 0x39, 0x2e, 0x86, 0xc0, + 0x53, 0x9b, 0xc1, 0x35, 0xb3, 0x17, 0x5e, 0x62, + 0x95, 0xd6, 0xbc, 0x2a, 0xa6, 0xb1, 0xcf, 0x8f, + 0x99, 0x43, 0x1f, 0x3d, 0xd2, 0x70, 0x3f, 0x01, + 0x37, 0x2b, 0xdd, 0x69, 0x1a, 0x5c, 0x2b, 0x04, + 0x70, 0x92, 0xea, 0x2d, 0x86, 0x00, 0xcb, 0x79, + 0xca, 0xaf, 0xa4, 0x1c, 0xd9, 0x61, 0x21, 0x3b, + 0x1e, 0xc5, 0x88, 0xfb, 0xff, 0xbd, 0xc7, 0x3c, + 0x36, 0xa1, 0xc6, 0x85, 0x03, 0xaf, 0x47, 0x4f, + 0x42, 0x9e, 0x23, 0x65, 0x24, 0x69, 0x17, 0xdb, + 0xe7, 0xb7, 0xdc, 0x51, 0xc6, 0x30, 0x40, 0x32, + 0x4f, 0x71, 0xf1, 0x62, 0x2d, 0xaa, 0x98, 0xdb, + 0x11, 0x14, 0xf9, 0x9c, 0x35, 0xc3, 0x16, 0xe1, + 0x1a, 0xd1, 0x8c, 0x4d, 0x8c, 0xad, 0x06, 0x34, + 0xd2, 0x84, 0x97, 0xa4, 0x0b, 0x6e, 0x6d, 0x19, + 0x9f, 0xa7, 0x40, 0x1e, 0xb5, 0xfc, 0x4e, 0x12, + 0x08, 0xec, 0xf4, 0x07, 0x13, 0xdc, 0x5a, 0x8c, + 0xd5, 0x2a, 0xd6, 0x5a, 0x2c, 0xc9, 0x54, 0x84, + 0x78, 0x34, 0x8f, 0x11, 0xfb, 0x6e, 0xd4, 0x27, + 0x45, 0xd9, 0xfa, 0x90, 0x82, 0x83, 0x73, 0x22, + 0x15, 0xab, 0x96, 0x13, 0x0d, 0x52, 0x1c, 0xdc, + 0x17, 0xde, 0x12, 0x6f, 0x84, 0x46, 0xbb, 0xec, + 0xe3, 0xb1, 0xa1, 0x5d, 0x8b, 0xeb, 0xe6, 0xae, + 0x02, 0xb8, 0x76, 0x47, 0x76, 0x11, 0x61, 0x2b, + } + sig, err := signPKCS1v15(k, "SHA-256", hash) + if err != nil { + return err + } + if err := verifyPKCS1v15(k.PublicKey(), "SHA-256", hash, sig); err != nil { + return err + } + if !bytes.Equal(sig, want) { + return errors.New("unexpected result") + } + return nil + }) +}) diff --git a/crypto/internal/fips140/rsa/keygen.go b/crypto/internal/fips140/rsa/keygen.go new file mode 100644 index 00000000000..a0e9bca010a --- /dev/null +++ b/crypto/internal/fips140/rsa/keygen.go @@ -0,0 +1,388 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "errors" + "io" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" +) + +// GenerateKey generates a new RSA key pair of the given bit size. +// bits must be at least 32. +func GenerateKey(rand io.Reader, bits int) (*PrivateKey, error) { + if bits < 32 { + return nil, errors.New("rsa: key too small") + } + fips140.RecordApproved() + if bits < 2048 || bits%2 == 1 { + fips140.RecordNonApproved() + } + + for { + p, err := randomPrime(rand, (bits+1)/2) + if err != nil { + return nil, err + } + q, err := randomPrime(rand, bits/2) + if err != nil { + return nil, err + } + + P, err := bigmod.NewModulus(p) + if err != nil { + return nil, err + } + Q, err := bigmod.NewModulus(q) + if err != nil { + return nil, err + } + + if Q.Nat().ExpandFor(P).Equal(P.Nat()) == 1 { + return nil, errors.New("rsa: generated p == q, random source is broken") + } + + N, err := bigmod.NewModulusProduct(p, q) + if err != nil { + return nil, err + } + if N.BitLen() != bits { + return nil, errors.New("rsa: internal error: modulus size incorrect") + } + + // d can be safely computed as e⁻¹ mod φ(N) where φ(N) = (p-1)(q-1), and + // indeed that's what both the original RSA paper and the pre-FIPS + // crypto/rsa implementation did. + // + // However, FIPS 186-5, A.1.1(3) requires computing it as e⁻¹ mod λ(N) + // where λ(N) = lcm(p-1, q-1). + // + // This makes d smaller by 1.5 bits on average, which is irrelevant both + // because we exclusively use the CRT for private operations and because + // we use constant time windowed exponentiation. On the other hand, it + // requires computing a GCD of two values that are not coprime, and then + // a division, both complex variable-time operations. + λ, err := totient(P, Q) + if err == errDivisorTooLarge { + // The divisor is too large, try again with different primes. + continue + } + if err != nil { + return nil, err + } + + e := bigmod.NewNat().SetUint(65537) + d, ok := bigmod.NewNat().InverseVarTime(e, λ) + if !ok { + // This checks that GCD(e, lcm(p-1, q-1)) = 1, which is equivalent + // to checking GCD(e, p-1) = 1 and GCD(e, q-1) = 1 separately in + // FIPS 186-5, Appendix A.1.3, steps 4.5 and 5.6. + // + // We waste a prime by retrying the whole process, since 65537 is + // probably only a factor of one of p-1 or q-1, but the probability + // of this check failing is only 1/65537, so it doesn't matter. + continue + } + + if e.ExpandFor(λ).Mul(d, λ).IsOne() == 0 { + return nil, errors.New("rsa: internal error: e*d != 1 mod λ(N)") + } + + // FIPS 186-5, A.1.1(3) requires checking that d > 2^(nlen / 2). + // + // The probability of this check failing when d is derived from + // (e, p, q) is roughly + // + // 2^(nlen/2) / 2^nlen = 2^(-nlen/2) + // + // so less than 2⁻¹²⁸ for keys larger than 256 bits. + // + // We still need to check to comply with FIPS 186-5, but knowing it has + // negligible chance of failure we can defer the check to the end of key + // generation and return an error if it fails. See [checkPrivateKey]. + + return newPrivateKey(N, 65537, d, P, Q) + } +} + +// errDivisorTooLarge is returned by [totient] when gcd(p-1, q-1) is too large. +var errDivisorTooLarge = errors.New("divisor too large") + +// totient computes the Carmichael totient function λ(N) = lcm(p-1, q-1). +func totient(p, q *bigmod.Modulus) (*bigmod.Modulus, error) { + a, b := p.Nat().SubOne(p), q.Nat().SubOne(q) + + // lcm(a, b) = a×b / gcd(a, b) = a × (b / gcd(a, b)) + + // Our GCD requires at least one of the numbers to be odd. For LCM we only + // need to preserve the larger prime power of each prime factor, so we can + // right-shift the number with the fewest trailing zeros until it's odd. + // For odd a, b and m >= n, lcm(a×2ᵐ, b×2ⁿ) = lcm(a×2ᵐ, b). + az, bz := a.TrailingZeroBitsVarTime(), b.TrailingZeroBitsVarTime() + if az < bz { + a = a.ShiftRightVarTime(az) + } else { + b = b.ShiftRightVarTime(bz) + } + + gcd, err := bigmod.NewNat().GCDVarTime(a, b) + if err != nil { + return nil, err + } + if gcd.IsOdd() == 0 { + return nil, errors.New("rsa: internal error: gcd(a, b) is even") + } + + // To avoid implementing multiple-precision division, we just try again if + // the divisor doesn't fit in a single word. This would have a chance of + // 2⁻⁶⁴ on 64-bit platforms, and 2⁻³² on 32-bit platforms, but testing 2⁻⁶⁴ + // edge cases is impractical, and we'd rather not behave differently on + // different platforms, so we reject divisors above 2³²-1. + if gcd.BitLenVarTime() > 32 { + return nil, errDivisorTooLarge + } + if gcd.IsZero() == 1 || gcd.Bits()[0] == 0 { + return nil, errors.New("rsa: internal error: gcd(a, b) is zero") + } + if rem := b.DivShortVarTime(gcd.Bits()[0]); rem != 0 { + return nil, errors.New("rsa: internal error: b is not divisible by gcd(a, b)") + } + + return bigmod.NewModulusProduct(a.Bytes(p), b.Bytes(q)) +} + +// randomPrime returns a random prime number of the given bit size following +// the process in FIPS 186-5, Appendix A.1.3. +func randomPrime(rand io.Reader, bits int) ([]byte, error) { + if bits < 16 { + return nil, errors.New("rsa: prime size must be at least 16 bits") + } + + b := make([]byte, (bits+7)/8) + for { + if err := drbg.ReadWithReader(rand, b); err != nil { + return nil, err + } + // Clear the most significant bits to reach the desired size. We use a + // mask rather than right-shifting b[0] to make it easier to inject test + // candidates, which can be represented as simple big-endian integers. + excess := len(b)*8 - bits + b[0] &= 0b1111_1111 >> excess + + // Don't let the value be too small: set the most significant two bits. + // Setting the top two bits, rather than just the top bit, means that + // when two of these values are multiplied together, the result isn't + // ever one bit short. + if excess < 7 { + b[0] |= 0b1100_0000 >> excess + } else { + b[0] |= 0b0000_0001 + b[1] |= 0b1000_0000 + } + + // Make the value odd since an even number certainly isn't prime. + b[len(b)-1] |= 1 + + // We don't need to check for p >= √2 × 2^(bits-1) (steps 4.4 and 5.4) + // because we set the top two bits above, so + // + // p > 2^(bits-1) + 2^(bits-2) = 3⁄2 × 2^(bits-1) > √2 × 2^(bits-1) + // + + // Step 5.5 requires checking that |p - q| > 2^(nlen/2 - 100). + // + // The probability of |p - q| ≤ k where p and q are uniformly random in + // the range (a, b) is 1 - (b-a-k)^2 / (b-a)^2, so the probability of + // this check failing during key generation is 2⁻⁹⁷. + // + // We still need to check to comply with FIPS 186-5, but knowing it has + // negligible chance of failure we can defer the check to the end of key + // generation and return an error if it fails. See [checkPrivateKey]. + + if isPrime(b) { + return b, nil + } + } +} + +// isPrime runs the Miller-Rabin Probabilistic Primality Test from +// FIPS 186-5, Appendix B.3.1. +// +// w must be a random odd integer greater than three in big-endian order. +// isPrime might return false positives for adversarially chosen values. +// +// isPrime is not constant-time. +func isPrime(w []byte) bool { + mr, err := millerRabinSetup(w) + if err != nil { + // w is zero, one, or even. + return false + } + + primes, err := bigmod.NewNat().SetBytes(productOfPrimes, mr.w) + // If w is too small for productOfPrimes, key generation is + // going to be fast enough anyway. + if err == nil { + _, hasInverse := primes.InverseVarTime(primes, mr.w) + if !hasInverse { + // productOfPrimes doesn't have an inverse mod w, + // so w is divisible by at least one of the primes. + return false + } + } + + // iterations is the number of Miller-Rabin rounds, each with a + // randomly-selected base. + // + // The worst case false positive rate for a single iteration is 1/4 per + // https://eprint.iacr.org/2018/749, so if w were selected adversarially, we + // would need up to 64 iterations to get to a negligible (2⁻¹²⁸) chance of + // false positive. + // + // However, since this function is only used for randomly-selected w in the + // context of RSA key generation, we can use a smaller number of iterations. + // The exact number depends on the size of the prime (and the implied + // security level). See BoringSSL for the full formula. + // https://cs.opensource.google/boringssl/boringssl/+/master:crypto/fipsmodule/bn/prime.c.inc;l=208-283;drc=3a138e43 + bits := mr.w.BitLen() + var iterations int + switch { + case bits >= 3747: + iterations = 3 + case bits >= 1345: + iterations = 4 + case bits >= 476: + iterations = 5 + case bits >= 400: + iterations = 6 + case bits >= 347: + iterations = 7 + case bits >= 308: + iterations = 8 + case bits >= 55: + iterations = 27 + default: + iterations = 34 + } + + b := make([]byte, (bits+7)/8) + for { + drbg.Read(b) + excess := len(b)*8 - bits + b[0] &= 0b1111_1111 >> excess + result, err := millerRabinIteration(mr, b) + if err != nil { + // b was rejected. + continue + } + if result == millerRabinCOMPOSITE { + return false + } + iterations-- + if iterations == 0 { + return true + } + } +} + +// productOfPrimes is the product of the first 74 primes higher than 2. +// +// The number of primes was selected to be the highest such that the product fit +// in 512 bits, so to be usable for 1024 bit RSA keys. +// +// Higher values cause fewer Miller-Rabin tests of composites (nothing can help +// with the final test on the actual prime) but make InverseVarTime take longer. +// There are diminishing returns: including the 75th prime would increase the +// success rate of trial division by 0.05%. +var productOfPrimes = []byte{ + 0x10, 0x6a, 0xa9, 0xfb, 0x76, 0x46, 0xfa, 0x6e, 0xb0, 0x81, 0x3c, 0x28, 0xc5, 0xd5, 0xf0, 0x9f, + 0x07, 0x7e, 0xc3, 0xba, 0x23, 0x8b, 0xfb, 0x99, 0xc1, 0xb6, 0x31, 0xa2, 0x03, 0xe8, 0x11, 0x87, + 0x23, 0x3d, 0xb1, 0x17, 0xcb, 0xc3, 0x84, 0x05, 0x6e, 0xf0, 0x46, 0x59, 0xa4, 0xa1, 0x1d, 0xe4, + 0x9f, 0x7e, 0xcb, 0x29, 0xba, 0xda, 0x8f, 0x98, 0x0d, 0xec, 0xec, 0xe9, 0x2e, 0x30, 0xc4, 0x8f, +} + +type millerRabin struct { + w *bigmod.Modulus + a uint + m []byte +} + +// millerRabinSetup prepares state that's reused across multiple iterations of +// the Miller-Rabin test. +func millerRabinSetup(w []byte) (*millerRabin, error) { + mr := &millerRabin{} + + // Check that w is odd, and precompute Montgomery parameters. + wm, err := bigmod.NewModulus(w) + if err != nil { + return nil, err + } + if wm.Nat().IsOdd() == 0 { + return nil, errors.New("candidate is even") + } + mr.w = wm + + // Compute m = (w-1)/2^a, where m is odd. + wMinus1 := mr.w.Nat().SubOne(mr.w) + if wMinus1.IsZero() == 1 { + return nil, errors.New("candidate is one") + } + mr.a = wMinus1.TrailingZeroBitsVarTime() + + // Store mr.m as a big-endian byte slice with leading zero bytes removed, + // for use with [bigmod.Nat.Exp]. + m := wMinus1.ShiftRightVarTime(mr.a) + mr.m = m.Bytes(mr.w) + for mr.m[0] == 0 { + mr.m = mr.m[1:] + } + + return mr, nil +} + +const millerRabinCOMPOSITE = false +const millerRabinPOSSIBLYPRIME = true + +func millerRabinIteration(mr *millerRabin, bb []byte) (bool, error) { + // Reject b ≤ 1 or b ≥ w − 1. + if len(bb) != (mr.w.BitLen()+7)/8 { + return false, errors.New("incorrect length") + } + b := bigmod.NewNat() + if _, err := b.SetBytes(bb, mr.w); err != nil { + return false, err + } + if b.IsZero() == 1 || b.IsOne() == 1 || b.IsMinusOne(mr.w) == 1 { + return false, errors.New("out-of-range candidate") + } + + // Compute b^(m*2^i) mod w for successive i. + // If b^m mod w = 1, b is a possible prime. + // If b^(m*2^i) mod w = -1 for some 0 <= i < a, b is a possible prime. + // Otherwise b is composite. + + // Start by computing and checking b^m mod w (also the i = 0 case). + z := bigmod.NewNat().Exp(b, mr.m, mr.w) + if z.IsOne() == 1 || z.IsMinusOne(mr.w) == 1 { + return millerRabinPOSSIBLYPRIME, nil + } + + // Check b^(m*2^i) mod w = -1 for 0 < i < a. + for range mr.a - 1 { + z.Mul(z, mr.w) + if z.IsMinusOne(mr.w) == 1 { + return millerRabinPOSSIBLYPRIME, nil + } + if z.IsOne() == 1 { + // Future squaring will not turn z == 1 into -1. + break + } + } + + return millerRabinCOMPOSITE, nil +} diff --git a/crypto/internal/fips140/rsa/keygen_test.go b/crypto/internal/fips140/rsa/keygen_test.go new file mode 100644 index 00000000000..e60d19733ae --- /dev/null +++ b/crypto/internal/fips140/rsa/keygen_test.go @@ -0,0 +1,182 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "bufio" + "encoding/hex" + "fmt" + "math/big" + "os" + "strings" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" +) + +func TestMillerRabin(t *testing.T) { + f, err := os.Open("testdata/miller_rabin_tests.txt") + if err != nil { + t.Fatal(err) + } + + var expected bool + var W, B string + var lineNum int + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lineNum++ + line := scanner.Text() + if len(line) == 0 || line[0] == '#' { + continue + } + + k, v, _ := strings.Cut(line, " = ") + switch k { + case "Result": + switch v { + case "Composite": + expected = millerRabinCOMPOSITE + case "PossiblyPrime": + expected = millerRabinPOSSIBLYPRIME + default: + t.Fatalf("unknown result %q on line %d", v, lineNum) + } + case "W": + W = v + case "B": + B = v + + t.Run(fmt.Sprintf("line %d", lineNum), func(t *testing.T) { + if len(W)%2 != 0 { + W = "0" + W + } + for len(B) < len(W) { + B = "0" + B + } + + mr, err := millerRabinSetup(decodeHex(t, W)) + if err != nil { + t.Logf("W = %s", W) + t.Logf("B = %s", B) + t.Fatalf("failed to set up Miller-Rabin test: %v", err) + } + + result, err := millerRabinIteration(mr, decodeHex(t, B)) + if err != nil { + t.Logf("W = %s", W) + t.Logf("B = %s", B) + t.Fatalf("failed to run Miller-Rabin test: %v", err) + } + + if result != expected { + t.Logf("W = %s", W) + t.Logf("B = %s", B) + t.Fatalf("unexpected result: got %v, want %v", result, expected) + } + }) + default: + t.Fatalf("unknown key %q on line %d", k, lineNum) + } + } + if err := scanner.Err(); err != nil { + t.Fatal(err) + } +} + +func TestTotient(t *testing.T) { + f, err := os.Open("testdata/gcd_lcm_tests.txt") + if err != nil { + t.Fatal(err) + } + + var GCD, A, B, LCM string + var lineNum int + scanner := bufio.NewScanner(f) + for scanner.Scan() { + lineNum++ + line := scanner.Text() + if len(line) == 0 || line[0] == '#' { + continue + } + + k, v, _ := strings.Cut(line, " = ") + switch k { + case "GCD": + GCD = v + case "A": + A = v + case "B": + B = v + case "LCM": + LCM = v + + t.Run(fmt.Sprintf("line %d", lineNum), func(t *testing.T) { + if A == "0" || B == "0" { + t.Skip("skipping test with zero input") + } + if LCM == "1" { + t.Skip("skipping test with LCM=1") + } + + p, _ := bigmod.NewModulus(addOne(decodeHex(t, A))) + a, _ := bigmod.NewNat().SetBytes(decodeHex(t, A), p) + q, _ := bigmod.NewModulus(addOne(decodeHex(t, B))) + b, _ := bigmod.NewNat().SetBytes(decodeHex(t, B), q) + + gcd, err := bigmod.NewNat().GCDVarTime(a, b) + // GCD doesn't work if a and b are both even, but LCM handles it. + if err == nil { + if got := strings.TrimLeft(hex.EncodeToString(gcd.Bytes(p)), "0"); got != GCD { + t.Fatalf("unexpected GCD: got %s, want %s", got, GCD) + } + } + + lcm, err := totient(p, q) + if oddDivisorLargerThan32Bits(decodeHex(t, GCD)) { + if err != errDivisorTooLarge { + t.Fatalf("expected divisor too large error, got %v", err) + } + t.Skip("GCD too large") + } + if err != nil { + t.Fatalf("failed to calculate totient: %v", err) + } + if got := strings.TrimLeft(hex.EncodeToString(lcm.Nat().Bytes(lcm)), "0"); got != LCM { + t.Fatalf("unexpected LCM: got %s, want %s", got, LCM) + } + }) + default: + t.Fatalf("unknown key %q on line %d", k, lineNum) + } + } + if err := scanner.Err(); err != nil { + t.Fatal(err) + } +} + +func oddDivisorLargerThan32Bits(b []byte) bool { + x := new(big.Int).SetBytes(b) + x.Rsh(x, x.TrailingZeroBits()) + return x.BitLen() > 32 +} + +func addOne(b []byte) []byte { + x := new(big.Int).SetBytes(b) + x.Add(x, big.NewInt(1)) + return x.Bytes() +} + +func decodeHex(t *testing.T, s string) []byte { + t.Helper() + if len(s)%2 != 0 { + s = "0" + s + } + b, err := hex.DecodeString(s) + if err != nil { + t.Fatalf("failed to decode hex %q: %v", s, err) + } + return b +} diff --git a/crypto/internal/fips140/rsa/pkcs1v15.go b/crypto/internal/fips140/rsa/pkcs1v15.go new file mode 100644 index 00000000000..efcdd3a73c5 --- /dev/null +++ b/crypto/internal/fips140/rsa/pkcs1v15.go @@ -0,0 +1,139 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +// This file implements signing and verification using PKCS #1 v1.5 signatures. + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +// These are ASN1 DER structures: +// +// DigestInfo ::= SEQUENCE { +// digestAlgorithm AlgorithmIdentifier, +// digest OCTET STRING +// } +// +// For performance, we don't use the generic ASN1 encoder. Rather, we +// precompute a prefix of the digest value that makes a valid ASN1 DER string +// with the correct contents. +var hashPrefixes = map[string][]byte{ + "MD5": {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, + "SHA-1": {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, + "SHA-224": {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c}, + "SHA-256": {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, + "SHA-384": {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, + "SHA-512": {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}, + "SHA-512/224": {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x05, 0x05, 0x00, 0x04, 0x1C}, + "SHA-512/256": {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x06, 0x05, 0x00, 0x04, 0x20}, + "SHA3-224": {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x07, 0x05, 0x00, 0x04, 0x1C}, + "SHA3-256": {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x08, 0x05, 0x00, 0x04, 0x20}, + "SHA3-384": {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x09, 0x05, 0x00, 0x04, 0x30}, + "SHA3-512": {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x0a, 0x05, 0x00, 0x04, 0x40}, + "MD5+SHA1": {}, // A special TLS case which doesn't use an ASN1 prefix. + "RIPEMD-160": {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14}, +} + +// SignPKCS1v15 calculates an RSASSA-PKCS1-v1.5 signature. +// +// hash is the name of the hash function as returned by [crypto.Hash.String] +// or the empty string to indicate that the message is signed directly. +func SignPKCS1v15(priv *PrivateKey, hash string, hashed []byte) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHashName(hash) + + return signPKCS1v15(priv, hash, hashed) +} + +func signPKCS1v15(priv *PrivateKey, hash string, hashed []byte) ([]byte, error) { + em, err := pkcs1v15ConstructEM(&priv.pub, hash, hashed) + if err != nil { + return nil, err + } + + return decrypt(priv, em, withCheck) +} + +func pkcs1v15ConstructEM(pub *PublicKey, hash string, hashed []byte) ([]byte, error) { + // Special case: "" is used to indicate that the data is signed directly. + var prefix []byte + if hash != "" { + var ok bool + prefix, ok = hashPrefixes[hash] + if !ok { + return nil, errors.New("crypto/rsa: unsupported hash function") + } + } + + // EM = 0x00 || 0x01 || PS || 0x00 || T + k := pub.Size() + if k < len(prefix)+len(hashed)+2+8+1 { + return nil, ErrMessageTooLong + } + em := make([]byte, k) + em[1] = 1 + for i := 2; i < k-len(prefix)-len(hashed)-1; i++ { + em[i] = 0xff + } + copy(em[k-len(prefix)-len(hashed):], prefix) + copy(em[k-len(hashed):], hashed) + return em, nil +} + +// VerifyPKCS1v15 verifies an RSASSA-PKCS1-v1.5 signature. +// +// hash is the name of the hash function as returned by [crypto.Hash.String] +// or the empty string to indicate that the message is signed directly. +func VerifyPKCS1v15(pub *PublicKey, hash string, hashed []byte, sig []byte) error { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHashName(hash) + + return verifyPKCS1v15(pub, hash, hashed, sig) +} + +func verifyPKCS1v15(pub *PublicKey, hash string, hashed []byte, sig []byte) error { + if fipsApproved, err := checkPublicKey(pub); err != nil { + return err + } else if !fipsApproved { + fips140.RecordNonApproved() + } + + // RFC 8017 Section 8.2.2: If the length of the signature S is not k + // octets (where k is the length in octets of the RSA modulus n), output + // "invalid signature" and stop. + if pub.Size() != len(sig) { + return ErrVerification + } + + em, err := encrypt(pub, sig) + if err != nil { + return ErrVerification + } + + expected, err := pkcs1v15ConstructEM(pub, hash, hashed) + if err != nil { + return ErrVerification + } + if !bytes.Equal(em, expected) { + return ErrVerification + } + + return nil +} + +func checkApprovedHashName(hash string) { + switch hash { + case "SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA-512/224", "SHA-512/256", + "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512": + default: + fips140.RecordNonApproved() + } +} diff --git a/crypto/internal/fips140/rsa/pkcs1v15_test.go b/crypto/internal/fips140/rsa/pkcs1v15_test.go new file mode 100644 index 00000000000..5c94543ab4c --- /dev/null +++ b/crypto/internal/fips140/rsa/pkcs1v15_test.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "bytes" + "crypto" + "testing" + + "github.com/runZeroInc/excrypto/crypto/x509/pkix" + "github.com/runZeroInc/excrypto/encoding/asn1" +) + +func TestHashPrefixes(t *testing.T) { + prefixes := map[crypto.Hash]asn1.ObjectIdentifier{ + // RFC 3370, Section 2.1 and 2.2 + // + // sha-1 OBJECT IDENTIFIER ::= { iso(1) identified-organization(3) + // oiw(14) secsig(3) algorithm(2) 26 } + // + // md5 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) + // rsadsi(113549) digestAlgorithm(2) 5 } + crypto.MD5: {1, 2, 840, 113549, 2, 5}, + crypto.SHA1: {1, 3, 14, 3, 2, 26}, + + // https://csrc.nist.gov/projects/computer-security-objects-register/algorithm-registration + // + // nistAlgorithms OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) country(16) us(840) + // organization(1) gov(101) csor(3) nistAlgorithm(4) } + // + // hashAlgs OBJECT IDENTIFIER ::= { nistAlgorithms 2 } + // + // id-sha256 OBJECT IDENTIFIER ::= { hashAlgs 1 } + // id-sha384 OBJECT IDENTIFIER ::= { hashAlgs 2 } + // id-sha512 OBJECT IDENTIFIER ::= { hashAlgs 3 } + // id-sha224 OBJECT IDENTIFIER ::= { hashAlgs 4 } + // id-sha512-224 OBJECT IDENTIFIER ::= { hashAlgs 5 } + // id-sha512-256 OBJECT IDENTIFIER ::= { hashAlgs 6 } + // id-sha3-224 OBJECT IDENTIFIER ::= { hashAlgs 7 } + // id-sha3-256 OBJECT IDENTIFIER ::= { hashAlgs 8 } + // id-sha3-384 OBJECT IDENTIFIER ::= { hashAlgs 9 } + // id-sha3-512 OBJECT IDENTIFIER ::= { hashAlgs 10 } + crypto.SHA224: {2, 16, 840, 1, 101, 3, 4, 2, 4}, + crypto.SHA256: {2, 16, 840, 1, 101, 3, 4, 2, 1}, + crypto.SHA384: {2, 16, 840, 1, 101, 3, 4, 2, 2}, + crypto.SHA512: {2, 16, 840, 1, 101, 3, 4, 2, 3}, + crypto.SHA512_224: {2, 16, 840, 1, 101, 3, 4, 2, 5}, + crypto.SHA512_256: {2, 16, 840, 1, 101, 3, 4, 2, 6}, + crypto.SHA3_224: {2, 16, 840, 1, 101, 3, 4, 2, 7}, + crypto.SHA3_256: {2, 16, 840, 1, 101, 3, 4, 2, 8}, + crypto.SHA3_384: {2, 16, 840, 1, 101, 3, 4, 2, 9}, + crypto.SHA3_512: {2, 16, 840, 1, 101, 3, 4, 2, 10}, + } + + for h, oid := range prefixes { + want, err := asn1.Marshal(struct { + HashAlgorithm pkix.AlgorithmIdentifier + Hash []byte + }{ + HashAlgorithm: pkix.AlgorithmIdentifier{ + Algorithm: oid, + Parameters: asn1.NullRawValue, + }, + Hash: make([]byte, h.Size()), + }) + if err != nil { + t.Fatal(err) + } + want = want[:len(want)-h.Size()] + got := hashPrefixes[h.String()] + if !bytes.Equal(got, want) { + t.Errorf("%s: got %x, want %x", h, got, want) + } + } +} diff --git a/crypto/internal/fips140/rsa/pkcs1v22.go b/crypto/internal/fips140/rsa/pkcs1v22.go new file mode 100644 index 00000000000..ac2558551ff --- /dev/null +++ b/crypto/internal/fips140/rsa/pkcs1v22.go @@ -0,0 +1,473 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +// This file implements the RSASSA-PSS signature scheme and the RSAES-OAEP +// encryption scheme according to RFC 8017, aka PKCS #1 v2.2. + +import ( + "bytes" + "errors" + "io" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" +) + +// Per RFC 8017, Section 9.1 +// +// EM = MGF1 xor DB || H( 8*0x00 || mHash || salt ) || 0xbc +// +// where +// +// DB = PS || 0x01 || salt +// +// and PS can be empty so +// +// emLen = dbLen + hLen + 1 = psLen + sLen + hLen + 2 +// + +// incCounter increments a four byte, big-endian counter. +func incCounter(c *[4]byte) { + if c[3]++; c[3] != 0 { + return + } + if c[2]++; c[2] != 0 { + return + } + if c[1]++; c[1] != 0 { + return + } + c[0]++ +} + +// mgf1XOR XORs the bytes in out with a mask generated using the MGF1 function +// specified in PKCS #1 v2.1. +func mgf1XOR(out []byte, hash fips140.Hash, seed []byte) { + var counter [4]byte + var digest []byte + + done := 0 + for done < len(out) { + hash.Reset() + hash.Write(seed) + hash.Write(counter[0:4]) + digest = hash.Sum(digest[:0]) + + for i := 0; i < len(digest) && done < len(out); i++ { + out[done] ^= digest[i] + done++ + } + incCounter(&counter) + } +} + +func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash fips140.Hash) ([]byte, error) { + // See RFC 8017, Section 9.1.1. + + hLen := hash.Size() + sLen := len(salt) + emLen := (emBits + 7) / 8 + + // 1. If the length of M is greater than the input limitation for the + // hash function (2^61 - 1 octets for SHA-1), output "message too + // long" and stop. + // + // 2. Let mHash = Hash(M), an octet string of length hLen. + + if len(mHash) != hLen { + return nil, errors.New("crypto/rsa: input must be hashed with given hash") + } + + // 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. + + if emLen < hLen+sLen+2 { + return nil, ErrMessageTooLong + } + + em := make([]byte, emLen) + psLen := emLen - sLen - hLen - 2 + db := em[:psLen+1+sLen] + h := em[psLen+1+sLen : emLen-1] + + // 4. Generate a random octet string salt of length sLen; if sLen = 0, + // then salt is the empty string. + // + // 5. Let + // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; + // + // M' is an octet string of length 8 + hLen + sLen with eight + // initial zero octets. + // + // 6. Let H = Hash(M'), an octet string of length hLen. + + var prefix [8]byte + + hash.Reset() + hash.Write(prefix[:]) + hash.Write(mHash) + hash.Write(salt) + + h = hash.Sum(h[:0]) + + // 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2 + // zero octets. The length of PS may be 0. + // + // 8. Let DB = PS || 0x01 || salt; DB is an octet string of length + // emLen - hLen - 1. + + db[psLen] = 0x01 + copy(db[psLen+1:], salt) + + // 9. Let dbMask = MGF(H, emLen - hLen - 1). + // + // 10. Let maskedDB = DB \xor dbMask. + + mgf1XOR(db, hash, h) + + // 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in + // maskedDB to zero. + + db[0] &= 0xff >> (8*emLen - emBits) + + // 12. Let EM = maskedDB || H || 0xbc. + em[emLen-1] = 0xbc + + // 13. Output EM. + return em, nil +} + +const pssSaltLengthAutodetect = -1 + +func emsaPSSVerify(mHash, em []byte, emBits, sLen int, hash fips140.Hash) error { + // See RFC 8017, Section 9.1.2. + + hLen := hash.Size() + emLen := (emBits + 7) / 8 + if emLen != len(em) { + return errors.New("rsa: internal error: inconsistent length") + } + + // 1. If the length of M is greater than the input limitation for the + // hash function (2^61 - 1 octets for SHA-1), output "inconsistent" + // and stop. + // + // 2. Let mHash = Hash(M), an octet string of length hLen. + if hLen != len(mHash) { + return ErrVerification + } + + // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. + if emLen < hLen+sLen+2 { + return ErrVerification + } + + // 4. If the rightmost octet of EM does not have hexadecimal value + // 0xbc, output "inconsistent" and stop. + if em[emLen-1] != 0xbc { + return ErrVerification + } + + // 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and + // let H be the next hLen octets. + db := em[:emLen-hLen-1] + h := em[emLen-hLen-1 : emLen-1] + + // 6. If the leftmost 8 * emLen - emBits bits of the leftmost octet in + // maskedDB are not all equal to zero, output "inconsistent" and + // stop. + var bitMask byte = 0xff >> (8*emLen - emBits) + if em[0] & ^bitMask != 0 { + return ErrVerification + } + + // 7. Let dbMask = MGF(H, emLen - hLen - 1). + // + // 8. Let DB = maskedDB \xor dbMask. + mgf1XOR(db, hash, h) + + // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB + // to zero. + db[0] &= bitMask + + // If we don't know the salt length, look for the 0x01 delimiter. + if sLen == pssSaltLengthAutodetect { + psLen := bytes.IndexByte(db, 0x01) + if psLen < 0 { + return ErrVerification + } + sLen = len(db) - psLen - 1 + } + + // FIPS 186-5, Section 5.4(g): "the length (in bytes) of the salt (sLen) + // shall satisfy 0 ≤ sLen ≤ hLen". + if sLen > hLen { + fips140.RecordNonApproved() + } + + // 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero + // or if the octet at position emLen - hLen - sLen - 1 (the leftmost + // position is "position 1") does not have hexadecimal value 0x01, + // output "inconsistent" and stop. + psLen := emLen - hLen - sLen - 2 + for _, e := range db[:psLen] { + if e != 0x00 { + return ErrVerification + } + } + if db[psLen] != 0x01 { + return ErrVerification + } + + // 11. Let salt be the last sLen octets of DB. + salt := db[len(db)-sLen:] + + // 12. Let + // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ; + // M' is an octet string of length 8 + hLen + sLen with eight + // initial zero octets. + // + // 13. Let H' = Hash(M'), an octet string of length hLen. + hash.Reset() + var prefix [8]byte + hash.Write(prefix[:]) + hash.Write(mHash) + hash.Write(salt) + + h0 := hash.Sum(nil) + + // 14. If H = H', output "consistent." Otherwise, output "inconsistent." + if !bytes.Equal(h0, h) { // TODO: constant time? + return ErrVerification + } + return nil +} + +// PSSMaxSaltLength returns the maximum salt length for a given public key and +// hash function. +func PSSMaxSaltLength(pub *PublicKey, hash fips140.Hash) (int, error) { + saltLength := (pub.N.BitLen()-1+7)/8 - 2 - hash.Size() + if saltLength < 0 { + return 0, ErrMessageTooLong + } + // FIPS 186-5, Section 5.4(g): "the length (in bytes) of the salt (sLen) + // shall satisfy 0 ≤ sLen ≤ hLen". + if fips140.Enabled && saltLength > hash.Size() { + return hash.Size(), nil + } + return saltLength, nil +} + +// SignPSS calculates the signature of hashed using RSASSA-PSS. +func SignPSS(rand io.Reader, priv *PrivateKey, hash fips140.Hash, hashed []byte, saltLength int) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHash(hash) + + // Note that while we don't commit to deterministic execution with respect + // to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law + // it's probably relied upon by some. It's a tolerable promise because a + // well-specified number of random bytes is included in the signature, in a + // well-specified way. + + if saltLength < 0 { + return nil, errors.New("crypto/rsa: salt length cannot be negative") + } + // FIPS 186-5, Section 5.4(g): "the length (in bytes) of the salt (sLen) + // shall satisfy 0 ≤ sLen ≤ hLen". + if saltLength > hash.Size() { + fips140.RecordNonApproved() + } + salt := make([]byte, saltLength) + if err := drbg.ReadWithReaderDeterministic(rand, salt); err != nil { + return nil, err + } + + emBits := priv.pub.N.BitLen() - 1 + em, err := emsaPSSEncode(hashed, emBits, salt, hash) + if err != nil { + return nil, err + } + + // RFC 8017: "Note that the octet length of EM will be one less than k if + // modBits - 1 is divisible by 8 and equal to k otherwise, where k is the + // length in octets of the RSA modulus n." 🙄 + // + // This is extremely annoying, as all other encrypt and decrypt inputs are + // always the exact same size as the modulus. Since it only happens for + // weird modulus sizes, fix it by padding inefficiently. + if emLen, k := len(em), priv.pub.Size(); emLen < k { + emNew := make([]byte, k) + copy(emNew[k-emLen:], em) + em = emNew + } + + return decrypt(priv, em, withCheck) +} + +// VerifyPSS verifies sig with RSASSA-PSS automatically detecting the salt length. +func VerifyPSS(pub *PublicKey, hash fips140.Hash, digest []byte, sig []byte) error { + return verifyPSS(pub, hash, digest, sig, pssSaltLengthAutodetect) +} + +// VerifyPSS verifies sig with RSASSA-PSS and an expected salt length. +func VerifyPSSWithSaltLength(pub *PublicKey, hash fips140.Hash, digest []byte, sig []byte, saltLength int) error { + if saltLength < 0 { + return errors.New("crypto/rsa: salt length cannot be negative") + } + return verifyPSS(pub, hash, digest, sig, saltLength) +} + +func verifyPSS(pub *PublicKey, hash fips140.Hash, digest []byte, sig []byte, saltLength int) error { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHash(hash) + if fipsApproved, err := checkPublicKey(pub); err != nil { + return err + } else if !fipsApproved { + fips140.RecordNonApproved() + } + + if len(sig) != pub.Size() { + return ErrVerification + } + + emBits := pub.N.BitLen() - 1 + emLen := (emBits + 7) / 8 + em, err := encrypt(pub, sig) + if err != nil { + return ErrVerification + } + + // Like in signPSSWithSalt, deal with mismatches between emLen and the size + // of the modulus. The spec would have us wire emLen into the encoding + // function, but we'd rather always encode to the size of the modulus and + // then strip leading zeroes if necessary. This only happens for weird + // modulus sizes anyway. + for len(em) > emLen && len(em) > 0 { + if em[0] != 0 { + return ErrVerification + } + em = em[1:] + } + + return emsaPSSVerify(digest, em, emBits, saltLength, hash) +} + +func checkApprovedHash(hash fips140.Hash) { + switch hash.(type) { + case *sha256.Digest, *sha512.Digest, *sha3.Digest: + default: + fips140.RecordNonApproved() + } +} + +// EncryptOAEP encrypts the given message with RSAES-OAEP. +func EncryptOAEP(hash, mgfHash fips140.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { + // Note that while we don't commit to deterministic execution with respect + // to the random stream, we also don't apply MaybeReadByte, so per Hyrum's + // Law it's probably relied upon by some. It's a tolerable promise because a + // well-specified number of random bytes is included in the ciphertext, in a + // well-specified way. + + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHash(hash) + if fipsApproved, err := checkPublicKey(pub); err != nil { + return nil, err + } else if !fipsApproved { + fips140.RecordNonApproved() + } + k := pub.Size() + if len(msg) > k-2*hash.Size()-2 { + return nil, ErrMessageTooLong + } + + hash.Reset() + hash.Write(label) + lHash := hash.Sum(nil) + + em := make([]byte, k) + seed := em[1 : 1+hash.Size()] + db := em[1+hash.Size():] + + copy(db[0:hash.Size()], lHash) + db[len(db)-len(msg)-1] = 1 + copy(db[len(db)-len(msg):], msg) + + if err := drbg.ReadWithReaderDeterministic(random, seed); err != nil { + return nil, err + } + + mgf1XOR(db, mgfHash, seed) + mgf1XOR(seed, mgfHash, db) + + return encrypt(pub, em) +} + +// DecryptOAEP decrypts ciphertext using RSAES-OAEP. +func DecryptOAEP(hash, mgfHash fips140.Hash, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { + fipsSelfTest() + fips140.RecordApproved() + checkApprovedHash(hash) + + k := priv.pub.Size() + if len(ciphertext) > k || + k < hash.Size()*2+2 { + return nil, ErrDecryption + } + + em, err := decrypt(priv, ciphertext, noCheck) + if err != nil { + return nil, err + } + + hash.Reset() + hash.Write(label) + lHash := hash.Sum(nil) + + firstByteIsZero := subtle.ConstantTimeByteEq(em[0], 0) + + seed := em[1 : hash.Size()+1] + db := em[hash.Size()+1:] + + mgf1XOR(seed, mgfHash, db) + mgf1XOR(db, mgfHash, seed) + + lHash2 := db[0:hash.Size()] + + // We have to validate the plaintext in constant time in order to avoid + // attacks like: J. Manger. A Chosen Ciphertext Attack on RSA Optimal + // Asymmetric Encryption Padding (OAEP) as Standardized in PKCS #1 + // v2.0. In J. Kilian, editor, Advances in Cryptology. + lHash2Good := subtle.ConstantTimeCompare(lHash, lHash2) + + // The remainder of the plaintext must be zero or more 0x00, followed + // by 0x01, followed by the message. + // lookingForIndex: 1 iff we are still looking for the 0x01 + // index: the offset of the first 0x01 byte + // invalid: 1 iff we saw a non-zero byte before the 0x01. + var lookingForIndex, index, invalid int + lookingForIndex = 1 + rest := db[hash.Size():] + + for i := 0; i < len(rest); i++ { + equals0 := subtle.ConstantTimeByteEq(rest[i], 0) + equals1 := subtle.ConstantTimeByteEq(rest[i], 1) + index = subtle.ConstantTimeSelect(lookingForIndex&equals1, i, index) + lookingForIndex = subtle.ConstantTimeSelect(equals1, 0, lookingForIndex) + invalid = subtle.ConstantTimeSelect(lookingForIndex&^equals0, 1, invalid) + } + + if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 { + return nil, ErrDecryption + } + + return rest[index+1:], nil +} diff --git a/crypto/internal/fips140/rsa/pkcs1v22_test.go b/crypto/internal/fips140/rsa/pkcs1v22_test.go new file mode 100644 index 00000000000..fc607c8b358 --- /dev/null +++ b/crypto/internal/fips140/rsa/pkcs1v22_test.go @@ -0,0 +1,65 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "bytes" + "testing" + + "github.com/runZeroInc/excrypto/crypto/sha1" +) + +func TestEMSAPSS(t *testing.T) { + // Test vector in file pss-int.txt from: ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip + msg := []byte{ + 0x85, 0x9e, 0xef, 0x2f, 0xd7, 0x8a, 0xca, 0x00, 0x30, 0x8b, + 0xdc, 0x47, 0x11, 0x93, 0xbf, 0x55, 0xbf, 0x9d, 0x78, 0xdb, + 0x8f, 0x8a, 0x67, 0x2b, 0x48, 0x46, 0x34, 0xf3, 0xc9, 0xc2, + 0x6e, 0x64, 0x78, 0xae, 0x10, 0x26, 0x0f, 0xe0, 0xdd, 0x8c, + 0x08, 0x2e, 0x53, 0xa5, 0x29, 0x3a, 0xf2, 0x17, 0x3c, 0xd5, + 0x0c, 0x6d, 0x5d, 0x35, 0x4f, 0xeb, 0xf7, 0x8b, 0x26, 0x02, + 0x1c, 0x25, 0xc0, 0x27, 0x12, 0xe7, 0x8c, 0xd4, 0x69, 0x4c, + 0x9f, 0x46, 0x97, 0x77, 0xe4, 0x51, 0xe7, 0xf8, 0xe9, 0xe0, + 0x4c, 0xd3, 0x73, 0x9c, 0x6b, 0xbf, 0xed, 0xae, 0x48, 0x7f, + 0xb5, 0x56, 0x44, 0xe9, 0xca, 0x74, 0xff, 0x77, 0xa5, 0x3c, + 0xb7, 0x29, 0x80, 0x2f, 0x6e, 0xd4, 0xa5, 0xff, 0xa8, 0xba, + 0x15, 0x98, 0x90, 0xfc, + } + salt := []byte{ + 0xe3, 0xb5, 0xd5, 0xd0, 0x02, 0xc1, 0xbc, 0xe5, 0x0c, 0x2b, + 0x65, 0xef, 0x88, 0xa1, 0x88, 0xd8, 0x3b, 0xce, 0x7e, 0x61, + } + expected := []byte{ + 0x66, 0xe4, 0x67, 0x2e, 0x83, 0x6a, 0xd1, 0x21, 0xba, 0x24, + 0x4b, 0xed, 0x65, 0x76, 0xb8, 0x67, 0xd9, 0xa4, 0x47, 0xc2, + 0x8a, 0x6e, 0x66, 0xa5, 0xb8, 0x7d, 0xee, 0x7f, 0xbc, 0x7e, + 0x65, 0xaf, 0x50, 0x57, 0xf8, 0x6f, 0xae, 0x89, 0x84, 0xd9, + 0xba, 0x7f, 0x96, 0x9a, 0xd6, 0xfe, 0x02, 0xa4, 0xd7, 0x5f, + 0x74, 0x45, 0xfe, 0xfd, 0xd8, 0x5b, 0x6d, 0x3a, 0x47, 0x7c, + 0x28, 0xd2, 0x4b, 0xa1, 0xe3, 0x75, 0x6f, 0x79, 0x2d, 0xd1, + 0xdc, 0xe8, 0xca, 0x94, 0x44, 0x0e, 0xcb, 0x52, 0x79, 0xec, + 0xd3, 0x18, 0x3a, 0x31, 0x1f, 0xc8, 0x96, 0xda, 0x1c, 0xb3, + 0x93, 0x11, 0xaf, 0x37, 0xea, 0x4a, 0x75, 0xe2, 0x4b, 0xdb, + 0xfd, 0x5c, 0x1d, 0xa0, 0xde, 0x7c, 0xec, 0xdf, 0x1a, 0x89, + 0x6f, 0x9d, 0x8b, 0xc8, 0x16, 0xd9, 0x7c, 0xd7, 0xa2, 0xc4, + 0x3b, 0xad, 0x54, 0x6f, 0xbe, 0x8c, 0xfe, 0xbc, + } + + hash := sha1.New() + hash.Write(msg) + hashed := hash.Sum(nil) + + encoded, err := emsaPSSEncode(hashed, 1023, salt, sha1.New()) + if err != nil { + t.Errorf("Error from emsaPSSEncode: %s\n", err) + } + if !bytes.Equal(encoded, expected) { + t.Errorf("Bad encoding. got %x, want %x", encoded, expected) + } + + if err = emsaPSSVerify(hashed, encoded, 1023, len(salt), sha1.New()); err != nil { + t.Errorf("Bad verification: %s", err) + } +} diff --git a/crypto/internal/fips140/rsa/rsa.go b/crypto/internal/fips140/rsa/rsa.go new file mode 100644 index 00000000000..83b3b8453f3 --- /dev/null +++ b/crypto/internal/fips140/rsa/rsa.go @@ -0,0 +1,460 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" +) + +type PublicKey struct { + N *bigmod.Modulus + E int +} + +// Size returns the modulus size in bytes. Raw signatures and ciphertexts +// for or by this public key will have the same size. +func (pub *PublicKey) Size() int { + return (pub.N.BitLen() + 7) / 8 +} + +type PrivateKey struct { + // pub has already been checked with checkPublicKey. + pub PublicKey + d *bigmod.Nat + // The following values are not set for deprecated multi-prime keys. + // + // Since they are always set for keys in FIPS mode, for SP 800-56B Rev. 2 + // purposes we always use the Chinese Remainder Theorem (CRT) format. + p, q *bigmod.Modulus // p × q = n + // dP and dQ are used as exponents, so we store them as big-endian byte + // slices to be passed to [bigmod.Nat.Exp]. + dP []byte // d mod (p - 1) + dQ []byte // d mod (q - 1) + qInv *bigmod.Nat // qInv = q⁻¹ mod p + // fipsApproved is false if this key does not comply with FIPS 186-5 or + // SP 800-56B Rev. 2. + fipsApproved bool +} + +func (priv *PrivateKey) PublicKey() *PublicKey { + return &priv.pub +} + +// NewPrivateKey creates a new RSA private key from the given parameters. +// +// All values are in big-endian byte slice format, and may have leading zeros +// or be shorter if leading zeroes were trimmed. +func NewPrivateKey(N []byte, e int, d, P, Q []byte) (*PrivateKey, error) { + n, err := bigmod.NewModulus(N) + if err != nil { + return nil, err + } + p, err := bigmod.NewModulus(P) + if err != nil { + return nil, err + } + q, err := bigmod.NewModulus(Q) + if err != nil { + return nil, err + } + dN, err := bigmod.NewNat().SetBytes(d, n) + if err != nil { + return nil, err + } + return newPrivateKey(n, e, dN, p, q) +} + +func newPrivateKey(n *bigmod.Modulus, e int, d *bigmod.Nat, p, q *bigmod.Modulus) (*PrivateKey, error) { + pMinusOne := p.Nat().SubOne(p) + pMinusOneMod, err := bigmod.NewModulus(pMinusOne.Bytes(p)) + if err != nil { + return nil, err + } + dP := bigmod.NewNat().Mod(d, pMinusOneMod).Bytes(pMinusOneMod) + + qMinusOne := q.Nat().SubOne(q) + qMinusOneMod, err := bigmod.NewModulus(qMinusOne.Bytes(q)) + if err != nil { + return nil, err + } + dQ := bigmod.NewNat().Mod(d, qMinusOneMod).Bytes(qMinusOneMod) + + // Constant-time modular inversion with prime modulus by Fermat's Little + // Theorem: qInv = q⁻¹ mod p = q^(p-2) mod p. + if p.Nat().IsOdd() == 0 { + // [bigmod.Nat.Exp] requires an odd modulus. + return nil, errors.New("crypto/rsa: p is even") + } + pMinusTwo := p.Nat().SubOne(p).SubOne(p).Bytes(p) + qInv := bigmod.NewNat().Mod(q.Nat(), p) + qInv.Exp(qInv, pMinusTwo, p) + + pk := &PrivateKey{ + pub: PublicKey{ + N: n, E: e, + }, + d: d, p: p, q: q, + dP: dP, dQ: dQ, qInv: qInv, + } + if err := checkPrivateKey(pk); err != nil { + return nil, err + } + return pk, nil +} + +// NewPrivateKeyWithPrecomputation creates a new RSA private key from the given +// parameters, which include precomputed CRT values. +func NewPrivateKeyWithPrecomputation(N []byte, e int, d, P, Q, dP, dQ, qInv []byte) (*PrivateKey, error) { + n, err := bigmod.NewModulus(N) + if err != nil { + return nil, err + } + p, err := bigmod.NewModulus(P) + if err != nil { + return nil, err + } + q, err := bigmod.NewModulus(Q) + if err != nil { + return nil, err + } + dN, err := bigmod.NewNat().SetBytes(d, n) + if err != nil { + return nil, err + } + qInvNat, err := bigmod.NewNat().SetBytes(qInv, p) + if err != nil { + return nil, err + } + + pk := &PrivateKey{ + pub: PublicKey{ + N: n, E: e, + }, + d: dN, p: p, q: q, + dP: dP, dQ: dQ, qInv: qInvNat, + } + if err := checkPrivateKey(pk); err != nil { + return nil, err + } + return pk, nil +} + +// NewPrivateKeyWithoutCRT creates a new RSA private key from the given parameters. +// +// This is meant for deprecated multi-prime keys, and is not FIPS 140 compliant. +func NewPrivateKeyWithoutCRT(N []byte, e int, d []byte) (*PrivateKey, error) { + n, err := bigmod.NewModulus(N) + if err != nil { + return nil, err + } + dN, err := bigmod.NewNat().SetBytes(d, n) + if err != nil { + return nil, err + } + pk := &PrivateKey{ + pub: PublicKey{ + N: n, E: e, + }, + d: dN, + } + if err := checkPrivateKey(pk); err != nil { + return nil, err + } + return pk, nil +} + +// Export returns the key parameters in big-endian byte slice format. +// +// P, Q, dP, dQ, and qInv may be nil if the key was created with +// NewPrivateKeyWithoutCRT. +func (priv *PrivateKey) Export() (N []byte, e int, d, P, Q, dP, dQ, qInv []byte) { + N = priv.pub.N.Nat().Bytes(priv.pub.N) + e = priv.pub.E + d = priv.d.Bytes(priv.pub.N) + if priv.dP == nil { + return + } + P = priv.p.Nat().Bytes(priv.p) + Q = priv.q.Nat().Bytes(priv.q) + dP = bytes.Clone(priv.dP) + dQ = bytes.Clone(priv.dQ) + qInv = priv.qInv.Bytes(priv.p) + return +} + +// checkPrivateKey is called by the NewPrivateKey and GenerateKey functions, and +// is allowed to modify priv.fipsApproved. +func checkPrivateKey(priv *PrivateKey) error { + priv.fipsApproved = true + + if fipsApproved, err := checkPublicKey(&priv.pub); err != nil { + return err + } else if !fipsApproved { + priv.fipsApproved = false + } + + if priv.dP == nil { + // Legacy and deprecated multi-prime keys. + priv.fipsApproved = false + return nil + } + + N := priv.pub.N + p := priv.p + q := priv.q + + // FIPS 186-5, Section 5.1 requires "that p and q be of the same bit length." + if p.BitLen() != q.BitLen() { + priv.fipsApproved = false + } + + // Check that pq ≡ 1 mod N (and that p < N and q < N). + pN := bigmod.NewNat().ExpandFor(N) + if _, err := pN.SetBytes(p.Nat().Bytes(p), N); err != nil { + return errors.New("crypto/rsa: invalid prime") + } + qN := bigmod.NewNat().ExpandFor(N) + if _, err := qN.SetBytes(q.Nat().Bytes(q), N); err != nil { + return errors.New("crypto/rsa: invalid prime") + } + if pN.Mul(qN, N).IsZero() != 1 { + return errors.New("crypto/rsa: p * q != n") + } + + // Check that de ≡ 1 mod p-1, and de ≡ 1 mod q-1. + // + // This implies that e is coprime to each p-1 as e has a multiplicative + // inverse. Therefore e is coprime to lcm(p-1,q-1) = λ(N). + // It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1 mod p. Thus a^de ≡ a + // mod n for all a coprime to n, as required. + // + // This checks dP, dQ, and e. We don't check d because it is not actually + // used in the RSA private key operation. + pMinus1, err := bigmod.NewModulus(p.Nat().SubOne(p).Bytes(p)) + if err != nil { + return errors.New("crypto/rsa: invalid prime") + } + dP, err := bigmod.NewNat().SetBytes(priv.dP, pMinus1) + if err != nil { + return errors.New("crypto/rsa: invalid CRT exponent") + } + de := bigmod.NewNat() + de.SetUint(uint(priv.pub.E)).ExpandFor(pMinus1) + de.Mul(dP, pMinus1) + if de.IsOne() != 1 { + return errors.New("crypto/rsa: invalid CRT exponent") + } + + qMinus1, err := bigmod.NewModulus(q.Nat().SubOne(q).Bytes(q)) + if err != nil { + return errors.New("crypto/rsa: invalid prime") + } + dQ, err := bigmod.NewNat().SetBytes(priv.dQ, qMinus1) + if err != nil { + return errors.New("crypto/rsa: invalid CRT exponent") + } + de.SetUint(uint(priv.pub.E)).ExpandFor(qMinus1) + de.Mul(dQ, qMinus1) + if de.IsOne() != 1 { + return errors.New("crypto/rsa: invalid CRT exponent") + } + + // Check that qInv * q ≡ 1 mod p. + qP, err := bigmod.NewNat().SetOverflowingBytes(q.Nat().Bytes(q), p) + if err != nil { + // q >= 2^⌈log2(p)⌉ + qP = bigmod.NewNat().Mod(q.Nat(), p) + } + if qP.Mul(priv.qInv, p).IsOne() != 1 { + return errors.New("crypto/rsa: invalid CRT coefficient") + } + + // Check that |p - q| > 2^(nlen/2 - 100). + // + // If p and q are very close to each other, then N=pq can be trivially + // factored using Fermat's factorization method. Broken RSA implementations + // do generate such keys. See Hanno Böck, Fermat Factorization in the Wild, + // https://eprint.iacr.org/2023/026.pdf. + diff := bigmod.NewNat() + if qP, err := bigmod.NewNat().SetBytes(q.Nat().Bytes(q), p); err != nil { + // q > p + pQ, err := bigmod.NewNat().SetBytes(p.Nat().Bytes(p), q) + if err != nil { + return errors.New("crypto/rsa: p == q") + } + // diff = 0 - p mod q = q - p + diff.ExpandFor(q).Sub(pQ, q) + } else { + // p > q + // diff = 0 - q mod p = p - q + diff.ExpandFor(p).Sub(qP, p) + } + // A tiny bit of leakage is acceptable because it's not adaptive, an + // attacker only learns the magnitude of p - q. + if diff.BitLenVarTime() <= N.BitLen()/2-100 { + return errors.New("crypto/rsa: |p - q| too small") + } + + // Check that d > 2^(nlen/2). + // + // See section 3 of https://crypto.stanford.edu/~dabo/papers/RSA-survey.pdf + // for more details about attacks on small d values. + // + // Likewise, the leakage of the magnitude of d is not adaptive. + if priv.d.BitLenVarTime() <= N.BitLen()/2 { + return errors.New("crypto/rsa: d too small") + } + + // If the key is still in scope for FIPS mode, perform a Pairwise + // Consistency Test. + if priv.fipsApproved { + if err := fips140.PCT("RSA sign and verify PCT", func() error { + hash := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + sig, err := signPKCS1v15(priv, "SHA-256", hash) + if err != nil { + return err + } + return verifyPKCS1v15(priv.PublicKey(), "SHA-256", hash, sig) + }); err != nil { + return err + } + } + + return nil +} + +func checkPublicKey(pub *PublicKey) (fipsApproved bool, err error) { + fipsApproved = true + if pub.N == nil { + return false, errors.New("crypto/rsa: missing public modulus") + } + if pub.N.Nat().IsOdd() == 0 { + return false, errors.New("crypto/rsa: public modulus is even") + } + // FIPS 186-5, Section 5.1: "This standard specifies the use of a modulus + // whose bit length is an even integer and greater than or equal to 2048 + // bits." + if pub.N.BitLen() < 2048 { + fipsApproved = false + } + if pub.N.BitLen()%2 == 1 { + fipsApproved = false + } + if pub.E < 2 { + return false, errors.New("crypto/rsa: public exponent too small or negative") + } + // e needs to be coprime with p-1 and q-1, since it must be invertible + // modulo λ(pq). Since p and q are prime, this means e needs to be odd. + if pub.E&1 == 0 { + return false, errors.New("crypto/rsa: public exponent is even") + } + // FIPS 186-5, Section 5.5(e): "The exponent e shall be an odd, positive + // integer such that 2¹⁶ < e < 2²⁵⁶." + if pub.E <= 1<<16 { + fipsApproved = false + } + // We require pub.E to fit into a 32-bit integer so that we + // do not have different behavior depending on whether + // int is 32 or 64 bits. See also + // https://www.imperialviolet.org/2012/03/16/rsae.html. + if pub.E > 1<<31-1 { + return false, errors.New("crypto/rsa: public exponent too large") + } + return fipsApproved, nil +} + +// Encrypt performs the RSA public key operation. +func Encrypt(pub *PublicKey, plaintext []byte) ([]byte, error) { + fips140.RecordNonApproved() + if _, err := checkPublicKey(pub); err != nil { + return nil, err + } + return encrypt(pub, plaintext) +} + +func encrypt(pub *PublicKey, plaintext []byte) ([]byte, error) { + m, err := bigmod.NewNat().SetBytes(plaintext, pub.N) + if err != nil { + return nil, err + } + return bigmod.NewNat().ExpShortVarTime(m, uint(pub.E), pub.N).Bytes(pub.N), nil +} + +var ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA key size") +var ErrDecryption = errors.New("crypto/rsa: decryption error") +var ErrVerification = errors.New("crypto/rsa: verification error") + +const withCheck = true +const noCheck = false + +// DecryptWithoutCheck performs the RSA private key operation. +func DecryptWithoutCheck(priv *PrivateKey, ciphertext []byte) ([]byte, error) { + fips140.RecordNonApproved() + return decrypt(priv, ciphertext, noCheck) +} + +// DecryptWithCheck performs the RSA private key operation and checks the +// result to defend against errors in the CRT computation. +func DecryptWithCheck(priv *PrivateKey, ciphertext []byte) ([]byte, error) { + fips140.RecordNonApproved() + return decrypt(priv, ciphertext, withCheck) +} + +// decrypt performs an RSA decryption of ciphertext into out. If check is true, +// m^e is calculated and compared with ciphertext, in order to defend against +// errors in the CRT computation. +func decrypt(priv *PrivateKey, ciphertext []byte, check bool) ([]byte, error) { + if !priv.fipsApproved { + fips140.RecordNonApproved() + } + + var m *bigmod.Nat + N, E := priv.pub.N, priv.pub.E + + c, err := bigmod.NewNat().SetBytes(ciphertext, N) + if err != nil { + return nil, ErrDecryption + } + + if priv.dP == nil { + // Legacy codepath for deprecated multi-prime keys. + fips140.RecordNonApproved() + m = bigmod.NewNat().Exp(c, priv.d.Bytes(N), N) + + } else { + P, Q := priv.p, priv.q + t0 := bigmod.NewNat() + // m = c ^ Dp mod p + m = bigmod.NewNat().Exp(t0.Mod(c, P), priv.dP, P) + // m2 = c ^ Dq mod q + m2 := bigmod.NewNat().Exp(t0.Mod(c, Q), priv.dQ, Q) + // m = m - m2 mod p + m.Sub(t0.Mod(m2, P), P) + // m = m * Qinv mod p + m.Mul(priv.qInv, P) + // m = m * q mod N + m.ExpandFor(N).Mul(t0.Mod(Q.Nat(), N), N) + // m = m + m2 mod N + m.Add(m2.ExpandFor(N), N) + } + + if check { + c1 := bigmod.NewNat().ExpShortVarTime(m, uint(E), N) + if c1.Equal(c) != 1 { + return nil, ErrDecryption + } + } + + return m.Bytes(N), nil +} diff --git a/crypto/internal/fips140/rsa/testdata/gcd_lcm_tests.txt b/crypto/internal/fips140/rsa/testdata/gcd_lcm_tests.txt new file mode 100644 index 00000000000..b5a0c17ed2a --- /dev/null +++ b/crypto/internal/fips140/rsa/testdata/gcd_lcm_tests.txt @@ -0,0 +1,279 @@ +# GCD tests. +# +# These test vectors satisfy gcd(A, B) = GCD and lcm(A, B) = LCM. + +GCD = 0 +A = 0 +B = 0 +# Just to appease the syntax-checker. +LCM = 0 + +GCD = 1 +A = 92ff140ac8a659b31dd904161f9213706a08a817ae845e522c3af0c9096699e059b47c8c2f16434b1c5766ebb384b79190f2b2a62c2378f45e116890e7bb407a +B = 2f532c9e5902b0d68cd2ed69b2083bc226e8b04c549212c425a5287bb171c6a47fcb926c70cc0d34b8d6201c617aee66af865d31fdc8a2eeb986c19da8bb0897 +LCM = 1b2c97003e520b0bdd59d8c35a180b4aa36bce14211590435b990ad8f4c034ce3c77899581cb4ee1a022874203459b6d53859ab1d99ff755efa253fc0e5d8487bb000c13c566e8937f0fe90b95b68bc278610d4f232770b08d1f31bee55a03da47f2d0ebb9e7861c4f16cc22168b68593e9efcde00f54104b4c3e1a0b294d7f6 + +GCD = a +A = faaffa431343074f5c5d6f5788500d7bc68b86eb37edf166f699b4d75b76dae2cb7c8f6eccae8f18f6d510ef72f0b9633d5740c0bebb934d3be796bd9a53808e +B = 2f48ec5aa5511283c2935b15725d30f62244185573203b48c7eb135b2e6db5c115c9446ac78b020574665b06a75eb287e0dbeb5da7c193294699b4c2129d2ac4 +LCM = 4a15f305e9622aa19bd8f39e968bfc16d527a47f7a5219d7b02c242c77ef8b608a4a6141f643ca97cedf07c0f1f3e8879d2568b056718aa15c0756899a08ccbe0a658bae67face96fa110edb91757bfa4828e8ff7c5d71b204f36238b12dd26f17be8ba9771f7068d63e41d423671f898f054b1187605754bc5546f2b02c5ac + +GCD = 16 +A = cf0b21bde98b41b479ac8071086687a6707e9efaacd4e5299668ce1be8b13290f27fd32ae68df87c292e8583a09d73ec8e8a04a65a487380dcd7dacca3b6e692 +B = 3be3f563f81d5ad5c1211db7eff430aa345e830ce07b4bde7d4d32dba3ac618d2034351e5435fd6c7f077971fb4a1e83a7396a74fdff7fce1267112851db2582 +LCM = 233a2188de2c017235024b182286f17562b2ee5ab9fdfe4efa2f61c4ff99fa44e1ead5bf6cde05bd7502ce78373c83e3f9dbab0c9bb8620a87c2640bce5d12c685af656df789bb3d0ba1edbaa98cf4f0166d422ab17aa6706f8132264d45b72827d6671a00a9186e723379e3a3bb7902d08865f357c74100059f83800241976 + +GCD = 1 +A = dd7b7597d7c1eb399b1cea9b3042c14bd6022d31b1d2642a8f82fc32de6eadaf012fbbf349eaec4922a8468740ca73c6090833d6a69a380ed947b39c2f9b0b76 +B = 8e0dc8654e70eec55496038a8d3fff3c2086bc6dbfc0e2dbdf5bd7de03c5aef01a3982556ac3fc34fd5f13368be6cdc252c82367b7462e210f940f847d382dd9 +LCM = 7ae667df4bd4dd35bbec28719a9f1b5e1f396a9ab386c086742a6ab3014a3386d39f35b50624d0c5b4e6b206c2635c7de5ea69e2faa85dd616a7e36622962a07632839857aa49332942feccff2aee1c962e2f4e8ccfd738a5da5bf528b4c5a2440409350f5a17a39d234403e8482ccf838e0d2758ccfb8018198a51dbb407506 + +GCD = 1 +A = 0 +B = 1 +LCM = 0 + +GCD = 1 +A = 1 +B = 0 +LCM = 0 + +GCD = 1 +A = 1 +B = 1 +LCM = 1 + +GCD = 2b2 +A = dfccaa3549c1b59ab3e114fe87dc5d187719abad58c51724e972741eb895ab79a49f385f61d531ec5c88dbb505ae375093fa848165f71a5ed65e7832a42ade191a +B = fa58a81f43088da45e659fc1117d0f1cd015aa096c8e5377cf1832191baf7cc28b5c24998b93b64f8900a0973faedb9babaaf1854345f011739da8f1175d9684c +LCM = 5132f7ab7a982b9dc55114bd96800b7637f9742cf8a7a00a0d69d5e4574fc85792c89a1c52bcfc74b9d7f3f6164819466c46b2d622e280ced7ad1211604084a15dc1fd1951a05c8ce37122c0ec15891d818a70d3763670ea3195098de9b1ca50ea89893a9753fb9ea801541058f44801f7f50967124abfc864a2b01c41f94193c + +GCD = 8e +A = 248d96a8a4cab0a1b194e08c1146868b094597cadbc35531f0ed2d77cba9f15cb5cc7c10e64ce054bf93396d25259d750b3de3aba65073db1fd2b852a6454ac1a +B = 4c7bad8e1844901fd6a2ce2edc82e698d28ec95d6672ca148d85b49ecc78dd0a8b870e202244210bc98592b99ff6abbd20630f9eee7d46b15ccfae8d08b86799de +LCM = 13b01f9d9c6c13e90c97e3d95bbce5a835c631b3de3bd4ff5df13ad850f5223dbdf71c53912275d0397df9335ef3a3ba8e4684c6b25962bb7b18bc74144cb5edf0196f79863a7ff032619a71646a92281f7baace7f223d254cb4d05ec19bf8d4c8ce4455a9d770daec89c0d3cf338cbdae39cf982b3c4568f5c9def4e1133d28a + +GCD = 3e55 +A = 2fa97382f46676b7a4cc2b8153f17b58792d24660e187d33ce55c81cc193ccb6e1e2b89feea1d5fd8faa36e13bf947fb48635e450a4d1488d0978324194a1f43c6 +B = ab08ad074139963bc18e5d87ba68db64ca6f4c279616c64039b02c55f2375b3bc04114e8e05e1ba92fb6470768f61d123845aea36774c18612736a220934561faf +LCM = 82c7c377ecda2cb9228604cd287df5eff94edd4a539c3eb3b3fdd4b4a79d2f4eaf2b22f8286272d3dad2e370cfcd9ea4d93ebb3f049c52b8fa23b68a5bf79af989822e2cfb978f68c6a5058f47319dffcb455b089b06ae6db9e5c8a2b6e951d6e118bd2b4cd08b6e5733476a446a57387d940d1289ec00e24315821ed3a5daf2 + +GCD = a7a +A = 923706dfed67834a1e7e6c8e8e9f93bfbc0b43ca1f324886cf1f1380fb9b77109275d4b50af1b7689802fe9b3623ac46c7ba0e17e908c20278127b07a5c12d86ec +B = 64473e878a29021fac1c1ce34a63eae1f4f83ee6851333b67213278b9a4a16f005cba0e8cdb410035bb580062f0e486c1a3a01f4a4edf782495f1dc3ebfa837d86 +LCM = 57785ca45b8873032f1709331436995525eed815c55140582ce57fd852116835deac7ca9d95ce9f280e246ea4d4f1b7140ab7e0dd6dc869de87f1b27372098b155ad0a1828fd387dff514acc92eae708609285edaab900583a786caf95153f71e6e6092c8c5ee727346567e6f58d60a5e01c2fa8ebcf86da9ea46876ecc58e914 + +GCD = 42 +A = 0 +B = 42 +LCM = 0 + +GCD = 42 +A = 42 +B = 0 +LCM = 0 + +GCD = 42 +A = 42 +B = 42 +LCM = 42 + +GCD = f60d +A = ef7886c3391407529d5cf2e75ed53e5c3f74439ad2e2dc48a79bc1a5322789b4ced2914b97f8ff4b9910d212243b54001eb8b375365b9a87bd022dd3772c78a9fd63 +B = d1d3ec32fa3103911830d4ec9f629c5f75af7039e307e05bc2977d01446cd2cbeeb8a8435b2170cf4d9197d83948c7b8999d901fe47d3ce7e4d30dc1b2de8af0c6e4 +LCM = cc376ed2dc362c38a45a719b2ed48201dab3e5506e3f1314e57af229dc7f3a6a0dad3d21cfb148c23a0bbb0092d667051aa0b35cff5b5cc61a7c52dec4ed72f6783edf181b3bf0500b79f87bb95abc66e4055f259791e4e5eb897d82de0e128ecf8a091119475351d65b7f320272db190898a02d33f45f03e27c36cb1c45208037dc + +GCD = 9370 +A = 1ee02fb1c02100d1937f9749f628c65384ff822e638fdb0f42e27b10ee36e380564d6e861fcad0518f4da0f8636c1b9f5124c0bc2beb3ca891004a14cd7b118ddfe0 +B = 67432fd1482d19c4a1c2a4997eab5dbf9c5421977d1de60b739af94c41a5ad384cd339ebfaa43e5ad6441d5b9aaed5a9f7485025f4b4d5014e1e406d5bd838a44e50 +LCM = 159ff177bdb0ffbd09e2aa7d86de266c5de910c12a48cbe61f6fa446f63a2151194777555cd59903d24cb30965973571fb1f89c26f2b760526f73ded7ee8a34ebcecd1a3374a7559bcdb9ac6e78be17a62b830d6bb3982afdf10cf83d61fd0d588eab17d6abef8e6a7a5763fcb766d9a4d86adf5bb904f2dd6b528b9faec603987a0 + +GCD = c5f +A = 5a3a2088b5c759420ed0fb9c4c7685da3725b659c132a710ef01e79435e63d009d2931ea0a9ed9432f3d6b8851730c323efb9db686486614332c6e6ba54d597cf98 +B = 1b1eb33b006a98178bb35bbcf09c5bebd92d9ace79fa34c1567efa8d6cf6361547807cd3f8e7b8cd3ddb6209dccbae4b4c16c8c1ec19741a3a57f61571882b7aed7 +LCM = c5cbbbe9532d30d2a7dd7c1c8a6e69fd4fa4828a844d6afb44f3747fef584f7f1f3b835b006f8747d84f7699e88f6267b634e7aef78d6c7584829537d79514eec7d11219721f91015f5cefdc296261d85dba388729438991a8027de4827cd9eb575622e2912b28c9ce26d441e97880d18db025812cef5de01adeaec1322a9c9858 + +GCD = e052 +A = 67429f79b2ec3847cfc7e662880ab1d94acdf04284260fcfffd67c2862d59704ed45bcc53700c88a5eea023bc09029e9fd114fc94c227fd47a1faa1a5ef117b09bd2 +B = 39faa7cbdeb78f9028c1d50ab34fbe6924c83a1262596f6b85865d4e19cc258b3c3af1ee2898e39e5bee5839e92eac6753bbbb0253bd576d1839a59748b778846a86 +LCM = 1ab071fb733ef142e94def10b26d69982128561669e58b20b80d39cf7c2759d26b4a65d73b7f940c6e8fc417180ef62d7e52ac24678137bd927cd8d004ad52b02affe176a1ecde903dbc26dcc705678f76dd8cd874c0c3fe737474309767507bbe70dd7fb671bbb3694cedf0dcdaa0c716250ddd6dfec525261572fa3e1387f7b906 + +GCD = 3523 +A = 0 +B = 3523 +LCM = 0 + +GCD = 3523 +A = 3523 +B = 0 +LCM = 0 + +GCD = 3523 +A = 3523 +B = 3523 +LCM = 3523 + +GCD = f035a941 +A = 16cd5745464dfc426726359312398f3c4486ed8aaeea6386a67598b10f744f336c89cdafcb18e643d55c3a62f4ab2c658a0d19ea3967ea1af3aee22e11f12c6df6e886f7 +B = 74df09f309541d26b4b39e0c01152b8ad05ad2dfe9dd2b6706240e9d9f0c530bfb9e4b1cad3d4a94342aab309e66dd42d9df01b47a45173b507e41826f24eb1e8bcc4459 +LCM = b181771d0e9d6b36fdfcbf01d349c7de6b7e305e1485ea2aa32938aa919a3eee9811e1c3c649068a7572f5d251b424308da31400d81ac4078463f9f71d7efd2e681f92b13a6ab3ca5c9063032dcbdf3d3a9940ce65e54786463bbc06544e1280f25bc7579d264f6f1590cf09d1badbf542ce435a14ab04d25d88ddbac7d22e8cae1c91f + +GCD = 33ad1b8f +A = 1af010429a74e1b612c2fc4d7127436f2a5dafda99015ad15385783bd3af8d81798a57d85038bcf09a2a9e99df713b4d6fc1e3926910fbbf1f006133cb27dc5ebb9cca85 +B = 92a4f45a90965a4ef454f1cdd883d20f0f3be34d43588b5914677c39d577a052d1b25a522be1a656860a540970f99cbc8a3adf3e2139770f664b4b7b9379e13daf7d26c +LCM = 4c715520ed920718c3b2f62821bc75e3ff9fd184f76c60faf2906ef68d28cd540d3d6c071fa8704edd519709c3b09dfaee12cb02ab01ad0f3af4f5923d5705ce6d18bcab705a97e21896bb5dd8acb36ee8ec98c254a4ddc744297827a33c241f09016a5f109248c83dd41e4cea73ce3eabb28d76678b7e15545b96d22da83c111b6b624 + +GCD = dc0429aa +A = ccb423cfb78d7150201a97114b6644e8e0bbbb33cadb0ef5da5d3c521a244ec96e6d1538c64c10c85b2089bdd702d74c505adce9235aa4195068c9077217c0d431de7f96 +B = 710786f3d9022fc3acbf47ac901f62debcfda684a39234644bac630ab2d211111df71c0844b02c969fc5b4c5a15b785c96efd1e403514235dc9356f7faf75a0888de5e5a +LCM = 6929af911850c55450e2f2c4c9a72adf284fe271cf26e41c66e1a2ee19e30d928ae824f13d4e2a6d7bb12d10411573e04011725d3b6089c28d87738749107d990162b485805f5eedc8f788345bcbb5963641f73c303b2d92f80529902d3c2d7899623958499c8a9133aae49a616c96a2c5482a37947f23af18c3247203ac2d0e760340e6 + +GCD = 743166058 +A = 16cd476e8031d4624716238a3f85badd97f274cdfd9d53e0bd74de2a6c46d1827cc83057f3889588b6b7ca0640e7d743ed4a6eaf6f9b8df130011ecc72f56ef0af79680 +B = 86eba1fc8d761f22e0f596a03fcb6fe53ad15a03f5b4e37999f60b20966f78ba3280f02d3853f9ace40438ccfaf8faed7ace2f2bf089b2cdd4713f3f293bf602666c39f8 +LCM = 1a7a1b38727324d6ba0290f259b8e2b89c339b2445cada38a5a00ded1468ab069f40678ce76f7f78c7c6f97783cc8a49ef7e2a0c73abbac3abc66d1ce99566ce7f874a8949ca3442051e71967695dc65361184748c1908e1b587dc02ed899a524b34eb30b6f8db302432cfa1a8fbf2c46591e0ab3db7fd32c01b1f86c39832ee9f0c80 + +GCD = 6612ba2c +A = 0 +B = 6612ba2c +LCM = 0 + +GCD = 6612ba2c +A = 6612ba2c +B = 0 +LCM = 0 + +GCD = 6612ba2c +A = 6612ba2c +B = 6612ba2c +LCM = 6612ba2c + +GCD = 2272525aa08ccb20 +A = 11b9e23001e7446f6483fc9977140d91c3d82568dabb1f043a5620544fc3dda233b51009274cdb004fdff3f5c4267d34181d543d913553b6bdb11ce2a9392365fec8f9a3797e1200 +B = 11295529342bfb795f0611d03afb873c70bd16322b2cf9483f357f723b5b19f796a6206cf3ae3982daaeafcd9a68f0ce3355a7eba3fe4e743683709a2dd4b2ff46158bd99ff4d5a0 +LCM = 8d4cbf00d02f6adbaa70484bcd42ea932000843dcb667c69b75142426255f79b6c3b6bf22572597100c06c3277e40bf60c14c1f4a6822d86167812038cf1eefec2b0b19981ad99ad3125ff4a455a4a8344cbc609e1b3a173533db432bd717c72be25e05ed488d3970e7ed17a46353c5e0d91c8428d2fec7a93210759589df042cab028f545e3a00 + +GCD = 3480bf145713d56f9 +A = 8cf8ef1d4f216c6bcec673208fd93b7561b0eb8303af57113edc5c6ff4e1eeae9ddc3112b943d947653ba2179b7f63505465126d88ad0a0a15b682f5c89aa4a2a51c768cd9fdeaa9 +B = a6fd114023e7d79017c552a9051ca827f3ffa9f31e2ee9d78f8408967064fcdc9466e95cc8fac9a4fa88248987caf7cf57af58400d27abd60d9b79d2fe03fad76b879eceb504d7f +LCM = 1c05eee73a4f0db210a9007f94a5af88c1cdd2cba456061fd41de1e746d836fa4e0e972812842e0f44f10a61505f5d55760c48ba0d06af78bb6bde7da8b0080b29f82b1161e9c0b5458e05ac090b00f4d78b1cc10cf065124ba610e3acab092a36fe408525e21c0ddc7c9696ed4e48bd2f70423deecfe62cecc865c6088f265da0e5961d3f3a84f + +GCD = 917e74ae941fcaae +A = 652f8a92d96cbf0a309629011d0fbaceb1266bc2e8243d9e494eead4cf7100c661b537a8bea93dec88cfc68597d88a976c125c3b4de19aba38d4ea9578202e59848d42652518348a +B = 32e07b71979d57e8344e97c39680a61e07d692d824ae26b682156890792d8a766ee29a4968f461aaced5bf049044fba2f4120b1c1f05985676f975d4582e9e82750d73c532cd07b2 +LCM = 23620c7b897dc26c7717e32f3517ac70bf09fbe08f7255ab010cf4cf946f4e96304c425043452c5d5a0e841d3a3cfd9c2d84d9256f3b5974fe3ebfa9255fe20a710d3e6511606c0d85970381101c7f4986d65ad6a73a71507f146b11f903043cfa805cc0b14d4f3072da98bf22282f7762040406c02d5b3ef9e7587f63bab8b29c61d8e30911aa96 + +GCD = 2b9adc82005b2697 +A = 19764a84f46045ef1bca571d3cbf49b4545998e64d2e564cc343a53bc7a0bcfbe0baa5383f2b346e224eb9ce1137d9a4f79e8e19f946a493ff08c9b423574d56cbe053155177c37 +B = 1bbd489ad2ab825885cdac571a95ab4924e7446ce06c0f77cf29666a1e20ed5d9bc65e4102e11131d824acad1592075e13024e11f12f8210d86ab52aa60deb250b3930aabd960e5a +LCM = 1032a0c5fffc0425e6478185db0e5985c645dd929c7ebfeb5c1ee12ee3d7b842cfab8c9aa7ff3131ac41d4988fb928c0073103cea6bb2cc39808f1b0ad79a6d080eac5a0fc6e3853d43f903729549e03dba0a4405500e0096b9c8e00510c1852982baec441ed94efb80a78ed28ed526d055ad34751b831b8749b7c19728bf229357cc5e17eb8e1a + +GCD = 8d9d4f30773c4edf +A = 0 +B = 8d9d4f30773c4edf +LCM = 0 + +GCD = 8d9d4f30773c4edf +A = 8d9d4f30773c4edf +B = 0 +LCM = 0 + +GCD = 8d9d4f30773c4edf +A = 8d9d4f30773c4edf +B = 8d9d4f30773c4edf +LCM = 8d9d4f30773c4edf + +GCD = 6ebd8eafb9a957a6c3d3d5016be604f9624b0debf04d19cdabccf3612bbd59e00 +A = 34dc66a0ffd5b8b5e0ffc858dfc4655753e59247c4f82a4d2543b1f7bb7be0e24d2bbf27bb0b2b7e56ee22b29bbde7baf0d7bfb96331e27ba029de9ffdff7bdb7dc4da836d0e58a0829367ec84ea256833fd4fe1456ad4dd920557a345e12000 +B = 1f3406a20e20ebf96ccb765f898889a19b7636608fd7dc7c212607b641399543f71111d60e42989de01eaa6ff19a86ea8fbde1a3d368c0d86dc899e8e250fc764090f337958ca493119cbb4ad70cbfae7097d06d4f90ec62fbdd3f0a4496e600 +LCM = ee502c50e3667946e9089d0a9a0382e7fd0b75a17db23b56a0eec997a112c4dbd56d188808f76fe90451e5605550c9559ef14a95014c6eb97e9c1c659b98515c41470142843de60f72fb4c235faa55b0a97d943221003d44e2c28928f0b84bf071256254897ed31a7fd8d174fc962bc1311f67900ac3abcad83a28e259812f1ee229511ab1d82d41f5add34693ba7519babd52eb4ec9de31581f5f2e40a000 + +GCD = ef7399b217fc6a62b90461e58a44b22e5280d480b148ec4e3b4d106583f8e428 +A = 7025e2fe5f00aec73d90f5ad80d99ca873f71997d58e59937423a5e6ddeb5e1925ed2fd2c36a5a9fc560c9023d6332c5d8a4b333d3315ed419d60b2f98ccf28bbf5bf539284fd070d2690aeaac747a3d6384ee6450903a64c3017de33c969c98 +B = df0ac41dbabce1deeb0bceb1b65b1079850052ecf6534d0cff84a5a7fb5e63baee028d240f4419925154b96eaa69e8fbb1aae5102db7916234f290aa60c5d7e69406f02aeea9fe9384afbff7d878c9ac87cd31f7c35dff243b1441e09baff478 +LCM = 687669343f5208a6b2bb2e2efcac41ec467a438fde288cc5ef7157d130139ba65db9eb53e86a30c870bd769c0e0ab15a50f656cd9626621ae68d85eaff491b98da3ea5812062e4145af11ea5e1da457084911961ef2cd2ac45715f885ba94b4082aa76ffd1f32461f47c845b229d350bf36514c5ce3a7c782418746be342eca2721346ade73a59475f178c4f2448e1326110f5d26a0fef1a7a0c9288489e4dc8 + +GCD = 84b917557acf24dff70cb282a07fc52548b6fbbe96ca8c46d0397c8e44d30573 +A = 81dbb771713342b33912b03f08649fb2506874b96125a1ac712bc94bfd09b679db7327a824f0a5837046f58af3a8365c89e06ff4d48784f60086a99816e0065a5f6f0f49066b0ff4c972a6b837b63373ca4bb04dcc21e5effb6dfe38271cb0fa +B = 1da91553c0a2217442f1c502a437bb14d8c385aa595db47b23a97b53927b4493dd19f1bc8baf145bc10052394243089a7b88d19b6f106e64a5ab34acad94538ab504d1c8ebf22ac42048bbd1d4b0294a2e12c09fe2a3bd92756ba7578cb34b39 +LCM = 1d0530f8142754d1ee0249b0c3968d0ae7570e37dadbe4824ab966d655abf04cd6de5eb700eba89d8352dec3ae51f2a10267c32fbd39b788c7c5047fe69da3d7ad505435a6212f44899ba7e983bb780f62bcdee6f94b7dba8af7070a4cc008f351ae8be4579bc4a2e5c659ce000ad9c8cdc83723b32c96aeb0f5f4127f6347353d05525f559a8543cd389ad0af6f9d08a75b8c0b32419c097e6efe8746aee92e + +GCD = 66091477ea3b37f115038095814605896e845b20259a772f09405a8818f644aa +A = cedac27069a68edfd49bd5a859173c8e318ba8be65673d9d2ba13c717568754ed9cbc10bb6c32da3b7238cff8c1352d6325668fd21b4e82620c2e75ee0c4b1aff6fb1e9b948bbdb1af83cecdf356299b50543b72f801b6a58444b176e4369e0 +B = 5f64ca1ba481f42c4c9cf1ffa0e515b52aa9d69ceb97c4a2897f2e9fa87f72bae56ee6c5227f354304994c6a5cc742d9f09b2c058521975f69ca5835bce898cf22b28457cd7e28870df14e663bb46c9be8f6662f4ff34d5c4ae17a888eba504e +LCM = c163cb28642e19a40aa77887c63180c2c49fc10cda98f6f929c8131752ea30b5283a814a81681b69b9d1762e6c1a9db85f480bc17f998d235fd7e64c1caa70ef170c9e816d3e80f516b29f2c80cfb68bf208b4d5082ef078da4314b3f20c7d6c54b0aeb378096b029a7b61c0a4cd14aeddc01004c53915a4f692d2291752e5af46b23d7fa6dd61f2d56c6f4bf8e6119688abac8fd7aba80e846a7764bb3fca0 + +GCD = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +A = 0 +B = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +LCM = 0 + +GCD = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +A = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +B = 0 +LCM = 0 + +GCD = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +A = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +B = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 +LCM = bb80bf51757ba696c700fa4e4c0132b3151d2bf9ebff8382f808ded78be67182 + +GCD = 120451d8307219aa0c96f328ad653ccd462e92423ca93ed8a3dde45bf5cb9b13cdaf9800e4d05dd71c4db6a129fb3280ee4ec96ec5297d881c1a8b5efccbd91fef21f5c5bf5fba42a4c8eaa358f620a074b7a17054527bdaa58d5acaa0dfdc48ecba1a10ebf4d57bb4215de406e6be13fed3fe493b1cd1e2d11a8d4ac03c47756 +A = 3f8179a8e1f0b342475a855c3e1bae402dd41424cf24a0b4d2e263c8efb08bde7d92eae8607fb5e88b1378f0f1bd0733f229a35be6b1383a48d32749d5d6b32427d26323b7ab05bb5781289e96bfbc21971439319b15f6c0fe93fdb35d0b67ec41443c59a081dd3cef047ac797fccb45bece84c0bb0bb7e1797259526d8ec9cc63ba4d32cfc692ccd3d243cb2b53ac216312f3a8e8c0daa09d21b6150d697639a5e52059414a417c607be8ec0eee2e708219cadbaf37a369c4485b01ed87bbc2 +B = 2c474e396a2dd9cd10b9d7313f69d3b4ca123e9fd853edd488339236d14c56453a1381958864a04d2624e81995dabcdd0ccf60db9917813f887de68da075d0ea4440001e18f470e43b38ee3440b49be651d709fbdef980e3e4149913f4ae2681124f54523f4881376ddb533b5219e804cc26f4c2e577be4e02613c4da80ba1215775b0a5178a965ad47bd2befb32493943ded1004ef66347b4983f8d1ba990d4a943505dfce6debcfb322842ed88106cd6dee9aa592ff0d2274bc727a6e1f14c +LCM = 9c129cf649555bfd2d3d9c64dc6d6f022295e53bca5d2f218adaa66aa60eb4694429b7e83bf81b6df4459c5104023ab9a33f006ffcd8114507baa17e2ef6fe23ebdd4740f66879033da2041f2cb7ba517ad3526ffe75614ea9432c085f71b2d65a736bac7ba42b639e330b82733372083843dcb78b6a273ab20e0d4b7c8998a14048aa15bb20a0a0bd997917107274c89b4cec175fb98043d52e6c555bd9e0036566d052a6d4e7e276d1e8835e1f06e3ca46d47747ba586e95fb1a790d992834b7c3e136141eb8a434e6c12067246ac3c0a81c69e03b1ed28aa0b3173d6eff83d278c2f461a47a416f3f9a5dae3bb410fd18817bd4115e7f1e84b936cc02364 + +GCD = 95aa569a2c76854300d7660847dd20fe0b8c445fdbcaa98465cee61aee76ad6a438e75a8c573198570ffb62bc07ec3a2be0ae0a1f631670fa88d6f75f3161e8b9a4d44b6801ffc884c7f469c5ed1f27b1edecce9f2977f9e92d1a3b230492fea7e6f2af739dc158a7fbd29856cbedb57b4119e64b27ab09eb1c2df01507d6e7fd +A = 4c653b5bfec44e9be100c064dffe5d8cd59b0cf4cc56b03eabb4ef87cfda6506c9a756b811907fe9d8b783eb7a0b9e129773bf1da365ddb488d27b16fb983e89345d1ccdb4f06a67a11925c3f266373be5d7b0075189c6f3c2157e2da197058fe0a7bcc50adc34e99e254a29abbe2d5948d3157e1b0c3fca3d641760f7b9862843b63abef0b3d83fd486f4526b30382fda355575da30e9a106718a3921774c4d69f5311f8d737fe618f5236b4763fe1b2ee7f13184db67367d3903c535ff6d7b +B = 2dcca83c99a28e9fd2f84e78973699baf2f04fd454094730948b22477834a0064817b86e0835e6d7b26e5b0b1dcf4ad91a07ac0780d6522df1fcac758cf5db6c2a5623d7c0f1afefd5718f7b6de639867d07a9ec525991304e9355d1635104bea837f74758d6aa2aab4e4afbb606af1d98de7417505e4710cd0589bdff9a0bf38a857cc59a5f1781043e694fc2337fd84bdeb28b13a222bb09328a81ec409ad586e74236393d27398cc24d412135e34247c589149e134b97f4bd538ac9a3424b +LCM = 1760c0b0066aa0695767099e87e9388729ea89b8e8c36bddcd04d257591e741613c07b0e69447c0a468c33a745084171e06523d987d8db40a1433bf435325e8a724a0876503b34495170ff3671d42117a2e4f3a75b1d9dd809a34fa0fb26fe50d84f80a9b02e40190e5efb927a5a61a03f13edbce2e666af6c3a2a9bcb84e47e3090008753ff27c4b8cf06480f471379a93f5230923623a83b286b71a555cd5e5347282f664ed90b14b2c4de84a70375e488211a7b3931119ef3bbe029b712389fe784818a0bf29d80733ce9cc940c547aa1eb3f06d492eb676bf37802283c82ce76156dfaab5c2d5107e08062681b5fa169f6eb68e1ab8bd9b2005e90bd4fd + +GCD = 244b9b1290cf5b4ba2f810574c050651489f2d3a2b03e702b76ebfaf4e33de9bbe5da24c919e68d3a72eadd35982b3a89c6b18b38ff7082ac65263e52b6ec75a5717b971c98257b194c828bff0216a99536603b41a396ea2fb50f5ea7cf3edf10bb0d039123e78593ae9ffcbbba02e51e038533e83b6bc73c70551d6467f39809 +A = 41a0b1310669500681cdf888836f6c556758750f562d743ac780dd4c0d161856380e44fdbb1f8a2786bf45be6b0e7f1cb2cd85f6b9e50acc72793d92383c7d7fb796fc74d32e8fac8225bdc19ae47546d9c9c75f5f06ca684f07daccaf89ccf2cddeb7ec255d530c7dd1e71daf44cafdc9d30fbcb1cbaefae3480585f79f4177e3834a5bc91845e2e8cd8aeb27f484e5e5b2c3c076dbb6c23e91303f0a0fdde83cd33a8ea6ed1549e727b4d766c1017c169710fd98e1585d60f66e121f9180b3 +B = 251f5aeaa60b3959285f49540cdaf8e21451110bbddb9933bbbcaea3112f4eb45e435a3ba37c52d2ab79ce997a8f6c829b3aa561f2852924b8effb52396d09d2bf257ebb4fb56c7aa25648f69b06d2cd01e876c9f9c0679de9e6fffa79eb7e603723e5af7de46ee405a5a079229577b5b6fffb8d43e391fe6f4eb89638e64d6eff8026249aaa355a91625eb0bfd14caa81e4c3586aaa2e94fde143a44f223a91e226661d12f55dfcdb4215e5a64e14e968005733be6a71c465de312ca109b34a +LCM = 431f918b274f3e43f446e4e85567883d6536a0332db662cef088f5a36b0f4b68372048174ba10fee94b9f8f1c2e189c974be2e6e8ae8e2ae108445326d40f63e38d8d4e2e46174589a3cbc9583e0036dc8146e79eee9e96f4436313b3f143dd0f5aceab05243def7f915169c360f55ef123977cf623c5ba432c3259c62fb5e37d5adab0f24b825aa4ada99ec4e83e9ca4698399e1ed633091ce5f9844c540a642cd264201116ed4168aa2105a5159f5df064f845830c469140f766c7319052ce59bd1ad7c3f2d8c30e54f147f6aeb5586c70c984302ba18d854a60aec01b394c7d66fa33fe18fe4a8cfb3238df219294e6e42190a30d28b10049a1b75853a4e + +GCD = 206695d52bc391a4db61bf8cb6ea96188333a9c78f477ee76976c2346dad682cf56ca6f176d86ef67d41ff5921b6162b0eca52359975872430dd14c45643eacdf028d830770714c033fd150669705851b2f02de932322d271d565d26768530c3f6cb84f0b3356f970b9070b26c050ead0417152c324c8ffe266d4e8b5b7bef3a +A = 1114eb9f1a9d5947eb1399e57f5c980833489685023ed2fe537fe1276c1e026b9a19e6fff55aa889d6c4e977b6e6f3111e2ad463138637b50f42cf32e57d83f282de9e72f813e5969195159a666d74dcd689bd527c60199ae327f7bd548ac36868fea5fdf6f35d19b921e7c10b6448ca480de6826478cd0642d72f05af3f8e65ce42409fbd49f56e81946e89c8e83962c4edc0ed54600600a305e52d081aed3c351e450e11f8fb0ce5754c92cf765b71393b2b7a89c95df79b9ea1b3cb600862 +B = 1d8f3179ca7b5cc7119360c10de939ffa57c9043da2f2b0ca3009c9bdad9f19ed16e3c2c197bef4b527fa1bf2bbab98b77e26c329911db68bd63d3d0fbfc727a977395b9ad067106de3094d68e097830858c5ccfa505fc25e972bdee6f347e7d1163efacd3d29a791ec2a94ffeed467884ae04896efc5e7e5f43d8d76c147e3c9951a1999173bc4e5767d51268b92cc68487ba1295372143b538711e0a62bf0ac111cc750ca4dd6c318c9cbe106d7fc492261404b86a1ba728e2d25b1976dc42 +LCM = f9570211f694141bfb096560551080cbe02a80271b4505591aaea9e3b99ea1d5ac1c1f2378fd72799e117ac2a73381b1ad26314e39972164d93971479ee3ba21a4d98cef0bd299d540ce5826995dcee0de420dff73d30b23cbf3188c625c7696df517535bc5675d71faa00807efbebdca547933f4a37849d1c014484a77da6df0670c4974bcc91eb5f5fe5faf9dd095ef195ec32ad9eeebf0e63288b4032ed9e70b888afc642f4ff96f0b4c0a68787301c12e4527fe79bdfe72dd3844ab5e094a9295df6616f24d1b9eeebc2116177dacf91969dda73667bc421ef3ccd8d5c23dddc283f5d36568d31f2654926be67f78e181075bdc148f2b39c630b141ae8a + +GCD = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +A = 0 +B = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +LCM = 0 + +GCD = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +A = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +B = 0 +LCM = 0 + +GCD = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +A = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +B = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 +LCM = 3d319c42d872f21131ce5ff3ab8bec94339308e620316dda218e85fedcd511cd62f0b2f3448d5e58fd3520ae8118abd54ead9ad9e8ec3890365c6b2cca2172d4b8839b2d2c5ab02f65180826cb0cd5c9798f5d6261efe6e6ec31dea047da7c486b0590359e6f333557f67ceebf9ea9cd5dd986a999a8c88bdbd0ca21816b2423 + +GCD = 2 +A = 14e95a85e59ade9ef39e2f400c65db18702fa5fc485b9bba479a5282b2206129160e54f73ef4917983c17b4c5ebff7be112a886de069706eee29ba902515cb038 +B = ddcfff1d39c90c599f55495bf71c1e7597c6b08b7430707f360c6a6e5137bbc7b403c6d9e2c34f3d2f29d5d32b869346853c2de239cc35381bdfb4a01569211a +LCM = 90f38564ee72e55d362c04599e7d74f068c75f541b84e97abba2841f1a9f66b06b5c9009f6a4c2e319fced85270588de03ccebddbd9279aaecb13bdc1dbea7f42acaee751cb7da83779b8785cc86f41b94b13b54964208ca287d981634778d1096f20e76ca636c0717fd27e0800c43f599a5eded807421b502eaf9990a8c8ed8 + +GCD = 4 +A = 3c719c1c363cdeb7b57c2aabb71f425da4c3e6d3e447204d555e7cf0f3d372bdda906f36078045044978dafc20171767c8b1464d52dfdf3e2ba8a4906da033a8 +B = 30fe0ef151ac51404e128c064d836b191921769dc02d9b09889ed40eb68d15bfdd2edea33580a1a4d7dcee918fefd5c776cbe80ca6131aa080d3989b5e77e1b24 +LCM = 2e4526157bbd765b0486d90bcd4728f890bc6dbd9a855c67ca5cb2d6b48f8e74e1d99485999e04b193afca58dbf282610185d6c0272007744ff26e00dbdc813929b47940b137dc56ba974da07d54a1c50ec4a5c2b26e83f47cf17f4ccce8c3687e8d1e91d7c491a599f3d057c73473723ce9eee52c20fe8ae1595447552a7ee8 + +GCD = 10 +A = 44e04071d09119ea9783a53df35de4a989200133bb20280fdca6003d3ca63fdd9350ad1a1673d444d2f7c7be639824681643ec4f77535c626bd3ee8fa100e0bb0 +B = ca927a5a3124ce89accd6ac41a8441d352a5d42feb7f62687a5ebc0e181cc2679888ecc2d38516bdc3b3443550efccac81e53044ae9341ecace2598fe5ce67780 +LCM = 36805ba9b2412a0cb3fe4ed9bdabfa55515c9d615a3d0af268c45c5f6098d2de4a583f3791f1e3883c55d51ce23c5658fd0e8faa9a3709a1cfbd6a61dbab861690f27c86664f084c86cfd4a183b24aaadf59a6f8cbec04f1b0ded8a59b188cb46ae920052e3e099a570540dbc00f7d4a571eef08aa70d2d189a1804bf04e94a80 + +GCD = 100 +A = 73725032b214a677687c811031555b0c51c1703f10d59b97a4d732b7feaec5726cb3882193419d3f057583b2bc02b297d76bb689977936febaae92638fdfc46a00 +B = 979f4c10f4dc60ad15068cedd62ff0ab293aeaa1d6935763aed41fe3e445de2e366e8661eadf345201529310f4b805c5800b99f351fddab95d7f313e3bb429d900 +LCM = 4460439b4be72f533e9c7232f7e99c48328b457969364c951868ceab56cb2cbbeda8be2e8e3cae45c0758048468b841fdb246b2086d19b59d17b389333166ab82ed785860620d53c44f7aaaff4625ee70fb8072df10fb4d1acb142eadc02978ff2bb07cea9f434e35424b3323a7bda3a1a57aa60c75e49ebb2f59fb653aa77da00 + +GCD = 100000000 +A = f8b4f19e09f5862d79fb2931c4d616a1b8e0dd44781ca52902c8035166c8fca52d33a56ff484c365ec1257de7fa8ed2786163cfc051d5223b4aad859a049e8ba00000000 +B = 6e54cb41b454b080e68a2c3dd0fa79f516eb80239af2be8250ca9cd377ba501aabafc09146fad4402bdc7a49f2c3eec815e25f4c0a223f58e36709eefd92410500000000 +LCM = 6b3020a880ddeff9d17d3dc234da8771962de3322cd15ba7b1e4b1dd4a6a2a802a16c49653865c6fdf6c207cbe0940f8d81ef4cb0e159385fd709d515ee99d109ad9ad680031cbae4eab2ed62944babdade4e3036426b18920022f737897c7d751dce98d626cdda761fec48ad87a377fb70f97a0a15aa3d10d865785719cc5a200000000 diff --git a/crypto/internal/fips140/rsa/testdata/miller_rabin_tests.txt b/crypto/internal/fips140/rsa/testdata/miller_rabin_tests.txt new file mode 100644 index 00000000000..10e9685a615 --- /dev/null +++ b/crypto/internal/fips140/rsa/testdata/miller_rabin_tests.txt @@ -0,0 +1,344 @@ +# This file contains test vectors for whether B is a Miller-Rabin composite +# witness for W. W must be odd and B must satisfy 1 <= B <= W-1. +# +# It was copied from BoringSSL's crypto/fipsmodule/bn/test/miller_rabin_tests.txt, +# removing out-of-range candidates that we reject within the iteration function. + +# Exhaustively test a small prime. + +Result = PossiblyPrime +W = 7 +B = 2 + +Result = PossiblyPrime +W = 7 +B = 3 + +Result = PossiblyPrime +W = 7 +B = 4 + +Result = PossiblyPrime +W = 7 +B = 5 + + +# Random large inputs which try to cover a few cases. The nontrivial square root +# case appears to be difficult to hit randomly. + +# b^m = w-1 +Result = PossiblyPrime +W = d6b4ffc7cf70b2a2fc5d6023015875504d40e3dcce7c2e6b762c3de7bb806a5074144e7054198dabf53d23108679ccc541d5a99efeb1d1abaf89e0dbcead2a8b +B = fabbafdbec6494ddb5ea4bf458536e87082369b0e53a200ed413f3e64b2fddc7c57c565710fbe73fae5b188fce97d8dcca74c2b5d90906c96d3c2c358a735cd + +# b^m = w-1 +Result = PossiblyPrime +W = 52cc61c42b341ad56dc11495e7cb2fe31e506b9e99522efbf44cd7c28468d3833c5e360f3c77b0aa43c0495c4e14665ab0d7cee9294c722f0de47d4401828401 +B = 3bdc9639c0fc2e77ab48d46e0b4ac6529c11c900e8fe4d82d75767c0556feb23d3f42d4924d16876a743feb386b7b84c7fd16a6c252f662faf0024d19972e62f + +# b^m = w-1 +Result = PossiblyPrime +W = cff9897aa7dce0f2afad262b2de57d301305de717f3539c537c4ce062f8cb70df13fbc1eb4a3b9f0958a8810d1ca9042b4f23334b285a15fee3fc66498761d4b +B = 9ceb43132fddf9ee4104ea1cb3eb2253c1d7f803f05f0305de9e31a17dd75832f47b8bf189a9b7ca0905f2a7470d9c6349080f481ff1708696fa12d972e7d7ba + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 67d1825dad5344170e65247a87aef1634a1b32bdc22f2f04d9d2959767bb5a27610fba55cd607e0f9fdd9fbb0f7f98e40d5e1eb2f52318fb5be4dbfd30d38861 +B = 260fb14724ff80984736859d8755ee98b25bcb56db9fde1db001a1e1273374034c5b75fd60b3710c7a08ce7d390776f010f384d4e32943cf0c477497d53e9e05 + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = ad0bc85b58aaa204177aa9431a40929beb1cbea2dd6f66a25cc54600013213b225ba881805661df43f4208965ada7aacc8095d07d3cbef1a7bbfaae8b745f731 +B = 3d9310f20e9c80269fa6830c7e1a6f02fc5c58646001a9ef6b8b3e496602ff22c3dcb2ddb6a221723fc1722ce237fb46f7a7bb2945e415c8839b15a972f076c9 + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = b25c917f55f6c7b596921daba919f35039e5d805119c1587e99849dd7104460c86214f162a6f17aea847bc7f3859e59f2991d457059511972ef373d4bc75e309 +B = a1f10b261dee84619b0423201d46af19eef9ec0612cf947c4d5c36c0c4b28207f75967e69452eabad0a5dcd28f27f7a8a7ed9c8b3e5026c6e0ba5634d94c2d44 + +# b^m = 1 +Result = PossiblyPrime +W = d3eeb0eff05b6992e9fa61b02755e155f4aae28c6e45ddb874edd86acdd2d83d18a20e0e00d8b8bc94b92d14fc3f41ced6ababe8ac98c7730c075dbe0f699369 +B = 6b7717269c6225203681a1cacec87cacd83003ec6e9e3f04effcc4f86634770c0860e1f2770b8f303719a44949664a1094205a99d95a0856758fed66d690105e + +# b^m = 1 +Result = PossiblyPrime +W = 64561b8d9aa50340c3a01ccb3e6e17f5023513661c012be288f3900a3ca76890e67290b9560fa1d480f9d2aacccca581b5690636665f243fa13aff5d0bff12d3 +B = 1f5ff70d3d60671ebc5fbfca731898a04438053dbc3c841e6335f487e457d92d9efb5d506d5bef6872d58d12b9a41c950bfc38d12ed977c90eacdd6535b811a0 + +# b^m = 1 +Result = PossiblyPrime +W = 69c63fbf44df21b0ed0ee929a740c12d1f3f064da0dcd9d509f31fa45fa27d1a759ab5a9f6f1040d7ee90a0b1e68f779273c41ea1c1198fd547ff6bd70c7e787 +B = 5f7996a9bbfd8fd88e472220b70077bfdacdd63d88885134431f024c2acb7126827b174eb093eb5313f07bb5461de9b0feb7d77ca2c39c2a323a150f33ea525f + +# End of iteration +Result = Composite +W = 28cc3e08c44571c6dcb98a9ab8b4f3e2b16e1f884997d94a3188bcbb7f1b7cdaecdae8329c013ec8f75dc00004da0039943e4262cd080b16a42910102e00dddb +B = 512061ab1c69931c2fa0bb89d8d09f3c9209230bf927ddd6fb6a72075f967ed3c4dbb5f437bf4d31ca7344782b22011ad56609dc19aed65319bababfc13dd7 + +# End of iteration +Result = Composite +W = 4eeb7b4d371c45fe8586fee3b1efd792176b70f6cc2698dfa1dd028366626febe0199c3c5f77a5c3cad0057a04767383051d41965255d03681b2a37edad34a9b +B = 4afc2e85f84017b3fd6967a227eb74c8297b40ea02733d9513bff9b3f01081963f25872f4254afc4e9321eea35b2a1e42eadb186fcc84f2f30f4a994350b93b8 + +# End of iteration +Result = Composite +W = 8e35a959555dd2eb66c65cee3c264071d20671f159e1f9896f1d0ceb041905fcf053eacc189de317c3ee6f93901223cbf30d5b7ddbbdab981790e2f6397e6803 +B = 44c0153759309ec4e5b1e59d57c1b126545ef7ea302b6e43561df4d16068b922389d6924f01c945d9080d1f93a0732599bdedae72d6d590839dc0884dd860441 + + +# 0x6c1 = 1729 = 7 * 13 * 19 is a Fermat pseudoprime. + +# Found non-trivial square root +Result = Composite +W = 6c1 +B = b8 + +# End of iteration +Result = Composite +W = 6c1 +B = 111 + +# End of iteration +Result = Composite +W = 6c1 +B = 11d + +# Found non-trivial square root +Result = Composite +W = 6c1 +B = 19c + +# Found non-trivial square root +Result = Composite +W = 6c1 +B = 223 + +# End of iteration +Result = Composite +W = 6c1 +B = 3aa + +# Found non-trivial square root +Result = Composite +W = 6c1 +B = 653 + + +# 1729 has a number of false witnesses. + +# b^m = 1 +Result = PossiblyPrime +W = 6c1 +B = 78 + +# b^m = 1 +Result = PossiblyPrime +W = 6c1 +B = eb + +# b^m = w-1 +Result = PossiblyPrime +W = 6c1 +B = 178 + +# b^m = w-1 +Result = PossiblyPrime +W = 6c1 +B = 178 + +# b^m = w-1 +Result = PossiblyPrime +W = 6c1 +B = 1aa + +# b^m = 1 +Result = PossiblyPrime +W = 6c1 +B = 271 + +# b^m = 1 +Result = PossiblyPrime +W = 6c1 +B = 2b2 + + +# https://kconrad.math.uconn.edu/blurbs/ugradnumthy/millerrabin.pdf, examples +# 3.1 and 3.2 has a complete list of false witnesses for 65 = 0x41 and +# 85 = 0x55. + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 41 +B = 8 + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 41 +B = 12 + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 41 +B = 2f + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 41 +B = 39 + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 55 +B = d + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 55 +B = 26 + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 55 +B = 2f + +# Some b^(m*2^j) = w-1 +Result = PossiblyPrime +W = 55 +B = 48 + +# Other witnesses for 65 and 85 will report composite: + +# Found non-trivial square root +Result = Composite +W = 41 +B = 2c + +# End of iteration +Result = Composite +W = 41 +B = 16 + +# End of iteration +Result = Composite +W = 41 +B = 14 + +# End of iteration +Result = Composite +W = 41 +B = 2 + +# End of iteration +Result = Composite +W = 41 +B = 3a + +# End of iteration +Result = Composite +W = 55 +B = 40 + +# End of iteration +Result = Composite +W = 55 +B = 7 + +# End of iteration +Result = Composite +W = 55 +B = 23 + +# End of iteration +Result = Composite +W = 55 +B = 2e + +# End of iteration +Result = Composite +W = 55 +B = 2a + +# W below is composite, but it is one of the worst case scenarios for +# Miller-Rabin, from Wycheproof tests. 1/4 of witnesses report the value is +# prime. Test that we correctly classify false and true witnesses. + +# b^m = w-1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 379c6027f818b5164bc13dff5e996ec7210976f33570d5c60275918b8988d97a63bb6582af85682c45667a8b94b7acab4d919ede00f5bd2ba7abc8634d66f8875fd930f35ec8013d37b958e65f07de015c0574e64198d73aab5466f3a971b74830b7f1671cb9277fbc95c1ba8c29dc903d8cea1b74c22ab9164f9c438ab9ba7d9919f832e40c3e36faca7343e2314669b0104d9c4f2e1b011cdbd9c686baef0 + +# b^m = w-1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 3cc4b644965b2133caffc2bb6258b1ecd5b586b900a09b010382fcef709e4cd37ee3e3182bf8d393c1ab6f9a933d46338b3d960923d8c9607c2b2763d5680230a2bc0c91138e9d0ecb35e7154a06aaa902d34b9b14964b81f4d8232641492d83b22cd805a115e75ddd8e63b864c00e4c90ba36a41e7966e97e063a60a6a6cfd53e1f62a57852c7443e88dcf6245557a4b65494c3e88e466ad75316aaa9727def + +# b^m = 1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 40c03b6ba22bd62c0379b1c36dfccd34d61e3d15f7af1d5f6a60ab972a9d0e956e2bb9e275294e0f1c879eb7a4555443429c99a8d74f7bd359a1046ac30072c04b0e2cbd005be15ff4ce0c93276de2c513fbc5771b5059904a87f180530f6773498114b5aaf70da01967d8294742e451df6377dd5e64b2a8968f4ba61b51a154317d63958ff3788defbeeebee21af5027c2291e8c5df8c0b66770d91b683cffe + +# b^m = w-1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 3c7c71b84f0c6c3817f57511946315cec7d0120a9c30ceabda801fbaec329a8f10c7b9f0ae90a3dada9885bf73a3cabed86784af9682f3dea50a7817f65cfc9190cf997f12784223c4965ed6e52a1be26d4dde31741cd3d1a2e2f3a74040d0f3868eef849727aa855f66c94791194ad5d360298364e2de9ca9288e6423f644b01d52e1bd66a9f7f00bd7995a9ca2ed16f40e902852c6250a3b52bbbf5bfd33e8 + +# b^m = w-1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 36e6aa9acb399a50f52be0324dcef05f3cff3117f94538f6d0952b7d7be88ba4dc75d843ff7ff775e11f55c86ba6b2a6ddebd8850c33424b4d35c66321af426662e7074f0a2409a9ccf1c66ef7d823efc8240b8f3c7e9e8dd65a64e8a3ca5b26695ef17171ffe136c0593b179414c5b5ad0d66f2a25146c38b2f97e60b0472ed72de34bff1b6ac186f23645a1bbe909cdfc2b2d861eb44931568f1bb117d8a0c + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 278f2215d3ab836043fbfa472216bbdcedb775a6a0ed711754d05aa75089a9e5d8201e113d68656f37381e44483cd365f5d383bdca5ae8d1f2e6575d7873851cfff0e12b1cfe100a04cb300cbd924353fcbd3307d01242cf6a5e86e752c6f4586bcabf48b018bb97e65c3ed409fd6f67f98987517356d88344b3c8945ccd753148a37b648dd2db44d19522a69a9ad8eb23edc55340e85a198abf179ad731db41 + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = afa1478bebbfe1157568f4ae53549b4c3a6a8771b816970bfac6ce5c8b962231db7a41da4d5f1d8bf504dcfe440325b54e1888bdae344eb969436a35e5c6ce5300d46313cb2fcb57fc83305f65f53d392de400e9231cbbc2ac8243defcaf7063c632b9601a81d83138274702ff336d727d3e82ccacce069843ac9c1c590c772c8c586b65c7085a1df5a47fc960d4098a22418b41f0062c77b5d55d17149d167 + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 10f7030590b629e0313a61bdf46936a1f25db91b2b421f7ebb671f7844c22561b44b2f7699db61e5228ebb5817afad416325f9439eff7a82d8a630c504de12eaa44d97c79ee56e726ae74ee0b472f0d5fa8f20aee426e689cd33dd084f96bf4d928a21e815f7e8aaca4a5752f39c4a76bdfaa8227dc05d0dfa885d8b26d46fbcbf0d2e0d999d2c31ad84c306c9126539dbdf447f8dc707d29c7fa8021a767668 + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 97dbb6a55c039ec926aaa5ff15a2917a2b4cafc3ca07c4c6b05f931d86c9bf60ee05cbbace194e5ca97682ec67c36394018d68c3536fbf13b50f8a7e31eaed87307759a0a48c6c58d21bc7c38b878c53db5d7a8e1fdd81abefc50470a3800852e74d76fdd1933e45f39ee97b8efb68837721890d867b32a894dd0ceb4c5844a05d384145865c10973ce748ccdd8fee73f1bf8611ce0535430b6b98fb36cad7a + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 225f58add44ed2b0a64a1d8452866d0f3c0cd45c8375e1bb33c188915c77fa11b81250b920245dda7f6126e5e0c79e6f98f89dc15db86394cf81b44f0d801e613fa4d5c6fef66fa31f26cfe6153f2e8159aad6b0351dcc0e93f9a68f649b2a77cff747b605b542d22419166befebec6cde3201e3c0cacaa2bc9d87073b8d1f1aa2b114d61de45ac8b0ad2141b43434a629ef284cd999fd82b310db7c57cf5c81 + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 2780926c9cf7c1eb2aaa935d90b6d4dea44eeefdfcf9ccd4a33feb215e3a1cb2d358136a490fed18403947f3d98807819737c66e12d42c3cc8c0e246b96b3c3b0795ab875fbaf668b81b5b05bf23e258ea00a0a140a790f76e04ab619800b7597f614ffc1a1c94be2f3f1a71d64eb47d98e4653d76eabedacff3a97ecf590e6a1fd55096b7bc9314629f698d0fbe9b01a1f2bc0bf3a2c097f99f1fd222b52ed2 + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 129cc5b0d9f8001b3895f1fcb4833779763636aeeeb3f980e63ea506202e6bde868444b6a58ff1dca08625f025a7e95a5eaaf1a8899eee640e3f05fbdb2867e2483bdc27c87b58684416e521c107f3667ed8dd23f0381edab767c5205a4378118bc011947cb6bdfe3fa4af50b8de876b555c9a0b2b0dae01261847f63e1e0cac2d032530bf19d5da60a04dfe22ce6343f60defbb94ccf0bdf010f89a4029720 + +# b^m = 1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 4e2a47cf67c3331b1e9976f583f6339cf76a8d48682d01355c25b2aed90c5544e737ecfa849c17d27a64fad7e659ef48df9a3ac0410e5c7ca8d087fc3a3ba23e5a3f000be009fcc8227ead28158c5b5d66f2efb47111638ef61cea4984de42fbd476bc2236ad02154d3ce85805c45e49d16b496e313a4052a37d4b88a3b13e598d2074a3e36a37e90278601f2b2305e034f9bf3aea8e939c3ba274e8ff4d8a14 + +# b^m = 1 +Result = PossiblyPrime +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 2455c4ab826e2ae72708a8ff51348ce4821cb86fa89e298c751c1754211c63b2e9a712d40f0235f310606fcf296726a86973f19f890d571f5b90f026e8d24d07bc0478a3c1333171587387f1f7fe4a770b593216f2743318aabacb3320c40a4e52b9f409e1176fe8db099e93a7991eb8568168e2e486fa5aa228bb1dce9df3290ef13fd21c331479bb0f8b7a7e7f03c5211ae8cc46fa4d0f46e86b2dadeddd5b + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 9951c2c02dd7deedce29bd0c78dd80066b1d69c0e6fe4a17f7d03c6a640d866d01fc8214bafb6737efd93d80a35b8993f5367ce287459b07954e9771ffbc72ccdd812d26a9bf4be0292a24eb5c3b56f09619b1c1b481f7566f7e50e65f69f5feb591bd107fec72a783429dbde6e2607f3db2c58d4b070a45b4d6b43537e19942ce890b04ae1e91069c04a96ed03ddb2f4fc456f136b98102c70a15700dbd911 + +# End of iteration +Result = Composite +W = 550fda19f97cdfbd13930911ef6e9e1cb2b7b5215a35c215d51ebffeb435642174cbe998f4451bde2d4bd2ce92ab5b9493b657f1d77d9ad4d348550247b903906109c608ecba7f88c239c76f0afc231e7f1ac1cee87b4c34448a16f7979ff4c18e65e05d5a86909615fe56587576962a2cb3ba467d9806445a0f039907601af77ba7d07578eff612364fbcac11d35e243734aa6d9a6cdcf912a2dd0a12ba7e87 +B = 4cb8217d229d5f95f6d94807a99363823655d6bba6bdafa4f0dbfe7a5c538aa79c918710aad4f55caaee5ab405ebdcef29dfb76cae99fca8d5a955b6315f71a3cb2d69a217ff45aed66ba87cdc5c0de5d512c6dd12e641e9fe6a2557dd2f03bf3a18650ff139efa179f0fbe69cbb4b54e50d13177bfe7bb90de36b548d5ccfef74b05d3c08a7e2a3bb4dc8d7eb338a7a1b068c433ea204d171eda5e7c6b6722c diff --git a/crypto/internal/fips140/sha256/_asm/go.mod b/crypto/internal/fips140/sha256/_asm/go.mod new file mode 100644 index 00000000000..eb93418b8a1 --- /dev/null +++ b/crypto/internal/fips140/sha256/_asm/go.mod @@ -0,0 +1,11 @@ +module crypto/sha256/_asm + +go 1.24 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) diff --git a/crypto/internal/fips140/sha256/_asm/go.sum b/crypto/internal/fips140/sha256/_asm/go.sum new file mode 100644 index 00000000000..76af484b2eb --- /dev/null +++ b/crypto/internal/fips140/sha256/_asm/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/crypto/internal/fips140/sha256/_asm/sha256block_amd64_asm.go b/crypto/internal/fips140/sha256/_asm/sha256block_amd64_asm.go new file mode 100644 index 00000000000..b26c2418e51 --- /dev/null +++ b/crypto/internal/fips140/sha256/_asm/sha256block_amd64_asm.go @@ -0,0 +1,326 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "os" + + . "github.com/mmcloughlin/avo/build" + . "github.com/mmcloughlin/avo/operand" + . "github.com/mmcloughlin/avo/reg" +) + +//go:generate go run . -out ../sha256block_amd64.s + +// SHA256 block routine. See sha256block.go for Go equivalent. +// +// The algorithm is detailed in FIPS 180-4: +// +// https://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf + +// Wt = Mt; for 0 <= t <= 15 +// Wt = SIGMA1(Wt-2) + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 63 +// +// a = H0 +// b = H1 +// c = H2 +// d = H3 +// e = H4 +// f = H5 +// g = H6 +// h = H7 +// +// for t = 0 to 63 { +// T1 = h + BIGSIGMA1(e) + Ch(e,f,g) + Kt + Wt +// T2 = BIGSIGMA0(a) + Maj(a,b,c) +// h = g +// g = f +// f = e +// e = d + T1 +// d = c +// c = b +// b = a +// a = T1 + T2 +// } +// +// H0 = a + H0 +// H1 = b + H1 +// H2 = c + H2 +// H3 = d + H3 +// H4 = e + H4 +// H5 = f + H5 +// H6 = g + H6 +// H7 = h + H7 + +func main() { + // https://github.com/mmcloughlin/avo/issues/450 + os.Setenv("GOOS", "linux") + os.Setenv("GOARCH", "amd64") + + Package("crypto/internal/fips140/sha256") + ConstraintExpr("!purego") + blockAMD64() + blockAVX2() + blockSHANI() + Generate() +} + +// Wt = Mt; for 0 <= t <= 15 +func msgSchedule0(index int) { + MOVL(Mem{Base: SI}.Offset(index*4), EAX) + BSWAPL(EAX) + MOVL(EAX, Mem{Base: BP}.Offset(index*4)) +} + +// Wt = SIGMA1(Wt-2) + Wt-7 + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 63 +// +// SIGMA0(x) = ROTR(7,x) XOR ROTR(18,x) XOR SHR(3,x) +// SIGMA1(x) = ROTR(17,x) XOR ROTR(19,x) XOR SHR(10,x) +func msgSchedule1(index int) { + MOVL(Mem{Base: BP}.Offset((index-2)*4), EAX) + MOVL(EAX, ECX) + RORL(Imm(17), EAX) + MOVL(ECX, EDX) + RORL(Imm(19), ECX) + SHRL(Imm(10), EDX) + MOVL(Mem{Base: BP}.Offset((index-15)*4), EBX) + XORL(ECX, EAX) + MOVL(EBX, ECX) + XORL(EDX, EAX) + RORL(Imm(7), EBX) + MOVL(ECX, EDX) + SHRL(Imm(3), EDX) + RORL(Imm(18), ECX) + ADDL(Mem{Base: BP}.Offset((index-7)*4), EAX) + XORL(ECX, EBX) + XORL(EDX, EBX) + ADDL(Mem{Base: BP}.Offset((index-16)*4), EBX) + ADDL(EBX, EAX) + MOVL(EAX, Mem{Base: BP}.Offset((index)*4)) +} + +// Calculate T1 in AX - uses AX, CX and DX registers. +// h is also used as an accumulator. Wt is passed in AX. +// +// T1 = h + BIGSIGMA1(e) + Ch(e, f, g) + Kt + Wt +// BIGSIGMA1(x) = ROTR(6,x) XOR ROTR(11,x) XOR ROTR(25,x) +// Ch(x, y, z) = (x AND y) XOR (NOT x AND z) +func sha256T1(konst uint32, e, f, g, h GPPhysical) { + ADDL(EAX, h) + MOVL(e, EAX) + ADDL(U32(konst), h) + MOVL(e, ECX) + RORL(U8(6), EAX) + MOVL(e, EDX) + RORL(U8(11), ECX) + XORL(ECX, EAX) + MOVL(e, ECX) + RORL(U8(25), EDX) + ANDL(f, ECX) + XORL(EAX, EDX) + MOVL(e, EAX) + NOTL(EAX) + ADDL(EDX, h) + ANDL(g, EAX) + XORL(ECX, EAX) + ADDL(h, EAX) +} + +// Calculate T2 in BX - uses BX, CX, DX and DI registers. +// +// T2 = BIGSIGMA0(a) + Maj(a, b, c) +// BIGSIGMA0(x) = ROTR(2,x) XOR ROTR(13,x) XOR ROTR(22,x) +// Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z) +func sha256T2(a, b, c GPPhysical) { + MOVL(a, EDI) + MOVL(c, EBX) + RORL(U8(2), EDI) + MOVL(a, EDX) + ANDL(b, EBX) + RORL(U8(13), EDX) + MOVL(a, ECX) + ANDL(c, ECX) + XORL(EDX, EDI) + XORL(ECX, EBX) + MOVL(a, EDX) + MOVL(b, ECX) + RORL(U8(22), EDX) + ANDL(a, ECX) + XORL(ECX, EBX) + XORL(EDX, EDI) + ADDL(EDI, EBX) +} + +// Calculate T1 and T2, then e = d + T1 and a = T1 + T2. +// The values for e and a are stored in d and h, ready for rotation. +func sha256Round(index int, konst uint32, a, b, c, d, e, f, g, h GPPhysical) { + sha256T1(konst, e, f, g, h) + sha256T2(a, b, c) + MOVL(EBX, h) + ADDL(EAX, d) + ADDL(EAX, h) +} + +func sha256Round0(index int, konst uint32, a, b, c, d, e, f, g, h GPPhysical) { + msgSchedule0(index) + sha256Round(index, konst, a, b, c, d, e, f, g, h) +} + +func sha256Round1(index int, konst uint32, a, b, c, d, e, f, g, h GPPhysical) { + msgSchedule1(index) + sha256Round(index, konst, a, b, c, d, e, f, g, h) +} + +func blockAMD64() { + Implement("blockAMD64") + AllocLocal(256 + 8) + + Load(Param("p").Base(), RSI) + Load(Param("p").Len(), RDX) + SHRQ(Imm(6), RDX) + SHLQ(Imm(6), RDX) + + // Return if p is empty + LEAQ(Mem{Base: RSI, Index: RDX, Scale: 1}, RDI) + MOVQ(RDI, Mem{Base: SP}.Offset(256)) + CMPQ(RSI, RDI) + JEQ(LabelRef("end")) + + BP := Mem{Base: BP} + Load(Param("dig"), RBP) + MOVL(BP.Offset(0*4), R8L) // a = H0 + MOVL(BP.Offset(1*4), R9L) // b = H1 + MOVL(BP.Offset(2*4), R10L) // c = H2 + MOVL(BP.Offset(3*4), R11L) // d = H3 + MOVL(BP.Offset(4*4), R12L) // e = H4 + MOVL(BP.Offset(5*4), R13L) // f = H5 + MOVL(BP.Offset(6*4), R14L) // g = H6 + MOVL(BP.Offset(7*4), R15L) // h = H7 + + loop() + end() +} + +func rotateRight(slice *[]GPPhysical) []GPPhysical { + n := len(*slice) + new := make([]GPPhysical, n) + for i, reg := range *slice { + new[(i+1)%n] = reg + } + return new +} + +func loop() { + Label("loop") + MOVQ(RSP, RBP) + + regs := []GPPhysical{R8L, R9L, R10L, R11L, R12L, R13L, R14L, R15L} + n := len(_K) + + for i := 0; i < 16; i++ { + sha256Round0(i, _K[i], regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]) + regs = rotateRight(®s) + } + + for i := 16; i < n; i++ { + sha256Round1(i, _K[i], regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]) + regs = rotateRight(®s) + } + + Load(Param("dig"), RBP) + BP := Mem{Base: BP} + ADDL(BP.Offset(0*4), R8L) // H0 = a + H0 + MOVL(R8L, BP.Offset(0*4)) + ADDL(BP.Offset(1*4), R9L) // H1 = b + H1 + MOVL(R9L, BP.Offset(1*4)) + ADDL(BP.Offset(2*4), R10L) // H2 = c + H2 + MOVL(R10L, BP.Offset(2*4)) + ADDL(BP.Offset(3*4), R11L) // H3 = d + H3 + MOVL(R11L, BP.Offset(3*4)) + ADDL(BP.Offset(4*4), R12L) // H4 = e + H4 + MOVL(R12L, BP.Offset(4*4)) + ADDL(BP.Offset(5*4), R13L) // H5 = f + H5 + MOVL(R13L, BP.Offset(5*4)) + ADDL(BP.Offset(6*4), R14L) // H6 = g + H6 + MOVL(R14L, BP.Offset(6*4)) + ADDL(BP.Offset(7*4), R15L) // H7 = h + H7 + MOVL(R15L, BP.Offset(7*4)) + + ADDQ(Imm(64), RSI) + CMPQ(RSI, Mem{Base: SP}.Offset(256)) + JB(LabelRef("loop")) +} + +func end() { + Label("end") + RET() +} + +var _K = []uint32{ + 0x428a2f98, + 0x71374491, + 0xb5c0fbcf, + 0xe9b5dba5, + 0x3956c25b, + 0x59f111f1, + 0x923f82a4, + 0xab1c5ed5, + 0xd807aa98, + 0x12835b01, + 0x243185be, + 0x550c7dc3, + 0x72be5d74, + 0x80deb1fe, + 0x9bdc06a7, + 0xc19bf174, + 0xe49b69c1, + 0xefbe4786, + 0x0fc19dc6, + 0x240ca1cc, + 0x2de92c6f, + 0x4a7484aa, + 0x5cb0a9dc, + 0x76f988da, + 0x983e5152, + 0xa831c66d, + 0xb00327c8, + 0xbf597fc7, + 0xc6e00bf3, + 0xd5a79147, + 0x06ca6351, + 0x14292967, + 0x27b70a85, + 0x2e1b2138, + 0x4d2c6dfc, + 0x53380d13, + 0x650a7354, + 0x766a0abb, + 0x81c2c92e, + 0x92722c85, + 0xa2bfe8a1, + 0xa81a664b, + 0xc24b8b70, + 0xc76c51a3, + 0xd192e819, + 0xd6990624, + 0xf40e3585, + 0x106aa070, + 0x19a4c116, + 0x1e376c08, + 0x2748774c, + 0x34b0bcb5, + 0x391c0cb3, + 0x4ed8aa4a, + 0x5b9cca4f, + 0x682e6ff3, + 0x748f82ee, + 0x78a5636f, + 0x84c87814, + 0x8cc70208, + 0x90befffa, + 0xa4506ceb, + 0xbef9a3f7, + 0xc67178f2, +} diff --git a/crypto/sha256/_asm/sha256block_amd64_asm.go b/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go similarity index 72% rename from crypto/sha256/_asm/sha256block_amd64_asm.go rename to crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go index 549906272d5..0e6f1c74cf5 100644 --- a/crypto/sha256/_asm/sha256block_amd64_asm.go +++ b/crypto/internal/fips140/sha256/_asm/sha256block_amd64_avx2.go @@ -10,14 +10,6 @@ import ( . "github.com/mmcloughlin/avo/reg" ) -//go:generate go run . -out ../sha256block_amd64.s -pkg sha256 - -// SHA256 block routine. See sha256block.go for Go equivalent. -// -// The algorithm is detailed in FIPS 180-4: -// -// https://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf - // The avx2-version is described in an Intel White-Paper: // "Fast SHA-256 Implementations on Intel Architecture Processors" // To find it, surf to http://www.intel.com/p/en_US/embedded @@ -29,154 +21,223 @@ import ( // Kirk Yap // Tim Chen -// Wt = Mt; for 0 <= t <= 15 -// Wt = SIGMA1(Wt-2) + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 63 -// -// a = H0 -// b = H1 -// c = H2 -// d = H3 -// e = H4 -// f = H5 -// g = H6 -// h = H7 -// -// for t = 0 to 63 { -// T1 = h + BIGSIGMA1(e) + Ch(e,f,g) + Kt + Wt -// T2 = BIGSIGMA0(a) + Maj(a,b,c) -// h = g -// g = f -// f = e -// e = d + T1 -// d = c -// c = b -// b = a -// a = T1 + T2 -// } -// -// H0 = a + H0 -// H1 = b + H1 -// H2 = c + H2 -// H3 = d + H3 -// H4 = e + H4 -// H5 = f + H5 -// H6 = g + H6 -// H7 = h + H7 - -func main() { - Package("github.com/runZeroInc/excrypto/crypto/sha256") - ConstraintExpr("!purego") - block() - Generate() +func blockAVX2() { + Implement("blockAVX2") + AllocLocal(536) + + Load(Param("dig"), CTX) // d.h[8] + Load(Param("p").Base(), INP) + Load(Param("p").Len(), NUM_BYTES) + + LEAQ(Mem{Base: INP, Index: NUM_BYTES, Scale: 1, Disp: -64}, NUM_BYTES) // Pointer to the last block + MOVQ(NUM_BYTES, Mem{Base: SP}.Offset(_INP_END)) + + CMPQ(NUM_BYTES, INP) + JE(LabelRef("avx2_only_one_block")) + + Comment("Load initial digest") + CTX := Mem{Base: CTX} + MOVL(CTX.Offset(0), a) // a = H0 + MOVL(CTX.Offset(4), b) // b = H1 + MOVL(CTX.Offset(8), c) // c = H2 + MOVL(CTX.Offset(12), d) // d = H3 + MOVL(CTX.Offset(16), e) // e = H4 + MOVL(CTX.Offset(20), f) // f = H5 + MOVL(CTX.Offset(24), g) // g = H6 + MOVL(CTX.Offset(28), h) // h = H7 + + avx2_loop0() + avx2_last_block_enter() + avx2_loop1() + avx2_loop2() + avx2_loop3() + avx2_do_last_block() + avx2_only_one_block() + done_hash() } -// Wt = Mt; for 0 <= t <= 15 -func msgSchedule0(index int) { - MOVL(Mem{Base: SI}.Offset(index*4), EAX) - BSWAPL(EAX) - MOVL(EAX, Mem{Base: BP}.Offset(index*4)) +func avx2_loop0() { + Label("avx2_loop0") + Comment("at each iteration works with one block (512 bit)") + VMOVDQU(Mem{Base: INP}.Offset(0*32), XTMP0) + VMOVDQU(Mem{Base: INP}.Offset(1*32), XTMP1) + VMOVDQU(Mem{Base: INP}.Offset(2*32), XTMP2) + VMOVDQU(Mem{Base: INP}.Offset(3*32), XTMP3) + + flip_mask := flip_mask_DATA() + + VMOVDQU(flip_mask, BYTE_FLIP_MASK) + + Comment("Apply Byte Flip Mask: LE -> BE") + VPSHUFB(BYTE_FLIP_MASK, XTMP0, XTMP0) + VPSHUFB(BYTE_FLIP_MASK, XTMP1, XTMP1) + VPSHUFB(BYTE_FLIP_MASK, XTMP2, XTMP2) + VPSHUFB(BYTE_FLIP_MASK, XTMP3, XTMP3) + + Comment("Transpose data into high/low parts") + VPERM2I128(Imm(0x20), XTMP2, XTMP0, XDWORD0) // w3, w2, w1, w0 + VPERM2I128(Imm(0x31), XTMP2, XTMP0, XDWORD1) // w7, w6, w5, w4 + VPERM2I128(Imm(0x20), XTMP3, XTMP1, XDWORD2) // w11, w10, w9, w8 + VPERM2I128(Imm(0x31), XTMP3, XTMP1, XDWORD3) // w15, w14, w13, w12 + + K256 := K256_DATA() + LEAQ(K256, TBL) // Loading address of table with round-specific constants } -// Wt = SIGMA1(Wt-2) + Wt-7 + SIGMA0(Wt-15) + Wt-16; for 16 <= t <= 63 -// -// SIGMA0(x) = ROTR(7,x) XOR ROTR(18,x) XOR SHR(3,x) -// SIGMA1(x) = ROTR(17,x) XOR ROTR(19,x) XOR SHR(10,x) -func msgSchedule1(index int) { - MOVL(Mem{Base: BP}.Offset((index-2)*4), EAX) - MOVL(EAX, ECX) - RORL(Imm(17), EAX) - MOVL(ECX, EDX) - RORL(Imm(19), ECX) - SHRL(Imm(10), EDX) - MOVL(Mem{Base: BP}.Offset((index-15)*4), EBX) - XORL(ECX, EAX) - MOVL(EBX, ECX) - XORL(EDX, EAX) - RORL(Imm(7), EBX) - MOVL(ECX, EDX) - SHRL(Imm(3), EDX) - RORL(Imm(18), ECX) - ADDL(Mem{Base: BP}.Offset((index-7)*4), EAX) - XORL(ECX, EBX) - XORL(EDX, EBX) - ADDL(Mem{Base: BP}.Offset((index-16)*4), EBX) - ADDL(EBX, EAX) - MOVL(EAX, Mem{Base: BP}.Offset((index)*4)) +func avx2_last_block_enter() { + Label("avx2_last_block_enter") + ADDQ(Imm(64), INP) + MOVQ(INP, Mem{Base: SP}.Offset(_INP)) + XORQ(SRND, SRND) } -// Calculate T1 in AX - uses AX, CX and DX registers. -// h is also used as an accumulator. Wt is passed in AX. -// -// T1 = h + BIGSIGMA1(e) + Ch(e, f, g) + Kt + Wt -// BIGSIGMA1(x) = ROTR(6,x) XOR ROTR(11,x) XOR ROTR(25,x) -// Ch(x, y, z) = (x AND y) XOR (NOT x AND z) -func sha256T1(konst uint32, e, f, g, h GPPhysical) { - ADDL(EAX, h) - MOVL(e, EAX) - ADDL(U32(konst), h) - MOVL(e, ECX) - RORL(U8(6), EAX) - MOVL(e, EDX) - RORL(U8(11), ECX) - XORL(ECX, EAX) - MOVL(e, ECX) - RORL(U8(25), EDX) - ANDL(f, ECX) - XORL(EAX, EDX) - MOVL(e, EAX) - NOTL(EAX) - ADDL(EDX, h) - ANDL(g, EAX) - XORL(ECX, EAX) - ADDL(h, EAX) +// for w0 - w47 +func avx2_loop1() { + Label("avx2_loop1") + + Comment("Do 4 rounds and scheduling") + VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset((0 * 32)), XDWORD0, XFER) + VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+0*32)) + roundAndSchedN0(_XFER+0*32, a, b, c, d, e, f, g, h, XDWORD0, XDWORD1, XDWORD2, XDWORD3) + roundAndSchedN1(_XFER+0*32, h, a, b, c, d, e, f, g, XDWORD0, XDWORD1, XDWORD2, XDWORD3) + roundAndSchedN2(_XFER+0*32, g, h, a, b, c, d, e, f, XDWORD0, XDWORD1, XDWORD2, XDWORD3) + roundAndSchedN3(_XFER+0*32, f, g, h, a, b, c, d, e, XDWORD0, XDWORD1, XDWORD2, XDWORD3) + + Comment("Do 4 rounds and scheduling") + VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset(1*32), XDWORD1, XFER) + VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+1*32)) + roundAndSchedN0(_XFER+1*32, e, f, g, h, a, b, c, d, XDWORD1, XDWORD2, XDWORD3, XDWORD0) + roundAndSchedN1(_XFER+1*32, d, e, f, g, h, a, b, c, XDWORD1, XDWORD2, XDWORD3, XDWORD0) + roundAndSchedN2(_XFER+1*32, c, d, e, f, g, h, a, b, XDWORD1, XDWORD2, XDWORD3, XDWORD0) + roundAndSchedN3(_XFER+1*32, b, c, d, e, f, g, h, a, XDWORD1, XDWORD2, XDWORD3, XDWORD0) + + Comment("Do 4 rounds and scheduling") + VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset((2 * 32)), XDWORD2, XFER) + VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+2*32)) + roundAndSchedN0(_XFER+2*32, a, b, c, d, e, f, g, h, XDWORD2, XDWORD3, XDWORD0, XDWORD1) + roundAndSchedN1(_XFER+2*32, h, a, b, c, d, e, f, g, XDWORD2, XDWORD3, XDWORD0, XDWORD1) + roundAndSchedN2(_XFER+2*32, g, h, a, b, c, d, e, f, XDWORD2, XDWORD3, XDWORD0, XDWORD1) + roundAndSchedN3(_XFER+2*32, f, g, h, a, b, c, d, e, XDWORD2, XDWORD3, XDWORD0, XDWORD1) + + Comment("Do 4 rounds and scheduling") + VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset((3 * 32)), XDWORD3, XFER) + VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+3*32)) + roundAndSchedN0(_XFER+3*32, e, f, g, h, a, b, c, d, XDWORD3, XDWORD0, XDWORD1, XDWORD2) + roundAndSchedN1(_XFER+3*32, d, e, f, g, h, a, b, c, XDWORD3, XDWORD0, XDWORD1, XDWORD2) + roundAndSchedN2(_XFER+3*32, c, d, e, f, g, h, a, b, XDWORD3, XDWORD0, XDWORD1, XDWORD2) + roundAndSchedN3(_XFER+3*32, b, c, d, e, f, g, h, a, XDWORD3, XDWORD0, XDWORD1, XDWORD2) + + ADDQ(Imm(4*32), SRND) + CMPQ(SRND, U32(3*4*32)) + JB(LabelRef("avx2_loop1")) } -// Calculate T2 in BX - uses BX, CX, DX and DI registers. -// -// T2 = BIGSIGMA0(a) + Maj(a, b, c) -// BIGSIGMA0(x) = ROTR(2,x) XOR ROTR(13,x) XOR ROTR(22,x) -// Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z) -func sha256T2(a, b, c GPPhysical) { - MOVL(a, EDI) - MOVL(c, EBX) - RORL(U8(2), EDI) - MOVL(a, EDX) - ANDL(b, EBX) - RORL(U8(13), EDX) - MOVL(a, ECX) - ANDL(c, ECX) - XORL(EDX, EDI) - XORL(ECX, EBX) - MOVL(a, EDX) - MOVL(b, ECX) - RORL(U8(22), EDX) - ANDL(a, ECX) - XORL(ECX, EBX) - XORL(EDX, EDI) - ADDL(EDI, EBX) +// w48 - w63 processed with no scheduling (last 16 rounds) +func avx2_loop2() { + Label("avx2_loop2") + VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset(0*32), XDWORD0, XFER) + VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+0*32)) + doRoundN0(_XFER+0*32, a, b, c, d, e, f, g, h, h) + doRoundN1(_XFER+0*32, h, a, b, c, d, e, f, g, h) + doRoundN2(_XFER+0*32, g, h, a, b, c, d, e, f, g) + doRoundN3(_XFER+0*32, f, g, h, a, b, c, d, e, f) + + VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset(1*32), XDWORD1, XFER) + VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+1*32)) + doRoundN0(_XFER+1*32, e, f, g, h, a, b, c, d, e) + doRoundN1(_XFER+1*32, d, e, f, g, h, a, b, c, d) + doRoundN2(_XFER+1*32, c, d, e, f, g, h, a, b, c) + doRoundN3(_XFER+1*32, b, c, d, e, f, g, h, a, b) + + ADDQ(Imm(2*32), SRND) + + VMOVDQU(XDWORD2, XDWORD0) + VMOVDQU(XDWORD3, XDWORD1) + + CMPQ(SRND, U32(4*4*32)) + JB(LabelRef("avx2_loop2")) + + Load(Param("dig"), CTX) // d.h[8] + MOVQ(Mem{Base: SP}.Offset(_INP), INP) + + registers := []GPPhysical{a, b, c, d, e, f, g, h} + for i, reg := range registers { + addm(Mem{Base: CTX}.Offset(i*4), reg) + } + + CMPQ(Mem{Base: SP}.Offset(_INP_END), INP) + JB(LabelRef("done_hash")) + + XORQ(SRND, SRND) } -// Calculate T1 and T2, then e = d + T1 and a = T1 + T2. -// The values for e and a are stored in d and h, ready for rotation. -func sha256Round(index int, konst uint32, a, b, c, d, e, f, g, h GPPhysical) { - sha256T1(konst, e, f, g, h) - sha256T2(a, b, c) - MOVL(EBX, h) - ADDL(EAX, d) - ADDL(EAX, h) +// Do second block using previously scheduled results +func avx2_loop3() { + Label("avx2_loop3") + doRoundN0(_XFER+0*32+16, a, b, c, d, e, f, g, h, a) + doRoundN1(_XFER+0*32+16, h, a, b, c, d, e, f, g, h) + doRoundN2(_XFER+0*32+16, g, h, a, b, c, d, e, f, g) + doRoundN3(_XFER+0*32+16, f, g, h, a, b, c, d, e, f) + + doRoundN0(_XFER+1*32+16, e, f, g, h, a, b, c, d, e) + doRoundN1(_XFER+1*32+16, d, e, f, g, h, a, b, c, d) + doRoundN2(_XFER+1*32+16, c, d, e, f, g, h, a, b, c) + doRoundN3(_XFER+1*32+16, b, c, d, e, f, g, h, a, b) + + ADDQ(Imm(2*32), SRND) + CMPQ(SRND, U32(4*4*32)) + JB(LabelRef("avx2_loop3")) + + Load(Param("dig"), CTX) // d.h[8] + MOVQ(Mem{Base: SP}.Offset(_INP), INP) + ADDQ(Imm(64), INP) + + registers := []GPPhysical{a, b, c, d, e, f, g, h} + for i, reg := range registers { + addm(Mem{Base: CTX}.Offset(i*4), reg) + } + + CMPQ(Mem{Base: SP}.Offset(_INP_END), INP) + JA(LabelRef("avx2_loop0")) + JB(LabelRef("done_hash")) } -func sha256Round0(index int, konst uint32, a, b, c, d, e, f, g, h GPPhysical) { - msgSchedule0(index) - sha256Round(index, konst, a, b, c, d, e, f, g, h) +func avx2_do_last_block() { + Label("avx2_do_last_block") + VMOVDQU(Mem{Base: INP}.Offset(0), XWORD0) + VMOVDQU(Mem{Base: INP}.Offset(16), XWORD1) + VMOVDQU(Mem{Base: INP}.Offset(32), XWORD2) + VMOVDQU(Mem{Base: INP}.Offset(48), XWORD3) + + flip_mask := flip_mask_DATA() + VMOVDQU(flip_mask, BYTE_FLIP_MASK) + + VPSHUFB(X_BYTE_FLIP_MASK, XWORD0, XWORD0) + VPSHUFB(X_BYTE_FLIP_MASK, XWORD1, XWORD1) + VPSHUFB(X_BYTE_FLIP_MASK, XWORD2, XWORD2) + VPSHUFB(X_BYTE_FLIP_MASK, XWORD3, XWORD3) + + K256 := K256_DATA() + LEAQ(K256, TBL) + + JMP(LabelRef("avx2_last_block_enter")) } -func sha256Round1(index int, konst uint32, a, b, c, d, e, f, g, h GPPhysical) { - msgSchedule1(index) - sha256Round(index, konst, a, b, c, d, e, f, g, h) +// Load initial digest +func avx2_only_one_block() { + Label("avx2_only_one_block") + registers := []GPPhysical{a, b, c, d, e, f, g, h} + for i, reg := range registers { + MOVL(Mem{Base: CTX}.Offset(i*4), reg) + } + JMP(LabelRef("avx2_do_last_block")) } -// Definitions for AVX2 version +func done_hash() { + Label("done_hash") + VZEROUPPER() + RET() +} // addm (mem), reg // - Add reg to mem using reg-mem add and store @@ -586,489 +647,6 @@ func doRoundN3(disp int, a, b, c, d, e, f, g, h, old_h GPPhysical) { ADDL(y3, h) // h = t1 + S0 + MAJ } -// Definitions for sha-ni version -// -// The sha-ni implementation uses Intel(R) SHA extensions SHA256RNDS2, SHA256MSG1, SHA256MSG2 -// It also reuses portions of the flip_mask (half) and K256 table (stride 32) from the avx2 version -// -// Reference -// S. Gulley, et al, "New Instructions Supporting the Secure Hash -// Algorithm on Intel® Architecture Processors", July 2013 -// https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sha-extensions.html -// - -var ( - digestPtr GPPhysical = RDI // input/output, base pointer to digest hash vector H0, H1, ..., H7 - dataPtr = RSI // input, base pointer to first input data block - numBytes = RDX // input, number of input bytes to be processed - sha256Constants = RAX // round contents from K256 table, indexed by round number x 32 - msg VecPhysical = X0 // input data - state0 = X1 // round intermediates and outputs - state1 = X2 - m0 = X3 // m0, m1,... m4 -- round message temps - m1 = X4 - m2 = X5 - m3 = X6 - m4 = X7 - shufMask = X8 // input data endian conversion control mask - abefSave = X9 // digest hash vector inter-block buffer abef - cdghSave = X10 // digest hash vector inter-block buffer cdgh -) - -// nop instead of final SHA256MSG1 for first and last few rounds -func nop(m, a VecPhysical) { -} - -// final SHA256MSG1 for middle rounds that require it -func sha256msg1(m, a VecPhysical) { - SHA256MSG1(m, a) -} - -// msg copy for all but rounds 12-15 -func vmov(a, b VecPhysical) { - VMOVDQA(a, b) -} - -// reverse copy for rounds 12-15 -func vmovrev(a, b VecPhysical) { - VMOVDQA(b, a) -} - -type VecFunc func(a, b VecPhysical) - -// sha rounds 0 to 11 -// -// identical with the exception of the final msg op -// which is replaced with a nop for rounds where it is not needed -// refer to Gulley, et al for more information -func rounds0to11(m, a VecPhysical, c int, sha256msg1 VecFunc) { - VMOVDQU(Mem{Base: dataPtr}.Offset(c*16), msg) - PSHUFB(shufMask, msg) - VMOVDQA(msg, m) - PADDD(Mem{Base: sha256Constants}.Offset(c*32), msg) - SHA256RNDS2(msg, state0, state1) - PSHUFD(U8(0x0e), msg, msg) - SHA256RNDS2(msg, state1, state0) - sha256msg1(m, a) -} - -// sha rounds 12 to 59 -// -// identical with the exception of the final msg op -// and the reverse copy(m,msg) in round 12 which is required -// after the last data load -// refer to Gulley, et al for more information -func rounds12to59(m VecPhysical, c int, a, t VecPhysical, sha256msg1, movop VecFunc) { - movop(m, msg) - PADDD(Mem{Base: sha256Constants}.Offset(c*32), msg) - SHA256RNDS2(msg, state0, state1) - VMOVDQA(m, m4) - PALIGNR(Imm(4), a, m4) - PADDD(m4, t) - SHA256MSG2(m, t) - PSHUFD(Imm(0x0e), msg, msg) - SHA256RNDS2(msg, state1, state0) - sha256msg1(m, a) -} - -func block() { - Implement("block") - AllocLocal(536) - - checkArchFlags() - sha256() - avx2() - sha_ni() -} - -func checkArchFlags() { - CMPB(Mem{Symbol: Symbol{Name: "·useSHA"}, Base: StaticBase}, Imm(1)) - JE(LabelRef("sha_ni")) - CMPB(Mem{Symbol: Symbol{Name: "·useAVX2"}, Base: StaticBase}, Imm(1)) - JE(LabelRef("avx2")) -} - -func sha256() { - Load(Param("p").Base(), RSI) - Load(Param("p").Len(), RDX) - SHRQ(Imm(6), RDX) - SHLQ(Imm(6), RDX) - - // Return if p is empty - LEAQ(Mem{Base: RSI, Index: RDX, Scale: 1}, RDI) - MOVQ(RDI, Mem{Base: SP}.Offset(256)) - CMPQ(RSI, RDI) - JEQ(LabelRef("end")) - - BP := Mem{Base: BP} - Load(Param("dig"), RBP) - MOVL(BP.Offset(0*4), R8L) // a = H0 - MOVL(BP.Offset(1*4), R9L) // b = H1 - MOVL(BP.Offset(2*4), R10L) // c = H2 - MOVL(BP.Offset(3*4), R11L) // d = H3 - MOVL(BP.Offset(4*4), R12L) // e = H4 - MOVL(BP.Offset(5*4), R13L) // f = H5 - MOVL(BP.Offset(6*4), R14L) // g = H6 - MOVL(BP.Offset(7*4), R15L) // h = H7 - - loop() - end() -} - -func rotateRight(slice *[]GPPhysical) []GPPhysical { - n := len(*slice) - new := make([]GPPhysical, n) - for i, reg := range *slice { - new[(i+1)%n] = reg - } - return new -} - -func loop() { - Label("loop") - MOVQ(RSP, RBP) - - regs := []GPPhysical{R8L, R9L, R10L, R11L, R12L, R13L, R14L, R15L} - n := len(_K) - - for i := 0; i < 16; i++ { - sha256Round0(i, _K[i], regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]) - regs = rotateRight(®s) - } - - for i := 16; i < n; i++ { - sha256Round1(i, _K[i], regs[0], regs[1], regs[2], regs[3], regs[4], regs[5], regs[6], regs[7]) - regs = rotateRight(®s) - } - - Load(Param("dig"), RBP) - BP := Mem{Base: BP} - ADDL(BP.Offset(0*4), R8L) // H0 = a + H0 - MOVL(R8L, BP.Offset(0*4)) - ADDL(BP.Offset(1*4), R9L) // H1 = b + H1 - MOVL(R9L, BP.Offset(1*4)) - ADDL(BP.Offset(2*4), R10L) // H2 = c + H2 - MOVL(R10L, BP.Offset(2*4)) - ADDL(BP.Offset(3*4), R11L) // H3 = d + H3 - MOVL(R11L, BP.Offset(3*4)) - ADDL(BP.Offset(4*4), R12L) // H4 = e + H4 - MOVL(R12L, BP.Offset(4*4)) - ADDL(BP.Offset(5*4), R13L) // H5 = f + H5 - MOVL(R13L, BP.Offset(5*4)) - ADDL(BP.Offset(6*4), R14L) // H6 = g + H6 - MOVL(R14L, BP.Offset(6*4)) - ADDL(BP.Offset(7*4), R15L) // H7 = h + H7 - MOVL(R15L, BP.Offset(7*4)) - - ADDQ(Imm(64), RSI) - CMPQ(RSI, Mem{Base: SP}.Offset(256)) - JB(LabelRef("loop")) -} - -func end() { - Label("end") - RET() -} - -func avx2() { - Label("avx2") - Load(Param("dig"), CTX) // d.h[8] - Load(Param("p").Base(), INP) - Load(Param("p").Len(), NUM_BYTES) - - LEAQ(Mem{Base: INP, Index: NUM_BYTES, Scale: 1, Disp: -64}, NUM_BYTES) // Pointer to the last block - MOVQ(NUM_BYTES, Mem{Base: SP}.Offset(_INP_END)) - - CMPQ(NUM_BYTES, INP) - JE(LabelRef("avx2_only_one_block")) - - Comment("Load initial digest") - CTX := Mem{Base: CTX} - MOVL(CTX.Offset(0), a) // a = H0 - MOVL(CTX.Offset(4), b) // b = H1 - MOVL(CTX.Offset(8), c) // c = H2 - MOVL(CTX.Offset(12), d) // d = H3 - MOVL(CTX.Offset(16), e) // e = H4 - MOVL(CTX.Offset(20), f) // f = H5 - MOVL(CTX.Offset(24), g) // g = H6 - MOVL(CTX.Offset(28), h) // h = H7 - - avx2_loop0() - avx2_last_block_enter() - avx2_loop1() - avx2_loop2() - avx2_loop3() - avx2_do_last_block() - avx2_only_one_block() - done_hash() -} - -func avx2_loop0() { - Label("avx2_loop0") - Comment("at each iteration works with one block (512 bit)") - VMOVDQU(Mem{Base: INP}.Offset(0*32), XTMP0) - VMOVDQU(Mem{Base: INP}.Offset(1*32), XTMP1) - VMOVDQU(Mem{Base: INP}.Offset(2*32), XTMP2) - VMOVDQU(Mem{Base: INP}.Offset(3*32), XTMP3) - - flip_mask := flip_mask_DATA() - - VMOVDQU(flip_mask, BYTE_FLIP_MASK) - - Comment("Apply Byte Flip Mask: LE -> BE") - VPSHUFB(BYTE_FLIP_MASK, XTMP0, XTMP0) - VPSHUFB(BYTE_FLIP_MASK, XTMP1, XTMP1) - VPSHUFB(BYTE_FLIP_MASK, XTMP2, XTMP2) - VPSHUFB(BYTE_FLIP_MASK, XTMP3, XTMP3) - - Comment("Transpose data into high/low parts") - VPERM2I128(Imm(0x20), XTMP2, XTMP0, XDWORD0) // w3, w2, w1, w0 - VPERM2I128(Imm(0x31), XTMP2, XTMP0, XDWORD1) // w7, w6, w5, w4 - VPERM2I128(Imm(0x20), XTMP3, XTMP1, XDWORD2) // w11, w10, w9, w8 - VPERM2I128(Imm(0x31), XTMP3, XTMP1, XDWORD3) // w15, w14, w13, w12 - - K256 := K256_DATA() - LEAQ(K256, TBL) // Loading address of table with round-specific constants -} - -func avx2_last_block_enter() { - Label("avx2_last_block_enter") - ADDQ(Imm(64), INP) - MOVQ(INP, Mem{Base: SP}.Offset(_INP)) - XORQ(SRND, SRND) -} - -// for w0 - w47 -func avx2_loop1() { - Label("avx2_loop1") - - Comment("Do 4 rounds and scheduling") - VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset((0 * 32)), XDWORD0, XFER) - VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+0*32)) - roundAndSchedN0(_XFER+0*32, a, b, c, d, e, f, g, h, XDWORD0, XDWORD1, XDWORD2, XDWORD3) - roundAndSchedN1(_XFER+0*32, h, a, b, c, d, e, f, g, XDWORD0, XDWORD1, XDWORD2, XDWORD3) - roundAndSchedN2(_XFER+0*32, g, h, a, b, c, d, e, f, XDWORD0, XDWORD1, XDWORD2, XDWORD3) - roundAndSchedN3(_XFER+0*32, f, g, h, a, b, c, d, e, XDWORD0, XDWORD1, XDWORD2, XDWORD3) - - Comment("Do 4 rounds and scheduling") - VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset(1*32), XDWORD1, XFER) - VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+1*32)) - roundAndSchedN0(_XFER+1*32, e, f, g, h, a, b, c, d, XDWORD1, XDWORD2, XDWORD3, XDWORD0) - roundAndSchedN1(_XFER+1*32, d, e, f, g, h, a, b, c, XDWORD1, XDWORD2, XDWORD3, XDWORD0) - roundAndSchedN2(_XFER+1*32, c, d, e, f, g, h, a, b, XDWORD1, XDWORD2, XDWORD3, XDWORD0) - roundAndSchedN3(_XFER+1*32, b, c, d, e, f, g, h, a, XDWORD1, XDWORD2, XDWORD3, XDWORD0) - - Comment("Do 4 rounds and scheduling") - VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset((2 * 32)), XDWORD2, XFER) - VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+2*32)) - roundAndSchedN0(_XFER+2*32, a, b, c, d, e, f, g, h, XDWORD2, XDWORD3, XDWORD0, XDWORD1) - roundAndSchedN1(_XFER+2*32, h, a, b, c, d, e, f, g, XDWORD2, XDWORD3, XDWORD0, XDWORD1) - roundAndSchedN2(_XFER+2*32, g, h, a, b, c, d, e, f, XDWORD2, XDWORD3, XDWORD0, XDWORD1) - roundAndSchedN3(_XFER+2*32, f, g, h, a, b, c, d, e, XDWORD2, XDWORD3, XDWORD0, XDWORD1) - - Comment("Do 4 rounds and scheduling") - VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset((3 * 32)), XDWORD3, XFER) - VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+3*32)) - roundAndSchedN0(_XFER+3*32, e, f, g, h, a, b, c, d, XDWORD3, XDWORD0, XDWORD1, XDWORD2) - roundAndSchedN1(_XFER+3*32, d, e, f, g, h, a, b, c, XDWORD3, XDWORD0, XDWORD1, XDWORD2) - roundAndSchedN2(_XFER+3*32, c, d, e, f, g, h, a, b, XDWORD3, XDWORD0, XDWORD1, XDWORD2) - roundAndSchedN3(_XFER+3*32, b, c, d, e, f, g, h, a, XDWORD3, XDWORD0, XDWORD1, XDWORD2) - - ADDQ(Imm(4*32), SRND) - CMPQ(SRND, U32(3*4*32)) - JB(LabelRef("avx2_loop1")) -} - -// w48 - w63 processed with no scheduling (last 16 rounds) -func avx2_loop2() { - Label("avx2_loop2") - VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset(0*32), XDWORD0, XFER) - VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+0*32)) - doRoundN0(_XFER+0*32, a, b, c, d, e, f, g, h, h) - doRoundN1(_XFER+0*32, h, a, b, c, d, e, f, g, h) - doRoundN2(_XFER+0*32, g, h, a, b, c, d, e, f, g) - doRoundN3(_XFER+0*32, f, g, h, a, b, c, d, e, f) - - VPADDD(Mem{Base: TBL, Scale: 1, Index: SRND}.Offset(1*32), XDWORD1, XFER) - VMOVDQU(XFER, Mem{Base: SP, Scale: 1, Index: SRND}.Offset(_XFER+1*32)) - doRoundN0(_XFER+1*32, e, f, g, h, a, b, c, d, e) - doRoundN1(_XFER+1*32, d, e, f, g, h, a, b, c, d) - doRoundN2(_XFER+1*32, c, d, e, f, g, h, a, b, c) - doRoundN3(_XFER+1*32, b, c, d, e, f, g, h, a, b) - - ADDQ(Imm(2*32), SRND) - - VMOVDQU(XDWORD2, XDWORD0) - VMOVDQU(XDWORD3, XDWORD1) - - CMPQ(SRND, U32(4*4*32)) - JB(LabelRef("avx2_loop2")) - - Load(Param("dig"), CTX) // d.h[8] - MOVQ(Mem{Base: SP}.Offset(_INP), INP) - - registers := []GPPhysical{a, b, c, d, e, f, g, h} - for i, reg := range registers { - addm(Mem{Base: CTX}.Offset(i*4), reg) - } - - CMPQ(Mem{Base: SP}.Offset(_INP_END), INP) - JB(LabelRef("done_hash")) - - XORQ(SRND, SRND) -} - -// Do second block using previously scheduled results -func avx2_loop3() { - Label("avx2_loop3") - doRoundN0(_XFER+0*32+16, a, b, c, d, e, f, g, h, a) - doRoundN1(_XFER+0*32+16, h, a, b, c, d, e, f, g, h) - doRoundN2(_XFER+0*32+16, g, h, a, b, c, d, e, f, g) - doRoundN3(_XFER+0*32+16, f, g, h, a, b, c, d, e, f) - - doRoundN0(_XFER+1*32+16, e, f, g, h, a, b, c, d, e) - doRoundN1(_XFER+1*32+16, d, e, f, g, h, a, b, c, d) - doRoundN2(_XFER+1*32+16, c, d, e, f, g, h, a, b, c) - doRoundN3(_XFER+1*32+16, b, c, d, e, f, g, h, a, b) - - ADDQ(Imm(2*32), SRND) - CMPQ(SRND, U32(4*4*32)) - JB(LabelRef("avx2_loop3")) - - Load(Param("dig"), CTX) // d.h[8] - MOVQ(Mem{Base: SP}.Offset(_INP), INP) - ADDQ(Imm(64), INP) - - registers := []GPPhysical{a, b, c, d, e, f, g, h} - for i, reg := range registers { - addm(Mem{Base: CTX}.Offset(i*4), reg) - } - - CMPQ(Mem{Base: SP}.Offset(_INP_END), INP) - JA(LabelRef("avx2_loop0")) - JB(LabelRef("done_hash")) -} - -func avx2_do_last_block() { - Label("avx2_do_last_block") - VMOVDQU(Mem{Base: INP}.Offset(0), XWORD0) - VMOVDQU(Mem{Base: INP}.Offset(16), XWORD1) - VMOVDQU(Mem{Base: INP}.Offset(32), XWORD2) - VMOVDQU(Mem{Base: INP}.Offset(48), XWORD3) - - flip_mask := flip_mask_DATA() - VMOVDQU(flip_mask, BYTE_FLIP_MASK) - - VPSHUFB(X_BYTE_FLIP_MASK, XWORD0, XWORD0) - VPSHUFB(X_BYTE_FLIP_MASK, XWORD1, XWORD1) - VPSHUFB(X_BYTE_FLIP_MASK, XWORD2, XWORD2) - VPSHUFB(X_BYTE_FLIP_MASK, XWORD3, XWORD3) - - K256 := K256_DATA() - LEAQ(K256, TBL) - - JMP(LabelRef("avx2_last_block_enter")) -} - -// Load initial digest -func avx2_only_one_block() { - Label("avx2_only_one_block") - registers := []GPPhysical{a, b, c, d, e, f, g, h} - for i, reg := range registers { - MOVL(Mem{Base: CTX}.Offset(i*4), reg) - } - JMP(LabelRef("avx2_do_last_block")) -} - -func done_hash() { - Label("done_hash") - VZEROUPPER() - RET() -} - -func sha_ni() { - Label("sha_ni") - Load(Param("dig"), digestPtr) // init digest hash vector H0, H1,..., H7 pointer - Load(Param("p").Base(), dataPtr) // init input data base pointer - Load(Param("p").Len(), numBytes) // get number of input bytes to hash - SHRQ(Imm(6), numBytes) // force modulo 64 input buffer length - SHLQ(Imm(6), numBytes) - CMPQ(numBytes, Imm(0)) // exit early for zero-length input buffer - JEQ(LabelRef("done")) - ADDQ(dataPtr, numBytes) // point numBytes to end of input buffer - VMOVDQU(Mem{Base: digestPtr}.Offset(0*16), state0) // load initial hash values and reorder - VMOVDQU(Mem{Base: digestPtr}.Offset(1*16), state1) // DCBA, HGFE -> ABEF, CDGH - PSHUFD(Imm(0xb1), state0, state0) // CDAB - PSHUFD(Imm(0x1b), state1, state1) // EFGH - VMOVDQA(state0, m4) - PALIGNR(Imm(8), state1, state0) // ABEF - PBLENDW(Imm(0xf0), m4, state1) // CDGH - flip_mask := flip_mask_DATA() - VMOVDQA(flip_mask, shufMask) - LEAQ(K256_DATA(), sha256Constants) - - roundLoop() - done() -} - -func roundLoop() { - Label("roundLoop") - Comment("save hash values for addition after rounds") - VMOVDQA(state0, abefSave) - VMOVDQA(state1, cdghSave) - - Comment("do rounds 0-59") - rounds0to11(m0, nil, 0, nop) // 0-3 - rounds0to11(m1, m0, 1, sha256msg1) // 4-7 - rounds0to11(m2, m1, 2, sha256msg1) // 8-11 - VMOVDQU(Mem{Base: dataPtr}.Offset(3*16), msg) - PSHUFB(shufMask, msg) - rounds12to59(m3, 3, m2, m0, sha256msg1, vmovrev) // 12-15 - rounds12to59(m0, 4, m3, m1, sha256msg1, vmov) // 16-19 - rounds12to59(m1, 5, m0, m2, sha256msg1, vmov) // 20-23 - rounds12to59(m2, 6, m1, m3, sha256msg1, vmov) // 24-27 - rounds12to59(m3, 7, m2, m0, sha256msg1, vmov) // 28-31 - rounds12to59(m0, 8, m3, m1, sha256msg1, vmov) // 32-35 - rounds12to59(m1, 9, m0, m2, sha256msg1, vmov) // 36-39 - rounds12to59(m2, 10, m1, m3, sha256msg1, vmov) // 40-43 - rounds12to59(m3, 11, m2, m0, sha256msg1, vmov) // 44-47 - rounds12to59(m0, 12, m3, m1, sha256msg1, vmov) // 48-51 - rounds12to59(m1, 13, m0, m2, nop, vmov) // 52-55 - rounds12to59(m2, 14, m1, m3, nop, vmov) // 56-59 - - Comment("do rounds 60-63") - VMOVDQA(m3, msg) - PADDD(Mem{Base: sha256Constants}.Offset(15*32), msg) - SHA256RNDS2(msg, state0, state1) - PSHUFD(Imm(0x0e), msg, msg) - SHA256RNDS2(msg, state1, state0) - - Comment("add current hash values with previously saved") - PADDD(abefSave, state0) - PADDD(cdghSave, state1) - - Comment("advance data pointer; loop until buffer empty") - ADDQ(Imm(64), dataPtr) - CMPQ(numBytes, dataPtr) - JNE(LabelRef("roundLoop")) - - Comment("write hash values back in the correct order") - PSHUFD(Imm(0x1b), state0, state0) - PSHUFD(Imm(0xb1), state1, state1) - VMOVDQA(state0, m4) - PBLENDW(Imm(0xf0), state1, state0) - PALIGNR(Imm(8), m4, state1) - VMOVDQU(state0, Mem{Base: digestPtr}.Offset(0*16)) - VMOVDQU(state1, Mem{Base: digestPtr}.Offset(1*16)) -} - -func done() { - Label("done") - RET() -} - -/**~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~DATA SECTION~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~**/ - // Pointers for memoizing Data section symbols var flip_maskPtr, shuff_00BAPtr, shuff_DC00Ptr, K256Ptr *Mem @@ -1120,73 +698,6 @@ func shuff_DC00_DATA() Mem { return shuff_DC00 } -var _K = []uint32{ - 0x428a2f98, - 0x71374491, - 0xb5c0fbcf, - 0xe9b5dba5, - 0x3956c25b, - 0x59f111f1, - 0x923f82a4, - 0xab1c5ed5, - 0xd807aa98, - 0x12835b01, - 0x243185be, - 0x550c7dc3, - 0x72be5d74, - 0x80deb1fe, - 0x9bdc06a7, - 0xc19bf174, - 0xe49b69c1, - 0xefbe4786, - 0x0fc19dc6, - 0x240ca1cc, - 0x2de92c6f, - 0x4a7484aa, - 0x5cb0a9dc, - 0x76f988da, - 0x983e5152, - 0xa831c66d, - 0xb00327c8, - 0xbf597fc7, - 0xc6e00bf3, - 0xd5a79147, - 0x06ca6351, - 0x14292967, - 0x27b70a85, - 0x2e1b2138, - 0x4d2c6dfc, - 0x53380d13, - 0x650a7354, - 0x766a0abb, - 0x81c2c92e, - 0x92722c85, - 0xa2bfe8a1, - 0xa81a664b, - 0xc24b8b70, - 0xc76c51a3, - 0xd192e819, - 0xd6990624, - 0xf40e3585, - 0x106aa070, - 0x19a4c116, - 0x1e376c08, - 0x2748774c, - 0x34b0bcb5, - 0x391c0cb3, - 0x4ed8aa4a, - 0x5b9cca4f, - 0x682e6ff3, - 0x748f82ee, - 0x78a5636f, - 0x84c87814, - 0x8cc70208, - 0x90befffa, - 0xa4506ceb, - 0xbef9a3f7, - 0xc67178f2, -} - // Round specific constants func K256_DATA() Mem { if K256Ptr != nil { diff --git a/crypto/internal/fips140/sha256/_asm/sha256block_amd64_shani.go b/crypto/internal/fips140/sha256/_asm/sha256block_amd64_shani.go new file mode 100644 index 00000000000..423e86206fa --- /dev/null +++ b/crypto/internal/fips140/sha256/_asm/sha256block_amd64_shani.go @@ -0,0 +1,174 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + . "github.com/mmcloughlin/avo/build" + . "github.com/mmcloughlin/avo/operand" + . "github.com/mmcloughlin/avo/reg" +) + +// The sha-ni implementation uses Intel(R) SHA extensions SHA256RNDS2, SHA256MSG1, SHA256MSG2 +// It also reuses portions of the flip_mask (half) and K256 table (stride 32) from the avx2 version +// +// Reference +// S. Gulley, et al, "New Instructions Supporting the Secure Hash +// Algorithm on Intel® Architecture Processors", July 2013 +// https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sha-extensions.html + +func blockSHANI() { + Implement("blockSHANI") + Load(Param("dig"), digestPtr) // init digest hash vector H0, H1,..., H7 pointer + Load(Param("p").Base(), dataPtr) // init input data base pointer + Load(Param("p").Len(), numBytes) // get number of input bytes to hash + SHRQ(Imm(6), numBytes) // force modulo 64 input buffer length + SHLQ(Imm(6), numBytes) + CMPQ(numBytes, Imm(0)) // exit early for zero-length input buffer + JEQ(LabelRef("done")) + ADDQ(dataPtr, numBytes) // point numBytes to end of input buffer + VMOVDQU(Mem{Base: digestPtr}.Offset(0*16), state0) // load initial hash values and reorder + VMOVDQU(Mem{Base: digestPtr}.Offset(1*16), state1) // DCBA, HGFE -> ABEF, CDGH + PSHUFD(Imm(0xb1), state0, state0) // CDAB + PSHUFD(Imm(0x1b), state1, state1) // EFGH + VMOVDQA(state0, m4) + PALIGNR(Imm(8), state1, state0) // ABEF + PBLENDW(Imm(0xf0), m4, state1) // CDGH + flip_mask := flip_mask_DATA() + VMOVDQA(flip_mask, shufMask) + LEAQ(K256_DATA(), sha256Constants) + + roundLoop() + done() +} + +func roundLoop() { + Label("roundLoop") + Comment("save hash values for addition after rounds") + VMOVDQA(state0, abefSave) + VMOVDQA(state1, cdghSave) + + Comment("do rounds 0-59") + rounds0to11(m0, nil, 0, nop) // 0-3 + rounds0to11(m1, m0, 1, sha256msg1) // 4-7 + rounds0to11(m2, m1, 2, sha256msg1) // 8-11 + VMOVDQU(Mem{Base: dataPtr}.Offset(3*16), msg) + PSHUFB(shufMask, msg) + rounds12to59(m3, 3, m2, m0, sha256msg1, vmovrev) // 12-15 + rounds12to59(m0, 4, m3, m1, sha256msg1, vmov) // 16-19 + rounds12to59(m1, 5, m0, m2, sha256msg1, vmov) // 20-23 + rounds12to59(m2, 6, m1, m3, sha256msg1, vmov) // 24-27 + rounds12to59(m3, 7, m2, m0, sha256msg1, vmov) // 28-31 + rounds12to59(m0, 8, m3, m1, sha256msg1, vmov) // 32-35 + rounds12to59(m1, 9, m0, m2, sha256msg1, vmov) // 36-39 + rounds12to59(m2, 10, m1, m3, sha256msg1, vmov) // 40-43 + rounds12to59(m3, 11, m2, m0, sha256msg1, vmov) // 44-47 + rounds12to59(m0, 12, m3, m1, sha256msg1, vmov) // 48-51 + rounds12to59(m1, 13, m0, m2, nop, vmov) // 52-55 + rounds12to59(m2, 14, m1, m3, nop, vmov) // 56-59 + + Comment("do rounds 60-63") + VMOVDQA(m3, msg) + PADDD(Mem{Base: sha256Constants}.Offset(15*32), msg) + SHA256RNDS2(msg, state0, state1) + PSHUFD(Imm(0x0e), msg, msg) + SHA256RNDS2(msg, state1, state0) + + Comment("add current hash values with previously saved") + PADDD(abefSave, state0) + PADDD(cdghSave, state1) + + Comment("advance data pointer; loop until buffer empty") + ADDQ(Imm(64), dataPtr) + CMPQ(numBytes, dataPtr) + JNE(LabelRef("roundLoop")) + + Comment("write hash values back in the correct order") + PSHUFD(Imm(0x1b), state0, state0) + PSHUFD(Imm(0xb1), state1, state1) + VMOVDQA(state0, m4) + PBLENDW(Imm(0xf0), state1, state0) + PALIGNR(Imm(8), m4, state1) + VMOVDQU(state0, Mem{Base: digestPtr}.Offset(0*16)) + VMOVDQU(state1, Mem{Base: digestPtr}.Offset(1*16)) +} + +func done() { + Label("done") + RET() +} + +var ( + digestPtr GPPhysical = RDI // input/output, base pointer to digest hash vector H0, H1, ..., H7 + dataPtr = RSI // input, base pointer to first input data block + numBytes = RDX // input, number of input bytes to be processed + sha256Constants = RAX // round contents from K256 table, indexed by round number x 32 + msg VecPhysical = X0 // input data + state0 = X1 // round intermediates and outputs + state1 = X2 + m0 = X3 // m0, m1,... m4 -- round message temps + m1 = X4 + m2 = X5 + m3 = X6 + m4 = X7 + shufMask = X8 // input data endian conversion control mask + abefSave = X9 // digest hash vector inter-block buffer abef + cdghSave = X10 // digest hash vector inter-block buffer cdgh +) + +// nop instead of final SHA256MSG1 for first and last few rounds +func nop(m, a VecPhysical) { +} + +// final SHA256MSG1 for middle rounds that require it +func sha256msg1(m, a VecPhysical) { + SHA256MSG1(m, a) +} + +// msg copy for all but rounds 12-15 +func vmov(a, b VecPhysical) { + VMOVDQA(a, b) +} + +// reverse copy for rounds 12-15 +func vmovrev(a, b VecPhysical) { + VMOVDQA(b, a) +} + +type VecFunc func(a, b VecPhysical) + +// sha rounds 0 to 11 +// +// identical with the exception of the final msg op +// which is replaced with a nop for rounds where it is not needed +// refer to Gulley, et al for more information +func rounds0to11(m, a VecPhysical, c int, sha256msg1 VecFunc) { + VMOVDQU(Mem{Base: dataPtr}.Offset(c*16), msg) + PSHUFB(shufMask, msg) + VMOVDQA(msg, m) + PADDD(Mem{Base: sha256Constants}.Offset(c*32), msg) + SHA256RNDS2(msg, state0, state1) + PSHUFD(U8(0x0e), msg, msg) + SHA256RNDS2(msg, state1, state0) + sha256msg1(m, a) +} + +// sha rounds 12 to 59 +// +// identical with the exception of the final msg op +// and the reverse copy(m,msg) in round 12 which is required +// after the last data load +// refer to Gulley, et al for more information +func rounds12to59(m VecPhysical, c int, a, t VecPhysical, sha256msg1, movop VecFunc) { + movop(m, msg) + PADDD(Mem{Base: sha256Constants}.Offset(c*32), msg) + SHA256RNDS2(msg, state0, state1) + VMOVDQA(m, m4) + PALIGNR(Imm(4), a, m4) + PADDD(m4, t) + SHA256MSG2(m, t) + PSHUFD(Imm(0x0e), msg, msg) + SHA256RNDS2(msg, state1, state0) + sha256msg1(m, a) +} diff --git a/crypto/internal/fips140/sha256/cast.go b/crypto/internal/fips140/sha256/cast.go new file mode 100644 index 00000000000..b7d142a9a6f --- /dev/null +++ b/crypto/internal/fips140/sha256/cast.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha256 + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func init() { + fips140.CAST("SHA2-256", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + want := []byte{ + 0x5d, 0xfb, 0xab, 0xee, 0xdf, 0x31, 0x8b, 0xf3, + 0x3c, 0x09, 0x27, 0xc4, 0x3d, 0x76, 0x30, 0xf5, + 0x1b, 0x82, 0xf3, 0x51, 0x74, 0x03, 0x01, 0x35, + 0x4f, 0xa3, 0xd7, 0xfc, 0x51, 0xf0, 0x13, 0x2e, + } + h := New() + h.Write(input) + if got := h.Sum(nil); !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/sha256/sha256.go b/crypto/internal/fips140/sha256/sha256.go new file mode 100644 index 00000000000..358126d3d4c --- /dev/null +++ b/crypto/internal/fips140/sha256/sha256.go @@ -0,0 +1,232 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha256 implements the SHA-224 and SHA-256 hash algorithms as defined +// in FIPS 180-4. +package sha256 + +import ( + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// The size of a SHA-256 checksum in bytes. +const size = 32 + +// The size of a SHA-224 checksum in bytes. +const size224 = 28 + +// The block size of SHA-256 and SHA-224 in bytes. +const blockSize = 64 + +const ( + chunk = 64 + init0 = 0x6A09E667 + init1 = 0xBB67AE85 + init2 = 0x3C6EF372 + init3 = 0xA54FF53A + init4 = 0x510E527F + init5 = 0x9B05688C + init6 = 0x1F83D9AB + init7 = 0x5BE0CD19 + init0_224 = 0xC1059ED8 + init1_224 = 0x367CD507 + init2_224 = 0x3070DD17 + init3_224 = 0xF70E5939 + init4_224 = 0xFFC00B31 + init5_224 = 0x68581511 + init6_224 = 0x64F98FA7 + init7_224 = 0xBEFA4FA4 +) + +// Digest is a SHA-224 or SHA-256 [hash.Hash] implementation. +type Digest struct { + h [8]uint32 + x [chunk]byte + nx int + len uint64 + is224 bool // mark if this digest is SHA-224 +} + +const ( + magic224 = "sha\x02" + magic256 = "sha\x03" + marshaledSize = len(magic256) + 8*4 + chunk + 8 +) + +func (d *Digest) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *Digest) AppendBinary(b []byte) ([]byte, error) { + if d.is224 { + b = append(b, magic224...) + } else { + b = append(b, magic256...) + } + b = byteorder.BEAppendUint32(b, d.h[0]) + b = byteorder.BEAppendUint32(b, d.h[1]) + b = byteorder.BEAppendUint32(b, d.h[2]) + b = byteorder.BEAppendUint32(b, d.h[3]) + b = byteorder.BEAppendUint32(b, d.h[4]) + b = byteorder.BEAppendUint32(b, d.h[5]) + b = byteorder.BEAppendUint32(b, d.h[6]) + b = byteorder.BEAppendUint32(b, d.h[7]) + b = append(b, d.x[:d.nx]...) + b = append(b, make([]byte, len(d.x)-d.nx)...) + b = byteorder.BEAppendUint64(b, d.len) + return b, nil +} + +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic224) || (d.is224 && string(b[:len(magic224)]) != magic224) || (!d.is224 && string(b[:len(magic256)]) != magic256) { + return errors.New("crypto/sha256: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/sha256: invalid hash state size") + } + b = b[len(magic224):] + b, d.h[0] = consumeUint32(b) + b, d.h[1] = consumeUint32(b) + b, d.h[2] = consumeUint32(b) + b, d.h[3] = consumeUint32(b) + b, d.h[4] = consumeUint32(b) + b, d.h[5] = consumeUint32(b) + b, d.h[6] = consumeUint32(b) + b, d.h[7] = consumeUint32(b) + b = b[copy(d.x[:], b):] + b, d.len = consumeUint64(b) + d.nx = int(d.len % chunk) + return nil +} + +func consumeUint64(b []byte) ([]byte, uint64) { + return b[8:], byteorder.BEUint64(b) +} + +func consumeUint32(b []byte) ([]byte, uint32) { + return b[4:], byteorder.BEUint32(b) +} + +func (d *Digest) Reset() { + if !d.is224 { + d.h[0] = init0 + d.h[1] = init1 + d.h[2] = init2 + d.h[3] = init3 + d.h[4] = init4 + d.h[5] = init5 + d.h[6] = init6 + d.h[7] = init7 + } else { + d.h[0] = init0_224 + d.h[1] = init1_224 + d.h[2] = init2_224 + d.h[3] = init3_224 + d.h[4] = init4_224 + d.h[5] = init5_224 + d.h[6] = init6_224 + d.h[7] = init7_224 + } + d.nx = 0 + d.len = 0 +} + +// New returns a new Digest computing the SHA-256 hash. +func New() *Digest { + d := new(Digest) + d.Reset() + return d +} + +// New224 returns a new Digest computing the SHA-224 hash. +func New224() *Digest { + d := new(Digest) + d.is224 = true + d.Reset() + return d +} + +func (d *Digest) Size() int { + if !d.is224 { + return size + } + return size224 +} + +func (d *Digest) BlockSize() int { return blockSize } + +func (d *Digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx:], p) + d.nx += n + if d.nx == chunk { + block(d, d.x[:]) + d.nx = 0 + } + p = p[n:] + } + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + block(d, p[:n]) + p = p[n:] + } + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d *Digest) Sum(in []byte) []byte { + fips140.RecordApproved() + // Make a copy of d so that caller can keep writing and summing. + d0 := *d + hash := d0.checkSum() + if d0.is224 { + return append(in, hash[:size224]...) + } + return append(in, hash[:]...) +} + +func (d *Digest) checkSum() [size]byte { + len := d.len + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + var tmp [64 + 8]byte // padding + length buffer + tmp[0] = 0x80 + var t uint64 + if len%64 < 56 { + t = 56 - len%64 + } else { + t = 64 + 56 - len%64 + } + + // Length in bits. + len <<= 3 + padlen := tmp[:t+8] + byteorder.BEPutUint64(padlen[t+0:], len) + d.Write(padlen) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [size]byte + + byteorder.BEPutUint32(digest[0:], d.h[0]) + byteorder.BEPutUint32(digest[4:], d.h[1]) + byteorder.BEPutUint32(digest[8:], d.h[2]) + byteorder.BEPutUint32(digest[12:], d.h[3]) + byteorder.BEPutUint32(digest[16:], d.h[4]) + byteorder.BEPutUint32(digest[20:], d.h[5]) + byteorder.BEPutUint32(digest[24:], d.h[6]) + if !d.is224 { + byteorder.BEPutUint32(digest[28:], d.h[7]) + } + + return digest +} diff --git a/crypto/sha256/sha256block.go b/crypto/internal/fips140/sha256/sha256block.go similarity index 97% rename from crypto/sha256/sha256block.go rename to crypto/internal/fips140/sha256/sha256block.go index bd2f9da93ce..55a400e2502 100644 --- a/crypto/sha256/sha256block.go +++ b/crypto/internal/fips140/sha256/sha256block.go @@ -10,7 +10,7 @@ package sha256 import "math/bits" -var _K = []uint32{ +var _K = [...]uint32{ 0x428a2f98, 0x71374491, 0xb5c0fbcf, @@ -77,7 +77,7 @@ var _K = []uint32{ 0xc67178f2, } -func blockGeneric(dig *digest, p []byte) { +func blockGeneric(dig *Digest, p []byte) { var w [64]uint32 h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] for len(p) >= chunk { diff --git a/crypto/sha256/sha256block_386.s b/crypto/internal/fips140/sha256/sha256block_386.s similarity index 100% rename from crypto/sha256/sha256block_386.s rename to crypto/internal/fips140/sha256/sha256block_386.s diff --git a/crypto/internal/fips140/sha256/sha256block_amd64.go b/crypto/internal/fips140/sha256/sha256block_amd64.go new file mode 100644 index 00000000000..6afbfcd08c9 --- /dev/null +++ b/crypto/internal/fips140/sha256/sha256block_amd64.go @@ -0,0 +1,39 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha256 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var useAVX2 = cpu.X86HasAVX && cpu.X86HasAVX2 && cpu.X86HasBMI2 +var useSHANI = cpu.X86HasAVX && cpu.X86HasSHA && cpu.X86HasSSE41 && cpu.X86HasSSSE3 + +func init() { + impl.Register("sha256", "AVX2", &useAVX2) + impl.Register("sha256", "SHA-NI", &useSHANI) +} + +//go:noescape +func blockAMD64(dig *Digest, p []byte) + +//go:noescape +func blockAVX2(dig *Digest, p []byte) + +//go:noescape +func blockSHANI(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if useSHANI { + blockSHANI(dig, p) + } else if useAVX2 { + blockAVX2(dig, p) + } else { + blockAMD64(dig, p) + } +} diff --git a/crypto/sha256/sha256block_amd64.s b/crypto/internal/fips140/sha256/sha256block_amd64.s similarity index 99% rename from crypto/sha256/sha256block_amd64.s rename to crypto/internal/fips140/sha256/sha256block_amd64.s index 700a4eff97e..ce0ad4f8877 100644 --- a/crypto/sha256/sha256block_amd64.s +++ b/crypto/internal/fips140/sha256/sha256block_amd64.s @@ -1,16 +1,11 @@ -// Code generated by command: go run sha256block_amd64_asm.go -out ../sha256block_amd64.s -pkg sha256. DO NOT EDIT. +// Code generated by command: go run sha256block_amd64_asm.go -out ../sha256block_amd64.s. DO NOT EDIT. //go:build !purego #include "textflag.h" -// func block(dig *digest, p []byte) -// Requires: AVX, AVX2, BMI2, SHA, SSE2, SSE4.1, SSSE3 -TEXT ·block(SB), $536-32 - CMPB ·useSHA+0(SB), $0x01 - JE sha_ni - CMPB ·useAVX2+0(SB), $0x01 - JE avx2 +// func blockAMD64(dig *Digest, p []byte) +TEXT ·blockAMD64(SB), $264-32 MOVQ p_base+8(FP), SI MOVQ p_len+16(FP), DX SHRQ $0x06, DX @@ -3495,7 +3490,9 @@ loop: end: RET -avx2: +// func blockAVX2(dig *Digest, p []byte) +// Requires: AVX, AVX2, BMI2 +TEXT ·blockAVX2(SB), $536-32 MOVQ dig+0(FP), SI MOVQ p_base+8(FP), DI MOVQ p_len+16(FP), DX @@ -4627,7 +4624,157 @@ done_hash: VZEROUPPER RET -sha_ni: +DATA flip_mask<>+0(SB)/8, $0x0405060700010203 +DATA flip_mask<>+8(SB)/8, $0x0c0d0e0f08090a0b +DATA flip_mask<>+16(SB)/8, $0x0405060700010203 +DATA flip_mask<>+24(SB)/8, $0x0c0d0e0f08090a0b +GLOBL flip_mask<>(SB), RODATA, $32 + +DATA K256<>+0(SB)/4, $0x428a2f98 +DATA K256<>+4(SB)/4, $0x71374491 +DATA K256<>+8(SB)/4, $0xb5c0fbcf +DATA K256<>+12(SB)/4, $0xe9b5dba5 +DATA K256<>+16(SB)/4, $0x428a2f98 +DATA K256<>+20(SB)/4, $0x71374491 +DATA K256<>+24(SB)/4, $0xb5c0fbcf +DATA K256<>+28(SB)/4, $0xe9b5dba5 +DATA K256<>+32(SB)/4, $0x3956c25b +DATA K256<>+36(SB)/4, $0x59f111f1 +DATA K256<>+40(SB)/4, $0x923f82a4 +DATA K256<>+44(SB)/4, $0xab1c5ed5 +DATA K256<>+48(SB)/4, $0x3956c25b +DATA K256<>+52(SB)/4, $0x59f111f1 +DATA K256<>+56(SB)/4, $0x923f82a4 +DATA K256<>+60(SB)/4, $0xab1c5ed5 +DATA K256<>+64(SB)/4, $0xd807aa98 +DATA K256<>+68(SB)/4, $0x12835b01 +DATA K256<>+72(SB)/4, $0x243185be +DATA K256<>+76(SB)/4, $0x550c7dc3 +DATA K256<>+80(SB)/4, $0xd807aa98 +DATA K256<>+84(SB)/4, $0x12835b01 +DATA K256<>+88(SB)/4, $0x243185be +DATA K256<>+92(SB)/4, $0x550c7dc3 +DATA K256<>+96(SB)/4, $0x72be5d74 +DATA K256<>+100(SB)/4, $0x80deb1fe +DATA K256<>+104(SB)/4, $0x9bdc06a7 +DATA K256<>+108(SB)/4, $0xc19bf174 +DATA K256<>+112(SB)/4, $0x72be5d74 +DATA K256<>+116(SB)/4, $0x80deb1fe +DATA K256<>+120(SB)/4, $0x9bdc06a7 +DATA K256<>+124(SB)/4, $0xc19bf174 +DATA K256<>+128(SB)/4, $0xe49b69c1 +DATA K256<>+132(SB)/4, $0xefbe4786 +DATA K256<>+136(SB)/4, $0x0fc19dc6 +DATA K256<>+140(SB)/4, $0x240ca1cc +DATA K256<>+144(SB)/4, $0xe49b69c1 +DATA K256<>+148(SB)/4, $0xefbe4786 +DATA K256<>+152(SB)/4, $0x0fc19dc6 +DATA K256<>+156(SB)/4, $0x240ca1cc +DATA K256<>+160(SB)/4, $0x2de92c6f +DATA K256<>+164(SB)/4, $0x4a7484aa +DATA K256<>+168(SB)/4, $0x5cb0a9dc +DATA K256<>+172(SB)/4, $0x76f988da +DATA K256<>+176(SB)/4, $0x2de92c6f +DATA K256<>+180(SB)/4, $0x4a7484aa +DATA K256<>+184(SB)/4, $0x5cb0a9dc +DATA K256<>+188(SB)/4, $0x76f988da +DATA K256<>+192(SB)/4, $0x983e5152 +DATA K256<>+196(SB)/4, $0xa831c66d +DATA K256<>+200(SB)/4, $0xb00327c8 +DATA K256<>+204(SB)/4, $0xbf597fc7 +DATA K256<>+208(SB)/4, $0x983e5152 +DATA K256<>+212(SB)/4, $0xa831c66d +DATA K256<>+216(SB)/4, $0xb00327c8 +DATA K256<>+220(SB)/4, $0xbf597fc7 +DATA K256<>+224(SB)/4, $0xc6e00bf3 +DATA K256<>+228(SB)/4, $0xd5a79147 +DATA K256<>+232(SB)/4, $0x06ca6351 +DATA K256<>+236(SB)/4, $0x14292967 +DATA K256<>+240(SB)/4, $0xc6e00bf3 +DATA K256<>+244(SB)/4, $0xd5a79147 +DATA K256<>+248(SB)/4, $0x06ca6351 +DATA K256<>+252(SB)/4, $0x14292967 +DATA K256<>+256(SB)/4, $0x27b70a85 +DATA K256<>+260(SB)/4, $0x2e1b2138 +DATA K256<>+264(SB)/4, $0x4d2c6dfc +DATA K256<>+268(SB)/4, $0x53380d13 +DATA K256<>+272(SB)/4, $0x27b70a85 +DATA K256<>+276(SB)/4, $0x2e1b2138 +DATA K256<>+280(SB)/4, $0x4d2c6dfc +DATA K256<>+284(SB)/4, $0x53380d13 +DATA K256<>+288(SB)/4, $0x650a7354 +DATA K256<>+292(SB)/4, $0x766a0abb +DATA K256<>+296(SB)/4, $0x81c2c92e +DATA K256<>+300(SB)/4, $0x92722c85 +DATA K256<>+304(SB)/4, $0x650a7354 +DATA K256<>+308(SB)/4, $0x766a0abb +DATA K256<>+312(SB)/4, $0x81c2c92e +DATA K256<>+316(SB)/4, $0x92722c85 +DATA K256<>+320(SB)/4, $0xa2bfe8a1 +DATA K256<>+324(SB)/4, $0xa81a664b +DATA K256<>+328(SB)/4, $0xc24b8b70 +DATA K256<>+332(SB)/4, $0xc76c51a3 +DATA K256<>+336(SB)/4, $0xa2bfe8a1 +DATA K256<>+340(SB)/4, $0xa81a664b +DATA K256<>+344(SB)/4, $0xc24b8b70 +DATA K256<>+348(SB)/4, $0xc76c51a3 +DATA K256<>+352(SB)/4, $0xd192e819 +DATA K256<>+356(SB)/4, $0xd6990624 +DATA K256<>+360(SB)/4, $0xf40e3585 +DATA K256<>+364(SB)/4, $0x106aa070 +DATA K256<>+368(SB)/4, $0xd192e819 +DATA K256<>+372(SB)/4, $0xd6990624 +DATA K256<>+376(SB)/4, $0xf40e3585 +DATA K256<>+380(SB)/4, $0x106aa070 +DATA K256<>+384(SB)/4, $0x19a4c116 +DATA K256<>+388(SB)/4, $0x1e376c08 +DATA K256<>+392(SB)/4, $0x2748774c +DATA K256<>+396(SB)/4, $0x34b0bcb5 +DATA K256<>+400(SB)/4, $0x19a4c116 +DATA K256<>+404(SB)/4, $0x1e376c08 +DATA K256<>+408(SB)/4, $0x2748774c +DATA K256<>+412(SB)/4, $0x34b0bcb5 +DATA K256<>+416(SB)/4, $0x391c0cb3 +DATA K256<>+420(SB)/4, $0x4ed8aa4a +DATA K256<>+424(SB)/4, $0x5b9cca4f +DATA K256<>+428(SB)/4, $0x682e6ff3 +DATA K256<>+432(SB)/4, $0x391c0cb3 +DATA K256<>+436(SB)/4, $0x4ed8aa4a +DATA K256<>+440(SB)/4, $0x5b9cca4f +DATA K256<>+444(SB)/4, $0x682e6ff3 +DATA K256<>+448(SB)/4, $0x748f82ee +DATA K256<>+452(SB)/4, $0x78a5636f +DATA K256<>+456(SB)/4, $0x84c87814 +DATA K256<>+460(SB)/4, $0x8cc70208 +DATA K256<>+464(SB)/4, $0x748f82ee +DATA K256<>+468(SB)/4, $0x78a5636f +DATA K256<>+472(SB)/4, $0x84c87814 +DATA K256<>+476(SB)/4, $0x8cc70208 +DATA K256<>+480(SB)/4, $0x90befffa +DATA K256<>+484(SB)/4, $0xa4506ceb +DATA K256<>+488(SB)/4, $0xbef9a3f7 +DATA K256<>+492(SB)/4, $0xc67178f2 +DATA K256<>+496(SB)/4, $0x90befffa +DATA K256<>+500(SB)/4, $0xa4506ceb +DATA K256<>+504(SB)/4, $0xbef9a3f7 +DATA K256<>+508(SB)/4, $0xc67178f2 +GLOBL K256<>(SB), RODATA|NOPTR, $512 + +DATA shuff_00BA<>+0(SB)/8, $0x0b0a090803020100 +DATA shuff_00BA<>+8(SB)/8, $0xffffffffffffffff +DATA shuff_00BA<>+16(SB)/8, $0x0b0a090803020100 +DATA shuff_00BA<>+24(SB)/8, $0xffffffffffffffff +GLOBL shuff_00BA<>(SB), RODATA, $32 + +DATA shuff_DC00<>+0(SB)/8, $0xffffffffffffffff +DATA shuff_DC00<>+8(SB)/8, $0x0b0a090803020100 +DATA shuff_DC00<>+16(SB)/8, $0xffffffffffffffff +DATA shuff_DC00<>+24(SB)/8, $0x0b0a090803020100 +GLOBL shuff_DC00<>(SB), RODATA, $32 + +// func blockSHANI(dig *Digest, p []byte) +// Requires: AVX, SHA, SSE2, SSE4.1, SSSE3 +TEXT ·blockSHANI(SB), $0-32 MOVQ dig+0(FP), DI MOVQ p_base+8(FP), SI MOVQ p_len+16(FP), DX @@ -4823,151 +4970,3 @@ roundLoop: done: RET - -DATA flip_mask<>+0(SB)/8, $0x0405060700010203 -DATA flip_mask<>+8(SB)/8, $0x0c0d0e0f08090a0b -DATA flip_mask<>+16(SB)/8, $0x0405060700010203 -DATA flip_mask<>+24(SB)/8, $0x0c0d0e0f08090a0b -GLOBL flip_mask<>(SB), RODATA, $32 - -DATA K256<>+0(SB)/4, $0x428a2f98 -DATA K256<>+4(SB)/4, $0x71374491 -DATA K256<>+8(SB)/4, $0xb5c0fbcf -DATA K256<>+12(SB)/4, $0xe9b5dba5 -DATA K256<>+16(SB)/4, $0x428a2f98 -DATA K256<>+20(SB)/4, $0x71374491 -DATA K256<>+24(SB)/4, $0xb5c0fbcf -DATA K256<>+28(SB)/4, $0xe9b5dba5 -DATA K256<>+32(SB)/4, $0x3956c25b -DATA K256<>+36(SB)/4, $0x59f111f1 -DATA K256<>+40(SB)/4, $0x923f82a4 -DATA K256<>+44(SB)/4, $0xab1c5ed5 -DATA K256<>+48(SB)/4, $0x3956c25b -DATA K256<>+52(SB)/4, $0x59f111f1 -DATA K256<>+56(SB)/4, $0x923f82a4 -DATA K256<>+60(SB)/4, $0xab1c5ed5 -DATA K256<>+64(SB)/4, $0xd807aa98 -DATA K256<>+68(SB)/4, $0x12835b01 -DATA K256<>+72(SB)/4, $0x243185be -DATA K256<>+76(SB)/4, $0x550c7dc3 -DATA K256<>+80(SB)/4, $0xd807aa98 -DATA K256<>+84(SB)/4, $0x12835b01 -DATA K256<>+88(SB)/4, $0x243185be -DATA K256<>+92(SB)/4, $0x550c7dc3 -DATA K256<>+96(SB)/4, $0x72be5d74 -DATA K256<>+100(SB)/4, $0x80deb1fe -DATA K256<>+104(SB)/4, $0x9bdc06a7 -DATA K256<>+108(SB)/4, $0xc19bf174 -DATA K256<>+112(SB)/4, $0x72be5d74 -DATA K256<>+116(SB)/4, $0x80deb1fe -DATA K256<>+120(SB)/4, $0x9bdc06a7 -DATA K256<>+124(SB)/4, $0xc19bf174 -DATA K256<>+128(SB)/4, $0xe49b69c1 -DATA K256<>+132(SB)/4, $0xefbe4786 -DATA K256<>+136(SB)/4, $0x0fc19dc6 -DATA K256<>+140(SB)/4, $0x240ca1cc -DATA K256<>+144(SB)/4, $0xe49b69c1 -DATA K256<>+148(SB)/4, $0xefbe4786 -DATA K256<>+152(SB)/4, $0x0fc19dc6 -DATA K256<>+156(SB)/4, $0x240ca1cc -DATA K256<>+160(SB)/4, $0x2de92c6f -DATA K256<>+164(SB)/4, $0x4a7484aa -DATA K256<>+168(SB)/4, $0x5cb0a9dc -DATA K256<>+172(SB)/4, $0x76f988da -DATA K256<>+176(SB)/4, $0x2de92c6f -DATA K256<>+180(SB)/4, $0x4a7484aa -DATA K256<>+184(SB)/4, $0x5cb0a9dc -DATA K256<>+188(SB)/4, $0x76f988da -DATA K256<>+192(SB)/4, $0x983e5152 -DATA K256<>+196(SB)/4, $0xa831c66d -DATA K256<>+200(SB)/4, $0xb00327c8 -DATA K256<>+204(SB)/4, $0xbf597fc7 -DATA K256<>+208(SB)/4, $0x983e5152 -DATA K256<>+212(SB)/4, $0xa831c66d -DATA K256<>+216(SB)/4, $0xb00327c8 -DATA K256<>+220(SB)/4, $0xbf597fc7 -DATA K256<>+224(SB)/4, $0xc6e00bf3 -DATA K256<>+228(SB)/4, $0xd5a79147 -DATA K256<>+232(SB)/4, $0x06ca6351 -DATA K256<>+236(SB)/4, $0x14292967 -DATA K256<>+240(SB)/4, $0xc6e00bf3 -DATA K256<>+244(SB)/4, $0xd5a79147 -DATA K256<>+248(SB)/4, $0x06ca6351 -DATA K256<>+252(SB)/4, $0x14292967 -DATA K256<>+256(SB)/4, $0x27b70a85 -DATA K256<>+260(SB)/4, $0x2e1b2138 -DATA K256<>+264(SB)/4, $0x4d2c6dfc -DATA K256<>+268(SB)/4, $0x53380d13 -DATA K256<>+272(SB)/4, $0x27b70a85 -DATA K256<>+276(SB)/4, $0x2e1b2138 -DATA K256<>+280(SB)/4, $0x4d2c6dfc -DATA K256<>+284(SB)/4, $0x53380d13 -DATA K256<>+288(SB)/4, $0x650a7354 -DATA K256<>+292(SB)/4, $0x766a0abb -DATA K256<>+296(SB)/4, $0x81c2c92e -DATA K256<>+300(SB)/4, $0x92722c85 -DATA K256<>+304(SB)/4, $0x650a7354 -DATA K256<>+308(SB)/4, $0x766a0abb -DATA K256<>+312(SB)/4, $0x81c2c92e -DATA K256<>+316(SB)/4, $0x92722c85 -DATA K256<>+320(SB)/4, $0xa2bfe8a1 -DATA K256<>+324(SB)/4, $0xa81a664b -DATA K256<>+328(SB)/4, $0xc24b8b70 -DATA K256<>+332(SB)/4, $0xc76c51a3 -DATA K256<>+336(SB)/4, $0xa2bfe8a1 -DATA K256<>+340(SB)/4, $0xa81a664b -DATA K256<>+344(SB)/4, $0xc24b8b70 -DATA K256<>+348(SB)/4, $0xc76c51a3 -DATA K256<>+352(SB)/4, $0xd192e819 -DATA K256<>+356(SB)/4, $0xd6990624 -DATA K256<>+360(SB)/4, $0xf40e3585 -DATA K256<>+364(SB)/4, $0x106aa070 -DATA K256<>+368(SB)/4, $0xd192e819 -DATA K256<>+372(SB)/4, $0xd6990624 -DATA K256<>+376(SB)/4, $0xf40e3585 -DATA K256<>+380(SB)/4, $0x106aa070 -DATA K256<>+384(SB)/4, $0x19a4c116 -DATA K256<>+388(SB)/4, $0x1e376c08 -DATA K256<>+392(SB)/4, $0x2748774c -DATA K256<>+396(SB)/4, $0x34b0bcb5 -DATA K256<>+400(SB)/4, $0x19a4c116 -DATA K256<>+404(SB)/4, $0x1e376c08 -DATA K256<>+408(SB)/4, $0x2748774c -DATA K256<>+412(SB)/4, $0x34b0bcb5 -DATA K256<>+416(SB)/4, $0x391c0cb3 -DATA K256<>+420(SB)/4, $0x4ed8aa4a -DATA K256<>+424(SB)/4, $0x5b9cca4f -DATA K256<>+428(SB)/4, $0x682e6ff3 -DATA K256<>+432(SB)/4, $0x391c0cb3 -DATA K256<>+436(SB)/4, $0x4ed8aa4a -DATA K256<>+440(SB)/4, $0x5b9cca4f -DATA K256<>+444(SB)/4, $0x682e6ff3 -DATA K256<>+448(SB)/4, $0x748f82ee -DATA K256<>+452(SB)/4, $0x78a5636f -DATA K256<>+456(SB)/4, $0x84c87814 -DATA K256<>+460(SB)/4, $0x8cc70208 -DATA K256<>+464(SB)/4, $0x748f82ee -DATA K256<>+468(SB)/4, $0x78a5636f -DATA K256<>+472(SB)/4, $0x84c87814 -DATA K256<>+476(SB)/4, $0x8cc70208 -DATA K256<>+480(SB)/4, $0x90befffa -DATA K256<>+484(SB)/4, $0xa4506ceb -DATA K256<>+488(SB)/4, $0xbef9a3f7 -DATA K256<>+492(SB)/4, $0xc67178f2 -DATA K256<>+496(SB)/4, $0x90befffa -DATA K256<>+500(SB)/4, $0xa4506ceb -DATA K256<>+504(SB)/4, $0xbef9a3f7 -DATA K256<>+508(SB)/4, $0xc67178f2 -GLOBL K256<>(SB), RODATA|NOPTR, $512 - -DATA shuff_00BA<>+0(SB)/8, $0x0b0a090803020100 -DATA shuff_00BA<>+8(SB)/8, $0xffffffffffffffff -DATA shuff_00BA<>+16(SB)/8, $0x0b0a090803020100 -DATA shuff_00BA<>+24(SB)/8, $0xffffffffffffffff -GLOBL shuff_00BA<>(SB), RODATA, $32 - -DATA shuff_DC00<>+0(SB)/8, $0xffffffffffffffff -DATA shuff_DC00<>+8(SB)/8, $0x0b0a090803020100 -DATA shuff_DC00<>+16(SB)/8, $0xffffffffffffffff -DATA shuff_DC00<>+24(SB)/8, $0x0b0a090803020100 -GLOBL shuff_DC00<>(SB), RODATA, $32 diff --git a/crypto/internal/fips140/sha256/sha256block_arm64.go b/crypto/internal/fips140/sha256/sha256block_arm64.go new file mode 100644 index 00000000000..a4c341a87d6 --- /dev/null +++ b/crypto/internal/fips140/sha256/sha256block_arm64.go @@ -0,0 +1,29 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha256 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var useSHA2 = cpu.ARM64HasSHA2 + +func init() { + impl.Register("sha256", "Armv8.0", &useSHA2) +} + +//go:noescape +func blockSHA2(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if useSHA2 { + blockSHA2(dig, p) + } else { + blockGeneric(dig, p) + } +} diff --git a/crypto/sha256/sha256block_arm64.s b/crypto/internal/fips140/sha256/sha256block_arm64.s similarity index 93% rename from crypto/sha256/sha256block_arm64.s rename to crypto/internal/fips140/sha256/sha256block_arm64.s index 6757310c34e..b4082607990 100644 --- a/crypto/sha256/sha256block_arm64.s +++ b/crypto/internal/fips140/sha256/sha256block_arm64.s @@ -11,12 +11,12 @@ SHA256H2 V9.S4, V8, V3 \ VMOV V2.B16, V8.B16 -// func sha256block(h []uint32, p []byte, k []uint32) -TEXT ·sha256block(SB),NOSPLIT,$0 - MOVD h_base+0(FP), R0 // Hash value first address - MOVD p_base+24(FP), R1 // message first address - MOVD k_base+48(FP), R2 // k constants first address - MOVD p_len+32(FP), R3 // message length +// func blockSHA2(dig *Digest, p []byte) +TEXT ·blockSHA2(SB),NOSPLIT,$0 + MOVD dig+0(FP), R0 // Hash value first address + MOVD p_base+8(FP), R1 // message first address + MOVD p_len+16(FP), R3 // message length + MOVD $·_K+0(SB), R2 // k constants first address VLD1 (R0), [V0.S4, V1.S4] // load h(a,b,c,d,e,f,g,h) VLD1.P 64(R2), [V16.S4, V17.S4, V18.S4, V19.S4] VLD1.P 64(R2), [V20.S4, V21.S4, V22.S4, V23.S4] diff --git a/crypto/sha256/sha256block_decl.go b/crypto/internal/fips140/sha256/sha256block_asm.go similarity index 61% rename from crypto/sha256/sha256block_decl.go rename to crypto/internal/fips140/sha256/sha256block_asm.go index e7930393874..1b157d744d6 100644 --- a/crypto/sha256/sha256block_decl.go +++ b/crypto/internal/fips140/sha256/sha256block_asm.go @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (386 || amd64 || loong64 || ppc64 || ppc64le || riscv64 || s390x) && !purego +//go:build (386 || loong64 || riscv64) && !purego package sha256 //go:noescape -func block(dig *digest, p []byte) +func block(dig *Digest, p []byte) diff --git a/crypto/sha256/sha256block_loong64.s b/crypto/internal/fips140/sha256/sha256block_loong64.s similarity index 99% rename from crypto/sha256/sha256block_loong64.s rename to crypto/internal/fips140/sha256/sha256block_loong64.s index 2a2fbe68337..971ad97ab82 100644 --- a/crypto/sha256/sha256block_loong64.s +++ b/crypto/internal/fips140/sha256/sha256block_loong64.s @@ -141,7 +141,7 @@ // the frame size used for data expansion is 64 bytes. // See the definition of the macro LOAD1 above (4 bytes * 16 entries). // -//func block(dig *digest, p []byte) +//func block(dig *Digest, p []byte) TEXT ·block(SB),NOSPLIT,$64-32 MOVV p_base+8(FP), R5 MOVV p_len+16(FP), R6 diff --git a/crypto/sha256/sha256block_generic.go b/crypto/internal/fips140/sha256/sha256block_noasm.go similarity index 89% rename from crypto/sha256/sha256block_generic.go rename to crypto/internal/fips140/sha256/sha256block_noasm.go index 8ca8401f65a..cc7abf6a382 100644 --- a/crypto/sha256/sha256block_generic.go +++ b/crypto/internal/fips140/sha256/sha256block_noasm.go @@ -6,6 +6,6 @@ package sha256 -func block(dig *digest, p []byte) { +func block(dig *Digest, p []byte) { blockGeneric(dig, p) } diff --git a/crypto/internal/fips140/sha256/sha256block_ppc64x.go b/crypto/internal/fips140/sha256/sha256block_ppc64x.go new file mode 100644 index 00000000000..882e35f1b82 --- /dev/null +++ b/crypto/internal/fips140/sha256/sha256block_ppc64x.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (ppc64 || ppc64le) && !purego + +package sha256 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// The POWER architecture doesn't have a way to turn off SHA-2 support at +// runtime with GODEBUG=cpu.something=off, so introduce a new GODEBUG knob for +// that. It's intentionally only checked at init() time, to avoid the +// performance overhead of checking it on every block. +var ppc64sha2 = godebug.Value("#ppc64sha2") != "off" + +func init() { + impl.Register("sha256", "POWER8", &ppc64sha2) +} + +//go:noescape +func blockPOWER(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if ppc64sha2 { + blockPOWER(dig, p) + } else { + blockGeneric(dig, p) + } +} diff --git a/crypto/sha256/sha256block_ppc64x.s b/crypto/internal/fips140/sha256/sha256block_ppc64x.s similarity index 99% rename from crypto/sha256/sha256block_ppc64x.s rename to crypto/internal/fips140/sha256/sha256block_ppc64x.s index ba8fa623c11..b28f80dcfa2 100644 --- a/crypto/sha256/sha256block_ppc64x.s +++ b/crypto/internal/fips140/sha256/sha256block_ppc64x.s @@ -284,8 +284,8 @@ GLOBL ·kcon(SB), RODATA, $1088 #define VPERMLE(va,vb,vc,vt) #endif -// func block(dig *digest, p []byte) -TEXT ·block(SB),0,$0-32 +// func blockPOWER(dig *Digest, p []byte) +TEXT ·blockPOWER(SB),0,$0-32 MOVD dig+0(FP), CTX MOVD p_base+8(FP), INP MOVD p_len+16(FP), LEN diff --git a/crypto/sha256/sha256block_riscv64.s b/crypto/internal/fips140/sha256/sha256block_riscv64.s similarity index 99% rename from crypto/sha256/sha256block_riscv64.s rename to crypto/internal/fips140/sha256/sha256block_riscv64.s index f31bfb8d53d..847b9699a62 100644 --- a/crypto/sha256/sha256block_riscv64.s +++ b/crypto/internal/fips140/sha256/sha256block_riscv64.s @@ -141,7 +141,7 @@ // Note that 64 bytes of stack space is used as a circular buffer // for the message schedule (4 bytes * 16 entries). // -// func block(dig *digest, p []byte) +// func block(dig *Digest, p []byte) TEXT ·block(SB),0,$64-32 MOV p_base+8(FP), X29 MOV p_len+16(FP), X30 @@ -151,7 +151,7 @@ TEXT ·block(SB),0,$64-32 ADD X29, X30, X28 BEQ X28, X29, end - MOV ·_K(SB), X18 // const table + MOV $·_K(SB), X18 // const table ADD $8, X2, X19 // message schedule MOV dig+0(FP), X20 diff --git a/crypto/internal/fips140/sha256/sha256block_s390x.go b/crypto/internal/fips140/sha256/sha256block_s390x.go new file mode 100644 index 00000000000..b2f22f80049 --- /dev/null +++ b/crypto/internal/fips140/sha256/sha256block_s390x.go @@ -0,0 +1,31 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha256 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var useSHA256 = cpu.S390XHasSHA256 + +func init() { + // CP Assist for Cryptographic Functions (CPACF) + // https://www.ibm.com/docs/en/zos/3.1.0?topic=icsf-cp-assist-cryptographic-functions-cpacf + impl.Register("sha256", "CPACF", &useSHA256) +} + +//go:noescape +func blockS390X(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if useSHA256 { + blockS390X(dig, p) + } else { + blockGeneric(dig, p) + } +} diff --git a/crypto/sha256/sha256block_s390x.s b/crypto/internal/fips140/sha256/sha256block_s390x.s similarity index 73% rename from crypto/sha256/sha256block_s390x.s rename to crypto/internal/fips140/sha256/sha256block_s390x.s index 757d62f5125..06469d68d65 100644 --- a/crypto/sha256/sha256block_s390x.s +++ b/crypto/internal/fips140/sha256/sha256block_s390x.s @@ -6,17 +6,12 @@ #include "textflag.h" -// func block(dig *digest, p []byte) -TEXT ·block(SB), NOSPLIT|NOFRAME, $0-32 - MOVBZ ·useAsm(SB), R4 +// func blockS390X(dig *Digest, p []byte) +TEXT ·blockS390X(SB), NOSPLIT|NOFRAME, $0-32 LMG dig+0(FP), R1, R3 // R2 = &p[0], R3 = len(p) MOVBZ $2, R0 // SHA-256 function code - CMPBEQ R4, $0, generic loop: KIMD R0, R2 // compute intermediate message digest (KIMD) BVS loop // continue if interrupted RET - -generic: - BR ·blockGeneric(SB) diff --git a/crypto/internal/fips140/sha3/_asm/go.mod b/crypto/internal/fips140/sha3/_asm/go.mod new file mode 100644 index 00000000000..39e83acc943 --- /dev/null +++ b/crypto/internal/fips140/sha3/_asm/go.mod @@ -0,0 +1,11 @@ +module sha3/_asm + +go 1.22 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.19.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/tools v0.23.0 // indirect +) diff --git a/crypto/internal/fips140/sha3/_asm/go.sum b/crypto/internal/fips140/sha3/_asm/go.sum new file mode 100644 index 00000000000..9e8f35f70fc --- /dev/null +++ b/crypto/internal/fips140/sha3/_asm/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= +golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= +golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= diff --git a/crypto/internal/fips140/sha3/_asm/keccakf_amd64_asm.go b/crypto/internal/fips140/sha3/_asm/keccakf_amd64_asm.go new file mode 100644 index 00000000000..5e59b11fc87 --- /dev/null +++ b/crypto/internal/fips140/sha3/_asm/keccakf_amd64_asm.go @@ -0,0 +1,443 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This code was translated into a form compatible with 6a from the public +// domain sources at https://github.com/gvanas/KeccakCodePackage + +package main + +import ( + "os" + + . "github.com/mmcloughlin/avo/build" + . "github.com/mmcloughlin/avo/operand" + . "github.com/mmcloughlin/avo/reg" +) + +//go:generate go run . -out ../sha3_amd64.s + +// Round Constants for use in the ι step. +var RoundConstants = [24]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +var ( + // Temporary registers + rT1 GPPhysical = RAX + + // Round vars + rpState = Mem{Base: RDI} + rpStack = Mem{Base: RSP} + + rDa = RBX + rDe = RCX + rDi = RDX + rDo = R8 + rDu = R9 + + rBa = R10 + rBe = R11 + rBi = R12 + rBo = R13 + rBu = R14 + + rCa = RSI + rCe = RBP + rCi = rBi + rCo = rBo + rCu = R15 +) + +const ( + _ba = iota * 8 + _be + _bi + _bo + _bu + _ga + _ge + _gi + _go + _gu + _ka + _ke + _ki + _ko + _ku + _ma + _me + _mi + _mo + _mu + _sa + _se + _si + _so + _su +) + +func main() { + // https://github.com/mmcloughlin/avo/issues/450 + os.Setenv("GOOS", "linux") + os.Setenv("GOARCH", "amd64") + + Package("crypto/internal/fips140/sha3") + ConstraintExpr("!purego") + keccakF1600() + Generate() +} + +func MOVQ_RBI_RCE() { MOVQ(rBi, rCe) } +func XORQ_RT1_RCA() { XORQ(rT1, rCa) } +func XORQ_RT1_RCE() { XORQ(rT1, rCe) } +func XORQ_RBA_RCU() { XORQ(rBa, rCu) } +func XORQ_RBE_RCU() { XORQ(rBe, rCu) } +func XORQ_RDU_RCU() { XORQ(rDu, rCu) } +func XORQ_RDA_RCA() { XORQ(rDa, rCa) } +func XORQ_RDE_RCE() { XORQ(rDe, rCe) } + +type ArgMacro func() + +func mKeccakRound( + iState, oState Mem, + rc U64, + B_RBI_RCE, G_RT1_RCA, G_RT1_RCE, G_RBA_RCU, + K_RT1_RCA, K_RT1_RCE, K_RBA_RCU, M_RT1_RCA, + M_RT1_RCE, M_RBE_RCU, S_RDU_RCU, S_RDA_RCA, + S_RDE_RCE ArgMacro, +) { + Comment("Prepare round") + MOVQ(rCe, rDa) + ROLQ(Imm(1), rDa) + + MOVQ(iState.Offset(_bi), rCi) + XORQ(iState.Offset(_gi), rDi) + XORQ(rCu, rDa) + XORQ(iState.Offset(_ki), rCi) + XORQ(iState.Offset(_mi), rDi) + XORQ(rDi, rCi) + + MOVQ(rCi, rDe) + ROLQ(Imm(1), rDe) + + MOVQ(iState.Offset(_bo), rCo) + XORQ(iState.Offset(_go), rDo) + XORQ(rCa, rDe) + XORQ(iState.Offset(_ko), rCo) + XORQ(iState.Offset(_mo), rDo) + XORQ(rDo, rCo) + + MOVQ(rCo, rDi) + ROLQ(Imm(1), rDi) + + MOVQ(rCu, rDo) + XORQ(rCe, rDi) + ROLQ(Imm(1), rDo) + + MOVQ(rCa, rDu) + XORQ(rCi, rDo) + ROLQ(Imm(1), rDu) + + Comment("Result b") + MOVQ(iState.Offset(_ba), rBa) + MOVQ(iState.Offset(_ge), rBe) + XORQ(rCo, rDu) + MOVQ(iState.Offset(_ki), rBi) + MOVQ(iState.Offset(_mo), rBo) + MOVQ(iState.Offset(_su), rBu) + XORQ(rDe, rBe) + ROLQ(Imm(44), rBe) + XORQ(rDi, rBi) + XORQ(rDa, rBa) + ROLQ(Imm(43), rBi) + + MOVQ(rBe, rCa) + MOVQ(rc, rT1) + ORQ(rBi, rCa) + XORQ(rBa, rT1) + XORQ(rT1, rCa) + MOVQ(rCa, oState.Offset(_ba)) + + XORQ(rDu, rBu) + ROLQ(Imm(14), rBu) + MOVQ(rBa, rCu) + ANDQ(rBe, rCu) + XORQ(rBu, rCu) + MOVQ(rCu, oState.Offset(_bu)) + + XORQ(rDo, rBo) + ROLQ(Imm(21), rBo) + MOVQ(rBo, rT1) + ANDQ(rBu, rT1) + XORQ(rBi, rT1) + MOVQ(rT1, oState.Offset(_bi)) + + NOTQ(rBi) + ORQ(rBa, rBu) + ORQ(rBo, rBi) + XORQ(rBo, rBu) + XORQ(rBe, rBi) + MOVQ(rBu, oState.Offset(_bo)) + MOVQ(rBi, oState.Offset(_be)) + B_RBI_RCE() + + Comment("Result g") + MOVQ(iState.Offset(_gu), rBe) + XORQ(rDu, rBe) + MOVQ(iState.Offset(_ka), rBi) + ROLQ(Imm(20), rBe) + XORQ(rDa, rBi) + ROLQ(Imm(3), rBi) + MOVQ(iState.Offset(_bo), rBa) + MOVQ(rBe, rT1) + ORQ(rBi, rT1) + XORQ(rDo, rBa) + MOVQ(iState.Offset(_me), rBo) + MOVQ(iState.Offset(_si), rBu) + ROLQ(Imm(28), rBa) + XORQ(rBa, rT1) + MOVQ(rT1, oState.Offset(_ga)) + G_RT1_RCA() + + XORQ(rDe, rBo) + ROLQ(Imm(45), rBo) + MOVQ(rBi, rT1) + ANDQ(rBo, rT1) + XORQ(rBe, rT1) + MOVQ(rT1, oState.Offset(_ge)) + G_RT1_RCE() + + XORQ(rDi, rBu) + ROLQ(Imm(61), rBu) + MOVQ(rBu, rT1) + ORQ(rBa, rT1) + XORQ(rBo, rT1) + MOVQ(rT1, oState.Offset(_go)) + + ANDQ(rBe, rBa) + XORQ(rBu, rBa) + MOVQ(rBa, oState.Offset(_gu)) + NOTQ(rBu) + G_RBA_RCU() + + ORQ(rBu, rBo) + XORQ(rBi, rBo) + MOVQ(rBo, oState.Offset(_gi)) + + Comment("Result k") + MOVQ(iState.Offset(_be), rBa) + MOVQ(iState.Offset(_gi), rBe) + MOVQ(iState.Offset(_ko), rBi) + MOVQ(iState.Offset(_mu), rBo) + MOVQ(iState.Offset(_sa), rBu) + XORQ(rDi, rBe) + ROLQ(Imm(6), rBe) + XORQ(rDo, rBi) + ROLQ(Imm(25), rBi) + MOVQ(rBe, rT1) + ORQ(rBi, rT1) + XORQ(rDe, rBa) + ROLQ(Imm(1), rBa) + XORQ(rBa, rT1) + MOVQ(rT1, oState.Offset(_ka)) + K_RT1_RCA() + + XORQ(rDu, rBo) + ROLQ(Imm(8), rBo) + MOVQ(rBi, rT1) + ANDQ(rBo, rT1) + XORQ(rBe, rT1) + MOVQ(rT1, oState.Offset(_ke)) + K_RT1_RCE() + + XORQ(rDa, rBu) + ROLQ(Imm(18), rBu) + NOTQ(rBo) + MOVQ(rBo, rT1) + ANDQ(rBu, rT1) + XORQ(rBi, rT1) + MOVQ(rT1, oState.Offset(_ki)) + + MOVQ(rBu, rT1) + ORQ(rBa, rT1) + XORQ(rBo, rT1) + MOVQ(rT1, oState.Offset(_ko)) + + ANDQ(rBe, rBa) + XORQ(rBu, rBa) + MOVQ(rBa, oState.Offset(_ku)) + K_RBA_RCU() + + Comment("Result m") + MOVQ(iState.Offset(_ga), rBe) + XORQ(rDa, rBe) + MOVQ(iState.Offset(_ke), rBi) + ROLQ(Imm(36), rBe) + XORQ(rDe, rBi) + MOVQ(iState.Offset(_bu), rBa) + ROLQ(Imm(10), rBi) + MOVQ(rBe, rT1) + MOVQ(iState.Offset(_mi), rBo) + ANDQ(rBi, rT1) + XORQ(rDu, rBa) + MOVQ(iState.Offset(_so), rBu) + ROLQ(Imm(27), rBa) + XORQ(rBa, rT1) + MOVQ(rT1, oState.Offset(_ma)) + M_RT1_RCA() + + XORQ(rDi, rBo) + ROLQ(Imm(15), rBo) + MOVQ(rBi, rT1) + ORQ(rBo, rT1) + XORQ(rBe, rT1) + MOVQ(rT1, oState.Offset(_me)) + M_RT1_RCE() + + XORQ(rDo, rBu) + ROLQ(Imm(56), rBu) + NOTQ(rBo) + MOVQ(rBo, rT1) + ORQ(rBu, rT1) + XORQ(rBi, rT1) + MOVQ(rT1, oState.Offset(_mi)) + + ORQ(rBa, rBe) + XORQ(rBu, rBe) + MOVQ(rBe, oState.Offset(_mu)) + + ANDQ(rBa, rBu) + XORQ(rBo, rBu) + MOVQ(rBu, oState.Offset(_mo)) + M_RBE_RCU() + + Comment("Result s") + MOVQ(iState.Offset(_bi), rBa) + MOVQ(iState.Offset(_go), rBe) + MOVQ(iState.Offset(_ku), rBi) + XORQ(rDi, rBa) + MOVQ(iState.Offset(_ma), rBo) + ROLQ(Imm(62), rBa) + XORQ(rDo, rBe) + MOVQ(iState.Offset(_se), rBu) + ROLQ(Imm(55), rBe) + + XORQ(rDu, rBi) + MOVQ(rBa, rDu) + XORQ(rDe, rBu) + ROLQ(Imm(2), rBu) + ANDQ(rBe, rDu) + XORQ(rBu, rDu) + MOVQ(rDu, oState.Offset(_su)) + + ROLQ(Imm(39), rBi) + S_RDU_RCU() + NOTQ(rBe) + XORQ(rDa, rBo) + MOVQ(rBe, rDa) + ANDQ(rBi, rDa) + XORQ(rBa, rDa) + MOVQ(rDa, oState.Offset(_sa)) + S_RDA_RCA() + + ROLQ(Imm(41), rBo) + MOVQ(rBi, rDe) + ORQ(rBo, rDe) + XORQ(rBe, rDe) + MOVQ(rDe, oState.Offset(_se)) + S_RDE_RCE() + + MOVQ(rBo, rDi) + MOVQ(rBu, rDo) + ANDQ(rBu, rDi) + ORQ(rBa, rDo) + XORQ(rBi, rDi) + XORQ(rBo, rDo) + MOVQ(rDi, oState.Offset(_si)) + MOVQ(rDo, oState.Offset(_so)) +} + +// keccakF1600 applies the Keccak permutation to a 1600b-wide +// state represented as a slice of 25 uint64s. +func keccakF1600() { + Implement("keccakF1600") + AllocLocal(200) + + Load(Param("a"), rpState.Base) + + Comment("Convert the user state into an internal state") + NOTQ(rpState.Offset(_be)) + NOTQ(rpState.Offset(_bi)) + NOTQ(rpState.Offset(_go)) + NOTQ(rpState.Offset(_ki)) + NOTQ(rpState.Offset(_mi)) + NOTQ(rpState.Offset(_sa)) + + Comment("Execute the KeccakF permutation") + MOVQ(rpState.Offset(_ba), rCa) + MOVQ(rpState.Offset(_be), rCe) + MOVQ(rpState.Offset(_bu), rCu) + + XORQ(rpState.Offset(_ga), rCa) + XORQ(rpState.Offset(_ge), rCe) + XORQ(rpState.Offset(_gu), rCu) + + XORQ(rpState.Offset(_ka), rCa) + XORQ(rpState.Offset(_ke), rCe) + XORQ(rpState.Offset(_ku), rCu) + + XORQ(rpState.Offset(_ma), rCa) + XORQ(rpState.Offset(_me), rCe) + XORQ(rpState.Offset(_mu), rCu) + + XORQ(rpState.Offset(_sa), rCa) + XORQ(rpState.Offset(_se), rCe) + MOVQ(rpState.Offset(_si), rDi) + MOVQ(rpState.Offset(_so), rDo) + XORQ(rpState.Offset(_su), rCu) + + for i, rc := range RoundConstants[:len(RoundConstants)-1] { + var iState, oState Mem + if i%2 == 0 { + iState, oState = rpState, rpStack + } else { + iState, oState = rpStack, rpState + } + mKeccakRound(iState, oState, U64(rc), MOVQ_RBI_RCE, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBA_RCU, XORQ_RT1_RCA, XORQ_RT1_RCE, XORQ_RBE_RCU, XORQ_RDU_RCU, XORQ_RDA_RCA, XORQ_RDE_RCE) + } + mKeccakRound(rpStack, rpState, U64(RoundConstants[len(RoundConstants)-1]), NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP, NOP) + + Comment("Revert the internal state to the user state") + NOTQ(rpState.Offset(_be)) + NOTQ(rpState.Offset(_bi)) + NOTQ(rpState.Offset(_go)) + NOTQ(rpState.Offset(_ki)) + NOTQ(rpState.Offset(_mi)) + NOTQ(rpState.Offset(_sa)) + + RET() +} diff --git a/crypto/internal/fips140/sha3/cast.go b/crypto/internal/fips140/sha3/cast.go new file mode 100644 index 00000000000..2ac6d4a3ecb --- /dev/null +++ b/crypto/internal/fips140/sha3/cast.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func init() { + fips140.CAST("cSHAKE128", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + want := []byte{ + 0xd2, 0x17, 0x37, 0x39, 0xf6, 0xa1, 0xe4, 0x6e, + 0x81, 0xe5, 0x70, 0xe3, 0x1b, 0x10, 0x4c, 0x82, + 0xc5, 0x48, 0xee, 0xe6, 0x09, 0xf5, 0x89, 0x52, + 0x52, 0xa4, 0x69, 0xd4, 0xd0, 0x76, 0x68, 0x6b, + } + h := NewCShake128(input, input) + h.Write(input) + if got := h.Sum(nil); !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/sha3/hashes.go b/crypto/internal/fips140/sha3/hashes.go new file mode 100644 index 00000000000..da1b9bcf5f8 --- /dev/null +++ b/crypto/internal/fips140/sha3/hashes.go @@ -0,0 +1,59 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +// New224 returns a new Digest computing the SHA3-224 hash. +func New224() *Digest { + return &Digest{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3} +} + +// New256 returns a new Digest computing the SHA3-256 hash. +func New256() *Digest { + return &Digest{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3} +} + +// New384 returns a new Digest computing the SHA3-384 hash. +func New384() *Digest { + return &Digest{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3} +} + +// New512 returns a new Digest computing the SHA3-512 hash. +func New512() *Digest { + return &Digest{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3} +} + +// TODO(fips): do this in the stdlib crypto/sha3 package. +// +// crypto.RegisterHash(crypto.SHA3_224, New224) +// crypto.RegisterHash(crypto.SHA3_256, New256) +// crypto.RegisterHash(crypto.SHA3_384, New384) +// crypto.RegisterHash(crypto.SHA3_512, New512) + +const ( + dsbyteSHA3 = 0b00000110 + dsbyteKeccak = 0b00000001 + dsbyteShake = 0b00011111 + dsbyteCShake = 0b00000100 + + // rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in + // bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits. + rateK256 = (1600 - 256) / 8 + rateK448 = (1600 - 448) / 8 + rateK512 = (1600 - 512) / 8 + rateK768 = (1600 - 768) / 8 + rateK1024 = (1600 - 1024) / 8 +) + +// NewLegacyKeccak256 returns a new Digest computing the legacy, non-standard +// Keccak-256 hash. +func NewLegacyKeccak256() *Digest { + return &Digest{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak} +} + +// NewLegacyKeccak512 returns a new Digest computing the legacy, non-standard +// Keccak-512 hash. +func NewLegacyKeccak512() *Digest { + return &Digest{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak} +} diff --git a/crypto/internal/fips140/sha3/keccakf.go b/crypto/internal/fips140/sha3/keccakf.go new file mode 100644 index 00000000000..0d9e764b465 --- /dev/null +++ b/crypto/internal/fips140/sha3/keccakf.go @@ -0,0 +1,432 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +import ( + "math/bits" + "unsafe" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" +) + +// rc stores the round constants for use in the ι step. +var rc = [24]uint64{ + 0x0000000000000001, + 0x0000000000008082, + 0x800000000000808A, + 0x8000000080008000, + 0x000000000000808B, + 0x0000000080000001, + 0x8000000080008081, + 0x8000000000008009, + 0x000000000000008A, + 0x0000000000000088, + 0x0000000080008009, + 0x000000008000000A, + 0x000000008000808B, + 0x800000000000008B, + 0x8000000000008089, + 0x8000000000008003, + 0x8000000000008002, + 0x8000000000000080, + 0x000000000000800A, + 0x800000008000000A, + 0x8000000080008081, + 0x8000000000008080, + 0x0000000080000001, + 0x8000000080008008, +} + +// keccakF1600Generic applies the Keccak permutation. +func keccakF1600Generic(da *[200]byte) { + var a *[25]uint64 + if cpu.BigEndian { + a = new([25]uint64) + for i := range a { + a[i] = byteorder.LEUint64(da[i*8:]) + } + defer func() { + for i := range a { + byteorder.LEPutUint64(da[i*8:], a[i]) + } + }() + } else { + a = (*[25]uint64)(unsafe.Pointer(da)) + } + + // Implementation translated from Keccak-inplace.c + // in the keccak reference code. + var t, bc0, bc1, bc2, bc3, bc4, d0, d1, d2, d3, d4 uint64 + + for i := 0; i < 24; i += 4 { + // Combines the 5 steps in each round into 2 steps. + // Unrolls 4 rounds per loop and spreads some steps across rounds. + + // Round 1 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[6] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[12] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[18] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[24] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i] + a[6] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[16] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[22] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[3] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[10] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[1] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[7] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[19] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[20] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[11] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[23] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[4] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[5] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[2] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[8] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[14] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[15] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + // Round 2 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[16] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[7] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[23] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[14] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+1] + a[16] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[11] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[2] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[18] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[20] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[6] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[22] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[4] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[15] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[1] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[8] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[24] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[10] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[12] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[3] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[19] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[5] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + // Round 3 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[11] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[22] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[8] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[19] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+2] + a[11] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[1] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[12] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[23] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[15] = bc0 ^ (bc2 &^ bc1) + a[1] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[16] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[2] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[24] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[5] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[6] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[3] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[14] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[20] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[7] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[18] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[4] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[10] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + // Round 4 + bc0 = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20] + bc1 = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21] + bc2 = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22] + bc3 = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23] + bc4 = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24] + d0 = bc4 ^ (bc1<<1 | bc1>>63) + d1 = bc0 ^ (bc2<<1 | bc2>>63) + d2 = bc1 ^ (bc3<<1 | bc3>>63) + d3 = bc2 ^ (bc4<<1 | bc4>>63) + d4 = bc3 ^ (bc0<<1 | bc0>>63) + + bc0 = a[0] ^ d0 + t = a[1] ^ d1 + bc1 = bits.RotateLeft64(t, 44) + t = a[2] ^ d2 + bc2 = bits.RotateLeft64(t, 43) + t = a[3] ^ d3 + bc3 = bits.RotateLeft64(t, 21) + t = a[4] ^ d4 + bc4 = bits.RotateLeft64(t, 14) + a[0] = bc0 ^ (bc2 &^ bc1) ^ rc[i+3] + a[1] = bc1 ^ (bc3 &^ bc2) + a[2] = bc2 ^ (bc4 &^ bc3) + a[3] = bc3 ^ (bc0 &^ bc4) + a[4] = bc4 ^ (bc1 &^ bc0) + + t = a[5] ^ d0 + bc2 = bits.RotateLeft64(t, 3) + t = a[6] ^ d1 + bc3 = bits.RotateLeft64(t, 45) + t = a[7] ^ d2 + bc4 = bits.RotateLeft64(t, 61) + t = a[8] ^ d3 + bc0 = bits.RotateLeft64(t, 28) + t = a[9] ^ d4 + bc1 = bits.RotateLeft64(t, 20) + a[5] = bc0 ^ (bc2 &^ bc1) + a[6] = bc1 ^ (bc3 &^ bc2) + a[7] = bc2 ^ (bc4 &^ bc3) + a[8] = bc3 ^ (bc0 &^ bc4) + a[9] = bc4 ^ (bc1 &^ bc0) + + t = a[10] ^ d0 + bc4 = bits.RotateLeft64(t, 18) + t = a[11] ^ d1 + bc0 = bits.RotateLeft64(t, 1) + t = a[12] ^ d2 + bc1 = bits.RotateLeft64(t, 6) + t = a[13] ^ d3 + bc2 = bits.RotateLeft64(t, 25) + t = a[14] ^ d4 + bc3 = bits.RotateLeft64(t, 8) + a[10] = bc0 ^ (bc2 &^ bc1) + a[11] = bc1 ^ (bc3 &^ bc2) + a[12] = bc2 ^ (bc4 &^ bc3) + a[13] = bc3 ^ (bc0 &^ bc4) + a[14] = bc4 ^ (bc1 &^ bc0) + + t = a[15] ^ d0 + bc1 = bits.RotateLeft64(t, 36) + t = a[16] ^ d1 + bc2 = bits.RotateLeft64(t, 10) + t = a[17] ^ d2 + bc3 = bits.RotateLeft64(t, 15) + t = a[18] ^ d3 + bc4 = bits.RotateLeft64(t, 56) + t = a[19] ^ d4 + bc0 = bits.RotateLeft64(t, 27) + a[15] = bc0 ^ (bc2 &^ bc1) + a[16] = bc1 ^ (bc3 &^ bc2) + a[17] = bc2 ^ (bc4 &^ bc3) + a[18] = bc3 ^ (bc0 &^ bc4) + a[19] = bc4 ^ (bc1 &^ bc0) + + t = a[20] ^ d0 + bc3 = bits.RotateLeft64(t, 41) + t = a[21] ^ d1 + bc4 = bits.RotateLeft64(t, 2) + t = a[22] ^ d2 + bc0 = bits.RotateLeft64(t, 62) + t = a[23] ^ d3 + bc1 = bits.RotateLeft64(t, 55) + t = a[24] ^ d4 + bc2 = bits.RotateLeft64(t, 39) + a[20] = bc0 ^ (bc2 &^ bc1) + a[21] = bc1 ^ (bc3 &^ bc2) + a[22] = bc2 ^ (bc4 &^ bc3) + a[23] = bc3 ^ (bc0 &^ bc4) + a[24] = bc4 ^ (bc1 &^ bc0) + } +} diff --git a/crypto/internal/fips140/sha3/sha3.go b/crypto/internal/fips140/sha3/sha3.go new file mode 100644 index 00000000000..4ac17655247 --- /dev/null +++ b/crypto/internal/fips140/sha3/sha3.go @@ -0,0 +1,236 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha3 implements the SHA-3 fixed-output-length hash functions and +// the SHAKE variable-output-length functions defined by [FIPS 202], as well as +// the cSHAKE extendable-output-length functions defined by [SP 800-185]. +// +// [FIPS 202]: https://doi.org/10.6028/NIST.FIPS.202 +// [SP 800-185]: https://doi.org/10.6028/NIST.SP.800-185 +package sha3 + +import ( + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" +) + +// spongeDirection indicates the direction bytes are flowing through the sponge. +type spongeDirection int + +const ( + // spongeAbsorbing indicates that the sponge is absorbing input. + spongeAbsorbing spongeDirection = iota + // spongeSqueezing indicates that the sponge is being squeezed. + spongeSqueezing +) + +type Digest struct { + a [1600 / 8]byte // main state of the hash + + // a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR + // into before running the permutation. If squeezing, it's the remaining + // output to produce before running the permutation. + n, rate int + + // dsbyte contains the "domain separation" bits and the first bit of + // the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the + // SHA-3 and SHAKE functions by appending bitstrings to the message. + // Using a little-endian bit-ordering convention, these are "01" for SHA-3 + // and "1111" for SHAKE, or 00000010b and 00001111b, respectively. Then the + // padding rule from section 5.1 is applied to pad the message to a multiple + // of the rate, which involves adding a "1" bit, zero or more "0" bits, and + // a final "1" bit. We merge the first "1" bit from the padding into dsbyte, + // giving 00000110b (0x06) and 00011111b (0x1f). + // [1] http://csrc.nist.gov/publications/drafts/fips-202/fips_202_draft.pdf + // "Draft FIPS 202: SHA-3 Standard: Permutation-Based Hash and + // Extendable-Output Functions (May 2014)" + dsbyte byte + + outputLen int // the default output size in bytes + state spongeDirection // whether the sponge is absorbing or squeezing +} + +// BlockSize returns the rate of sponge underlying this hash function. +func (d *Digest) BlockSize() int { return d.rate } + +// Size returns the output size of the hash function in bytes. +func (d *Digest) Size() int { return d.outputLen } + +// Reset resets the Digest to its initial state. +func (d *Digest) Reset() { + // Zero the permutation's state. + for i := range d.a { + d.a[i] = 0 + } + d.state = spongeAbsorbing + d.n = 0 +} + +func (d *Digest) Clone() *Digest { + ret := *d + return &ret +} + +// permute applies the KeccakF-1600 permutation. +func (d *Digest) permute() { + keccakF1600(&d.a) + d.n = 0 +} + +// padAndPermute appends the domain separation bits in dsbyte, applies +// the multi-bitrate 10..1 padding rule, and permutes the state. +func (d *Digest) padAndPermute() { + // Pad with this instance's domain-separator bits. We know that there's + // at least one byte of space in the sponge because, if it were full, + // permute would have been called to empty it. dsbyte also contains the + // first one bit for the padding. See the comment in the state struct. + d.a[d.n] ^= d.dsbyte + // This adds the final one bit for the padding. Because of the way that + // bits are numbered from the LSB upwards, the final bit is the MSB of + // the last byte. + d.a[d.rate-1] ^= 0x80 + // Apply the permutation + d.permute() + d.state = spongeSqueezing +} + +// Write absorbs more data into the hash's state. +func (d *Digest) Write(p []byte) (n int, err error) { return d.write(p) } +func (d *Digest) writeGeneric(p []byte) (n int, err error) { + if d.state != spongeAbsorbing { + panic("sha3: Write after Read") + } + + n = len(p) + + for len(p) > 0 { + x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p) + d.n += x + p = p[x:] + + // If the sponge is full, apply the permutation. + if d.n == d.rate { + d.permute() + } + } + + return +} + +// read squeezes an arbitrary number of bytes from the sponge. +func (d *Digest) readGeneric(out []byte) (n int, err error) { + // If we're still absorbing, pad and apply the permutation. + if d.state == spongeAbsorbing { + d.padAndPermute() + } + + n = len(out) + + // Now, do the squeezing. + for len(out) > 0 { + // Apply the permutation if we've squeezed the sponge dry. + if d.n == d.rate { + d.permute() + } + + x := copy(out, d.a[d.n:d.rate]) + d.n += x + out = out[x:] + } + + return +} + +// Sum appends the current hash to b and returns the resulting slice. +// It does not change the underlying hash state. +func (d *Digest) Sum(b []byte) []byte { + fips140.RecordApproved() + return d.sum(b) +} + +func (d *Digest) sumGeneric(b []byte) []byte { + if d.state != spongeAbsorbing { + panic("sha3: Sum after Read") + } + + // Make a copy of the original hash so that caller can keep writing + // and summing. + dup := d.Clone() + hash := make([]byte, dup.outputLen, 64) // explicit cap to allow stack allocation + dup.read(hash) + return append(b, hash...) +} + +const ( + magicSHA3 = "sha\x08" + magicShake = "sha\x09" + magicCShake = "sha\x0a" + magicKeccak = "sha\x0b" + // magic || rate || main state || n || sponge direction + marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1 +) + +func (d *Digest) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *Digest) AppendBinary(b []byte) ([]byte, error) { + switch d.dsbyte { + case dsbyteSHA3: + b = append(b, magicSHA3...) + case dsbyteShake: + b = append(b, magicShake...) + case dsbyteCShake: + b = append(b, magicCShake...) + case dsbyteKeccak: + b = append(b, magicKeccak...) + default: + panic("unknown dsbyte") + } + // rate is at most 168, and n is at most rate. + b = append(b, byte(d.rate)) + b = append(b, d.a[:]...) + b = append(b, byte(d.n), byte(d.state)) + return b, nil +} + +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) != marshaledSize { + return errors.New("sha3: invalid hash state") + } + + magic := string(b[:len(magicSHA3)]) + b = b[len(magicSHA3):] + switch { + case magic == magicSHA3 && d.dsbyte == dsbyteSHA3: + case magic == magicShake && d.dsbyte == dsbyteShake: + case magic == magicCShake && d.dsbyte == dsbyteCShake: + case magic == magicKeccak && d.dsbyte == dsbyteKeccak: + default: + return errors.New("sha3: invalid hash state identifier") + } + + rate := int(b[0]) + b = b[1:] + if rate != d.rate { + return errors.New("sha3: invalid hash state function") + } + + copy(d.a[:], b) + b = b[len(d.a):] + + n, state := int(b[0]), spongeDirection(b[1]) + if n > d.rate { + return errors.New("sha3: invalid hash state") + } + d.n = n + if state != spongeAbsorbing && state != spongeSqueezing { + return errors.New("sha3: invalid hash state") + } + d.state = state + + return nil +} diff --git a/crypto/internal/fips140/sha3/sha3_amd64.go b/crypto/internal/fips140/sha3/sha3_amd64.go new file mode 100644 index 00000000000..d986e3f7b32 --- /dev/null +++ b/crypto/internal/fips140/sha3/sha3_amd64.go @@ -0,0 +1,20 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha3 + +//go:noescape +func keccakF1600(a *[200]byte) + +func (d *Digest) write(p []byte) (n int, err error) { + return d.writeGeneric(p) +} +func (d *Digest) read(out []byte) (n int, err error) { + return d.readGeneric(out) +} +func (d *Digest) sum(b []byte) []byte { + return d.sumGeneric(b) +} diff --git a/crypto/internal/fips140/sha3/sha3_amd64.s b/crypto/internal/fips140/sha3/sha3_amd64.s new file mode 100644 index 00000000000..3137e2d6cfa --- /dev/null +++ b/crypto/internal/fips140/sha3/sha3_amd64.s @@ -0,0 +1,5419 @@ +// Code generated by command: go run keccakf_amd64_asm.go -out ../sha3_amd64.s. DO NOT EDIT. + +//go:build !purego + +// func keccakF1600(a *[200]byte) +TEXT ·keccakF1600(SB), $200-8 + MOVQ a+0(FP), DI + + // Convert the user state into an internal state + NOTQ 8(DI) + NOTQ 16(DI) + NOTQ 64(DI) + NOTQ 96(DI) + NOTQ 136(DI) + NOTQ 160(DI) + + // Execute the KeccakF permutation + MOVQ (DI), SI + MOVQ 8(DI), BP + MOVQ 32(DI), R15 + XORQ 40(DI), SI + XORQ 48(DI), BP + XORQ 72(DI), R15 + XORQ 80(DI), SI + XORQ 88(DI), BP + XORQ 112(DI), R15 + XORQ 120(DI), SI + XORQ 128(DI), BP + XORQ 152(DI), R15 + XORQ 160(DI), SI + XORQ 168(DI), BP + MOVQ 176(DI), DX + MOVQ 184(DI), R8 + XORQ 192(DI), R15 + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000000000001, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000000008082, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x800000000000808a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008000, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000000000808b, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000080000001, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008081, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008009, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000000000008a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000000000088, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000080008009, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000008000000a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000008000808b, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x800000000000008b, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008089, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008003, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008002, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000000080, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x000000000000800a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x800000008000000a, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008081, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000000008080, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + MOVQ R12, BP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + XORQ R10, R15 + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + XORQ R11, R15 + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(DI), R12 + XORQ 56(DI), DX + XORQ R15, BX + XORQ 96(DI), R12 + XORQ 136(DI), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(DI), R13 + XORQ 64(DI), R8 + XORQ SI, CX + XORQ 104(DI), R13 + XORQ 144(DI), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (DI), R10 + MOVQ 48(DI), R11 + XORQ R13, R9 + MOVQ 96(DI), R12 + MOVQ 144(DI), R13 + MOVQ 192(DI), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x0000000080000001, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (SP) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(SP) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(SP) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(SP) + MOVQ R12, 8(SP) + MOVQ R12, BP + + // Result g + MOVQ 72(DI), R11 + XORQ R9, R11 + MOVQ 80(DI), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(DI), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(DI), R13 + MOVQ 176(DI), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(SP) + XORQ AX, SI + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(SP) + XORQ AX, BP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(SP) + NOTQ R14 + XORQ R10, R15 + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(SP) + + // Result k + MOVQ 8(DI), R10 + MOVQ 56(DI), R11 + MOVQ 104(DI), R12 + MOVQ 152(DI), R13 + MOVQ 160(DI), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(SP) + XORQ AX, SI + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(SP) + XORQ AX, BP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(SP) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(SP) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(SP) + XORQ R10, R15 + + // Result m + MOVQ 40(DI), R11 + XORQ BX, R11 + MOVQ 88(DI), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(DI), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(DI), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(DI), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(SP) + XORQ AX, SI + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(SP) + XORQ AX, BP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(SP) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(SP) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(SP) + XORQ R11, R15 + + // Result s + MOVQ 16(DI), R10 + MOVQ 64(DI), R11 + MOVQ 112(DI), R12 + XORQ DX, R10 + MOVQ 120(DI), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(DI), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(SP) + ROLQ $0x27, R12 + XORQ R9, R15 + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(SP) + XORQ BX, SI + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(SP) + XORQ CX, BP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(SP) + MOVQ R8, 184(SP) + + // Prepare round + MOVQ BP, BX + ROLQ $0x01, BX + MOVQ 16(SP), R12 + XORQ 56(SP), DX + XORQ R15, BX + XORQ 96(SP), R12 + XORQ 136(SP), DX + XORQ DX, R12 + MOVQ R12, CX + ROLQ $0x01, CX + MOVQ 24(SP), R13 + XORQ 64(SP), R8 + XORQ SI, CX + XORQ 104(SP), R13 + XORQ 144(SP), R8 + XORQ R8, R13 + MOVQ R13, DX + ROLQ $0x01, DX + MOVQ R15, R8 + XORQ BP, DX + ROLQ $0x01, R8 + MOVQ SI, R9 + XORQ R12, R8 + ROLQ $0x01, R9 + + // Result b + MOVQ (SP), R10 + MOVQ 48(SP), R11 + XORQ R13, R9 + MOVQ 96(SP), R12 + MOVQ 144(SP), R13 + MOVQ 192(SP), R14 + XORQ CX, R11 + ROLQ $0x2c, R11 + XORQ DX, R12 + XORQ BX, R10 + ROLQ $0x2b, R12 + MOVQ R11, SI + MOVQ $0x8000000080008008, AX + ORQ R12, SI + XORQ R10, AX + XORQ AX, SI + MOVQ SI, (DI) + XORQ R9, R14 + ROLQ $0x0e, R14 + MOVQ R10, R15 + ANDQ R11, R15 + XORQ R14, R15 + MOVQ R15, 32(DI) + XORQ R8, R13 + ROLQ $0x15, R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 16(DI) + NOTQ R12 + ORQ R10, R14 + ORQ R13, R12 + XORQ R13, R14 + XORQ R11, R12 + MOVQ R14, 24(DI) + MOVQ R12, 8(DI) + NOP + + // Result g + MOVQ 72(SP), R11 + XORQ R9, R11 + MOVQ 80(SP), R12 + ROLQ $0x14, R11 + XORQ BX, R12 + ROLQ $0x03, R12 + MOVQ 24(SP), R10 + MOVQ R11, AX + ORQ R12, AX + XORQ R8, R10 + MOVQ 128(SP), R13 + MOVQ 176(SP), R14 + ROLQ $0x1c, R10 + XORQ R10, AX + MOVQ AX, 40(DI) + NOP + XORQ CX, R13 + ROLQ $0x2d, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 48(DI) + NOP + XORQ DX, R14 + ROLQ $0x3d, R14 + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 64(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 72(DI) + NOTQ R14 + NOP + ORQ R14, R13 + XORQ R12, R13 + MOVQ R13, 56(DI) + + // Result k + MOVQ 8(SP), R10 + MOVQ 56(SP), R11 + MOVQ 104(SP), R12 + MOVQ 152(SP), R13 + MOVQ 160(SP), R14 + XORQ DX, R11 + ROLQ $0x06, R11 + XORQ R8, R12 + ROLQ $0x19, R12 + MOVQ R11, AX + ORQ R12, AX + XORQ CX, R10 + ROLQ $0x01, R10 + XORQ R10, AX + MOVQ AX, 80(DI) + NOP + XORQ R9, R13 + ROLQ $0x08, R13 + MOVQ R12, AX + ANDQ R13, AX + XORQ R11, AX + MOVQ AX, 88(DI) + NOP + XORQ BX, R14 + ROLQ $0x12, R14 + NOTQ R13 + MOVQ R13, AX + ANDQ R14, AX + XORQ R12, AX + MOVQ AX, 96(DI) + MOVQ R14, AX + ORQ R10, AX + XORQ R13, AX + MOVQ AX, 104(DI) + ANDQ R11, R10 + XORQ R14, R10 + MOVQ R10, 112(DI) + NOP + + // Result m + MOVQ 40(SP), R11 + XORQ BX, R11 + MOVQ 88(SP), R12 + ROLQ $0x24, R11 + XORQ CX, R12 + MOVQ 32(SP), R10 + ROLQ $0x0a, R12 + MOVQ R11, AX + MOVQ 136(SP), R13 + ANDQ R12, AX + XORQ R9, R10 + MOVQ 184(SP), R14 + ROLQ $0x1b, R10 + XORQ R10, AX + MOVQ AX, 120(DI) + NOP + XORQ DX, R13 + ROLQ $0x0f, R13 + MOVQ R12, AX + ORQ R13, AX + XORQ R11, AX + MOVQ AX, 128(DI) + NOP + XORQ R8, R14 + ROLQ $0x38, R14 + NOTQ R13 + MOVQ R13, AX + ORQ R14, AX + XORQ R12, AX + MOVQ AX, 136(DI) + ORQ R10, R11 + XORQ R14, R11 + MOVQ R11, 152(DI) + ANDQ R10, R14 + XORQ R13, R14 + MOVQ R14, 144(DI) + NOP + + // Result s + MOVQ 16(SP), R10 + MOVQ 64(SP), R11 + MOVQ 112(SP), R12 + XORQ DX, R10 + MOVQ 120(SP), R13 + ROLQ $0x3e, R10 + XORQ R8, R11 + MOVQ 168(SP), R14 + ROLQ $0x37, R11 + XORQ R9, R12 + MOVQ R10, R9 + XORQ CX, R14 + ROLQ $0x02, R14 + ANDQ R11, R9 + XORQ R14, R9 + MOVQ R9, 192(DI) + ROLQ $0x27, R12 + NOP + NOTQ R11 + XORQ BX, R13 + MOVQ R11, BX + ANDQ R12, BX + XORQ R10, BX + MOVQ BX, 160(DI) + NOP + ROLQ $0x29, R13 + MOVQ R12, CX + ORQ R13, CX + XORQ R11, CX + MOVQ CX, 168(DI) + NOP + MOVQ R13, DX + MOVQ R14, R8 + ANDQ R14, DX + ORQ R10, R8 + XORQ R12, DX + XORQ R13, R8 + MOVQ DX, 176(DI) + MOVQ R8, 184(DI) + + // Revert the internal state to the user state + NOTQ 8(DI) + NOTQ 16(DI) + NOTQ 64(DI) + NOTQ 96(DI) + NOTQ 136(DI) + NOTQ 160(DI) + RET diff --git a/crypto/internal/fips140/sha3/sha3_noasm.go b/crypto/internal/fips140/sha3/sha3_noasm.go new file mode 100644 index 00000000000..0bcfc73d020 --- /dev/null +++ b/crypto/internal/fips140/sha3/sha3_noasm.go @@ -0,0 +1,21 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (!amd64 && !s390x) || purego + +package sha3 + +func keccakF1600(a *[200]byte) { + keccakF1600Generic(a) +} + +func (d *Digest) write(p []byte) (n int, err error) { + return d.writeGeneric(p) +} +func (d *Digest) read(out []byte) (n int, err error) { + return d.readGeneric(out) +} +func (d *Digest) sum(b []byte) []byte { + return d.sumGeneric(b) +} diff --git a/crypto/internal/fips140/sha3/sha3_s390x.go b/crypto/internal/fips140/sha3/sha3_s390x.go new file mode 100644 index 00000000000..fa4e8c4ceac --- /dev/null +++ b/crypto/internal/fips140/sha3/sha3_s390x.go @@ -0,0 +1,196 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha3 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// This file contains code for using the 'compute intermediate +// message digest' (KIMD) and 'compute last message digest' (KLMD) +// instructions to compute SHA-3 and SHAKE hashes on IBM Z. See +// [z/Architecture Principles of Operation, Fourteen Edition]. +// +// [z/Architecture Principles of Operation, Fourteen Edition]: https://www.ibm.com/docs/en/module_1678991624569/pdf/SA22-7832-13.pdf + +var useSHA3 = cpu.S390XHasSHA3 + +func init() { + // CP Assist for Cryptographic Functions (CPACF) + impl.Register("sha3", "CPACF", &useSHA3) +} + +func keccakF1600(a *[200]byte) { + keccakF1600Generic(a) +} + +// codes represent 7-bit KIMD/KLMD function codes as defined in +// the Principles of Operation. +type code uint64 + +const ( + // Function codes for KIMD/KLMD, from Figure 7-207. + sha3_224 code = 32 + sha3_256 code = 33 + sha3_384 code = 34 + sha3_512 code = 35 + shake_128 code = 36 + shake_256 code = 37 + nopad = 0x100 +) + +// kimd is a wrapper for the 'compute intermediate message digest' instruction. +// src is absorbed into the sponge state a. +// len(src) must be a multiple of the rate for the given function code. +// +//go:noescape +func kimd(function code, a *[200]byte, src []byte) + +// klmd is a wrapper for the 'compute last message digest' instruction. +// src is padded and absorbed into the sponge state a. +// +// If the function is a SHAKE XOF, the sponge is then optionally squeezed into +// dst by first applying the permutation and then copying the output until dst +// runs out. If len(dst) is a multiple of rate (including zero), the final +// permutation is not applied. If the nopad bit of function is set and len(src) +// is zero, only squeezing is performed. +// +//go:noescape +func klmd(function code, a *[200]byte, dst, src []byte) + +func (d *Digest) write(p []byte) (n int, err error) { + if d.state != spongeAbsorbing { + panic("sha3: Write after Read") + } + if !useSHA3 { + return d.writeGeneric(p) + } + + n = len(p) + + // If there is buffered input in the state, keep XOR'ing. + if d.n > 0 { + x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p) + d.n += x + p = p[x:] + } + + // If the sponge is full, apply the permutation. + if d.n == d.rate { + // Absorbing a "rate"ful of zeroes effectively XORs the state with + // zeroes (a no-op) and then runs the permutation. The actual function + // doesn't matter, they all run the same permutation. + kimd(shake_128, &d.a, make([]byte, rateK256)) + d.n = 0 + } + + // Absorb full blocks with KIMD. + if len(p) >= d.rate { + wholeBlocks := len(p) / d.rate * d.rate + kimd(d.function(), &d.a, p[:wholeBlocks]) + p = p[wholeBlocks:] + } + + // If there is any trailing input, XOR it into the state. + if len(p) > 0 { + d.n += subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p) + } + + return +} + +func (d *Digest) sum(b []byte) []byte { + if d.state != spongeAbsorbing { + panic("sha3: Sum after Read") + } + if !useSHA3 || d.dsbyte != dsbyteSHA3 && d.dsbyte != dsbyteShake { + return d.sumGeneric(b) + } + + // Copy the state to preserve the original. + a := d.a + + // We "absorb" a buffer of zeroes as long as the amount of input we already + // XOR'd into the sponge, to skip over it. The max cap is specified to avoid + // an allocation. + buf := make([]byte, d.n, rateK256) + function := d.function() + switch function { + case sha3_224, sha3_256, sha3_384, sha3_512: + klmd(function, &a, nil, buf) + return append(b, a[:d.outputLen]...) + case shake_128, shake_256: + h := make([]byte, d.outputLen, 64) + klmd(function, &a, h, buf) + return append(b, h...) + default: + panic("sha3: unknown function") + } +} + +func (d *Digest) read(out []byte) (n int, err error) { + if !useSHA3 || d.dsbyte != dsbyteShake { + return d.readGeneric(out) + } + + n = len(out) + + if d.state == spongeAbsorbing { + d.state = spongeSqueezing + + // We "absorb" a buffer of zeroes as long as the amount of input we + // already XOR'd into the sponge, to skip over it. The max cap is + // specified to avoid an allocation. + buf := make([]byte, d.n, rateK256) + klmd(d.function(), &d.a, out, buf) + } else { + // We have "buffered" output still to copy. + if d.n < d.rate { + x := copy(out, d.a[d.n:d.rate]) + d.n += x + out = out[x:] + } + if len(out) == 0 { + return + } + + klmd(d.function()|nopad, &d.a, out, nil) + } + + if len(out)%d.rate == 0 { + // The final permutation was not performed, + // so there is no "buffered" output. + d.n = d.rate + } else { + d.n = len(out) % d.rate + } + + return +} + +func (d *Digest) function() code { + switch d.rate { + case rateK256: + return shake_128 + case rateK448: + return sha3_224 + case rateK512: + if d.dsbyte == dsbyteSHA3 { + return sha3_256 + } else { + return shake_256 + } + case rateK768: + return sha3_384 + case rateK1024: + return sha3_512 + default: + panic("invalid rate") + } +} diff --git a/crypto/internal/fips140/sha3/sha3_s390x.s b/crypto/internal/fips140/sha3/sha3_s390x.s new file mode 100644 index 00000000000..c3944da628e --- /dev/null +++ b/crypto/internal/fips140/sha3/sha3_s390x.s @@ -0,0 +1,32 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +// func kimd(function code, a *[200]byte, src []byte) +TEXT ·kimd(SB), NOFRAME|NOSPLIT, $0-40 + MOVD function+0(FP), R0 + MOVD a+8(FP), R1 + LMG src+16(FP), R2, R3 // R2=base, R3=len + +continue: + WORD $0xB93E0002 // KIMD --, R2 + BVS continue // continue if interrupted + MOVD $0, R0 // reset R0 for pre-go1.8 compilers + RET + +// func klmd(function code, a *[200]byte, dst, src []byte) +TEXT ·klmd(SB), NOFRAME|NOSPLIT, $0-64 + MOVD function+0(FP), R0 + MOVD a+8(FP), R1 + LMG dst+16(FP), R2, R3 // R2=base, R3=len + LMG src+40(FP), R4, R5 // R4=base, R5=len + +continue: + WORD $0xB93F0024 // KLMD R2, R4 + BVS continue // continue if interrupted + MOVD $0, R0 // reset R0 for pre-go1.8 compilers + RET diff --git a/crypto/internal/fips140/sha3/shake.go b/crypto/internal/fips140/sha3/shake.go new file mode 100644 index 00000000000..91e19d07230 --- /dev/null +++ b/crypto/internal/fips140/sha3/shake.go @@ -0,0 +1,152 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3 + +import ( + "bytes" + "errors" + "math/bits" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +type SHAKE struct { + d Digest // SHA-3 state context and Read/Write operations + + // initBlock is the cSHAKE specific initialization set of bytes. It is initialized + // by newCShake function and stores concatenation of N followed by S, encoded + // by the method specified in 3.3 of [1]. + // It is stored here in order for Reset() to be able to put context into + // initial state. + initBlock []byte +} + +func bytepad(data []byte, rate int) []byte { + out := make([]byte, 0, 9+len(data)+rate-1) + out = append(out, leftEncode(uint64(rate))...) + out = append(out, data...) + if padlen := rate - len(out)%rate; padlen < rate { + out = append(out, make([]byte, padlen)...) + } + return out +} + +func leftEncode(x uint64) []byte { + // Let n be the smallest positive integer for which 2^(8n) > x. + n := (bits.Len64(x) + 7) / 8 + if n == 0 { + n = 1 + } + // Return n || x with n as a byte and x an n bytes in big-endian order. + b := make([]byte, 9) + byteorder.BEPutUint64(b[1:], x) + b = b[9-n-1:] + b[0] = byte(n) + return b +} + +func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) *SHAKE { + c := &SHAKE{d: Digest{rate: rate, outputLen: outputLen, dsbyte: dsbyte}} + c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes + c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...) + c.initBlock = append(c.initBlock, N...) + c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...) + c.initBlock = append(c.initBlock, S...) + c.Write(bytepad(c.initBlock, c.d.rate)) + return c +} + +func (s *SHAKE) BlockSize() int { return s.d.BlockSize() } +func (s *SHAKE) Size() int { return s.d.Size() } + +// Sum appends a portion of output to b and returns the resulting slice. The +// output length is selected to provide full-strength generic security: 32 bytes +// for SHAKE128 and 64 bytes for SHAKE256. It does not change the underlying +// state. It panics if any output has already been read. +func (s *SHAKE) Sum(in []byte) []byte { return s.d.Sum(in) } + +// Write absorbs more data into the hash's state. +// It panics if any output has already been read. +func (s *SHAKE) Write(p []byte) (n int, err error) { return s.d.Write(p) } + +func (s *SHAKE) Read(out []byte) (n int, err error) { + fips140.RecordApproved() + // Note that read is not exposed on Digest since SHA-3 does not offer + // variable output length. It is only used internally by Sum. + return s.d.read(out) +} + +// Reset resets the hash to initial state. +func (s *SHAKE) Reset() { + s.d.Reset() + if len(s.initBlock) != 0 { + s.Write(bytepad(s.initBlock, s.d.rate)) + } +} + +// Clone returns a copy of the SHAKE context in its current state. +func (s *SHAKE) Clone() *SHAKE { + ret := *s + return &ret +} + +func (s *SHAKE) MarshalBinary() ([]byte, error) { + return s.AppendBinary(make([]byte, 0, marshaledSize+len(s.initBlock))) +} + +func (s *SHAKE) AppendBinary(b []byte) ([]byte, error) { + b, err := s.d.AppendBinary(b) + if err != nil { + return nil, err + } + b = append(b, s.initBlock...) + return b, nil +} + +func (s *SHAKE) UnmarshalBinary(b []byte) error { + if len(b) < marshaledSize { + return errors.New("sha3: invalid hash state") + } + if err := s.d.UnmarshalBinary(b[:marshaledSize]); err != nil { + return err + } + s.initBlock = bytes.Clone(b[marshaledSize:]) + return nil +} + +// NewShake128 creates a new SHAKE128 XOF. +func NewShake128() *SHAKE { + return &SHAKE{d: Digest{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}} +} + +// NewShake256 creates a new SHAKE256 XOF. +func NewShake256() *SHAKE { + return &SHAKE{d: Digest{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}} +} + +// NewCShake128 creates a new cSHAKE128 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewShake128. +func NewCShake128(N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + return NewShake128() + } + return newCShake(N, S, rateK256, 32, dsbyteCShake) +} + +// NewCShake256 creates a new cSHAKE256 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewShake256. +func NewCShake256(N, S []byte) *SHAKE { + if len(N) == 0 && len(S) == 0 { + return NewShake256() + } + return newCShake(N, S, rateK512, 64, dsbyteCShake) +} diff --git a/crypto/internal/fips140/sha512/_asm/go.mod b/crypto/internal/fips140/sha512/_asm/go.mod new file mode 100644 index 00000000000..78b953258b6 --- /dev/null +++ b/crypto/internal/fips140/sha512/_asm/go.mod @@ -0,0 +1,11 @@ +module crypto/sha512/_asm + +go 1.24 + +require github.com/mmcloughlin/avo v0.6.0 + +require ( + golang.org/x/mod v0.20.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.24.0 // indirect +) diff --git a/crypto/internal/fips140/sha512/_asm/go.sum b/crypto/internal/fips140/sha512/_asm/go.sum new file mode 100644 index 00000000000..76af484b2eb --- /dev/null +++ b/crypto/internal/fips140/sha512/_asm/go.sum @@ -0,0 +1,8 @@ +github.com/mmcloughlin/avo v0.6.0 h1:QH6FU8SKoTLaVs80GA8TJuLNkUYl4VokHKlPhVDg4YY= +github.com/mmcloughlin/avo v0.6.0/go.mod h1:8CoAGaCSYXtCPR+8y18Y9aB/kxb8JSS6FRI7mSkvD+8= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= diff --git a/crypto/sha512/_asm/sha512block_amd64_asm.go b/crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go similarity index 99% rename from crypto/sha512/_asm/sha512block_amd64_asm.go rename to crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go index f8f8b737b07..ed7b1766bf4 100644 --- a/crypto/sha512/_asm/sha512block_amd64_asm.go +++ b/crypto/internal/fips140/sha512/_asm/sha512block_amd64_asm.go @@ -5,12 +5,14 @@ package main import ( + "os" + . "github.com/mmcloughlin/avo/build" . "github.com/mmcloughlin/avo/operand" . "github.com/mmcloughlin/avo/reg" ) -//go:generate go run . -out ../sha512block_amd64.s -pkg sha512 +//go:generate go run . -out ../sha512block_amd64.s // SHA512 block routine. See sha512block.go for Go equivalent. // @@ -138,7 +140,11 @@ var _K = []uint64{ } func main() { - Package("github.com/runZeroInc/excrypto/crypto/sha512") + // https://github.com/mmcloughlin/avo/issues/450 + os.Setenv("GOOS", "linux") + os.Setenv("GOARCH", "amd64") + + Package("crypto/internal/fips140/sha512") ConstraintExpr("!purego") blockAMD64() blockAVX2() @@ -432,7 +438,7 @@ func blockAVX2() { func loop0() { Label("loop0") - _K := NewDataAddr(Symbol{Name: ThatPeskyUnicodeDot + "_K"}, 0) + _K := NewDataAddr(Symbol{Name: "$" + ThatPeskyUnicodeDot + "_K"}, 0) MOVQ(_K, RBP) // byte swap first 16 dwords diff --git a/crypto/internal/fips140/sha512/cast.go b/crypto/internal/fips140/sha512/cast.go new file mode 100644 index 00000000000..b8c9fdf8b82 --- /dev/null +++ b/crypto/internal/fips140/sha512/cast.go @@ -0,0 +1,37 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha512 + +import ( + "bytes" + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func init() { + fips140.CAST("SHA2-512", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + want := []byte{ + 0xb4, 0xc4, 0xe0, 0x46, 0x82, 0x6b, 0xd2, 0x61, + 0x90, 0xd0, 0x97, 0x15, 0xfc, 0x31, 0xf4, 0xe6, + 0xa7, 0x28, 0x20, 0x4e, 0xad, 0xd1, 0x12, 0x90, + 0x5b, 0x08, 0xb1, 0x4b, 0x7f, 0x15, 0xc4, 0xf3, + 0x8e, 0x29, 0xb2, 0xfc, 0x54, 0x26, 0x5a, 0x12, + 0x63, 0x26, 0xc5, 0xbd, 0xea, 0x66, 0xc1, 0xb0, + 0x8e, 0x9e, 0x47, 0x72, 0x3b, 0x2d, 0x70, 0x06, + 0x5a, 0xc1, 0x26, 0x2e, 0xcc, 0x37, 0xbf, 0xb1, + } + h := New() + h.Write(input) + if got := h.Sum(nil); !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/sha512/sha512.go b/crypto/internal/fips140/sha512/sha512.go new file mode 100644 index 00000000000..eab2aab9da0 --- /dev/null +++ b/crypto/internal/fips140/sha512/sha512.go @@ -0,0 +1,302 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha512 implements the SHA-384, SHA-512, SHA-512/224, and SHA-512/256 +// hash algorithms as defined in FIPS 180-4. +package sha512 + +import ( + "errors" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +const ( + // size512 is the size, in bytes, of a SHA-512 checksum. + size512 = 64 + + // size224 is the size, in bytes, of a SHA-512/224 checksum. + size224 = 28 + + // size256 is the size, in bytes, of a SHA-512/256 checksum. + size256 = 32 + + // size384 is the size, in bytes, of a SHA-384 checksum. + size384 = 48 + + // blockSize is the block size, in bytes, of the SHA-512/224, + // SHA-512/256, SHA-384 and SHA-512 hash functions. + blockSize = 128 +) + +const ( + chunk = 128 + init0 = 0x6a09e667f3bcc908 + init1 = 0xbb67ae8584caa73b + init2 = 0x3c6ef372fe94f82b + init3 = 0xa54ff53a5f1d36f1 + init4 = 0x510e527fade682d1 + init5 = 0x9b05688c2b3e6c1f + init6 = 0x1f83d9abfb41bd6b + init7 = 0x5be0cd19137e2179 + init0_224 = 0x8c3d37c819544da2 + init1_224 = 0x73e1996689dcd4d6 + init2_224 = 0x1dfab7ae32ff9c82 + init3_224 = 0x679dd514582f9fcf + init4_224 = 0x0f6d2b697bd44da8 + init5_224 = 0x77e36f7304c48942 + init6_224 = 0x3f9d85a86a1d36c8 + init7_224 = 0x1112e6ad91d692a1 + init0_256 = 0x22312194fc2bf72c + init1_256 = 0x9f555fa3c84c64c2 + init2_256 = 0x2393b86b6f53b151 + init3_256 = 0x963877195940eabd + init4_256 = 0x96283ee2a88effe3 + init5_256 = 0xbe5e1e2553863992 + init6_256 = 0x2b0199fc2c85b8aa + init7_256 = 0x0eb72ddc81c52ca2 + init0_384 = 0xcbbb9d5dc1059ed8 + init1_384 = 0x629a292a367cd507 + init2_384 = 0x9159015a3070dd17 + init3_384 = 0x152fecd8f70e5939 + init4_384 = 0x67332667ffc00b31 + init5_384 = 0x8eb44a8768581511 + init6_384 = 0xdb0c2e0d64f98fa7 + init7_384 = 0x47b5481dbefa4fa4 +) + +// Digest is a SHA-384, SHA-512, SHA-512/224, or SHA-512/256 [hash.Hash] +// implementation. +type Digest struct { + h [8]uint64 + x [chunk]byte + nx int + len uint64 + size int // size224, size256, size384, or size512 +} + +func (d *Digest) Reset() { + switch d.size { + case size384: + d.h[0] = init0_384 + d.h[1] = init1_384 + d.h[2] = init2_384 + d.h[3] = init3_384 + d.h[4] = init4_384 + d.h[5] = init5_384 + d.h[6] = init6_384 + d.h[7] = init7_384 + case size224: + d.h[0] = init0_224 + d.h[1] = init1_224 + d.h[2] = init2_224 + d.h[3] = init3_224 + d.h[4] = init4_224 + d.h[5] = init5_224 + d.h[6] = init6_224 + d.h[7] = init7_224 + case size256: + d.h[0] = init0_256 + d.h[1] = init1_256 + d.h[2] = init2_256 + d.h[3] = init3_256 + d.h[4] = init4_256 + d.h[5] = init5_256 + d.h[6] = init6_256 + d.h[7] = init7_256 + case size512: + d.h[0] = init0 + d.h[1] = init1 + d.h[2] = init2 + d.h[3] = init3 + d.h[4] = init4 + d.h[5] = init5 + d.h[6] = init6 + d.h[7] = init7 + default: + panic("unknown size") + } + d.nx = 0 + d.len = 0 +} + +const ( + magic384 = "sha\x04" + magic512_224 = "sha\x05" + magic512_256 = "sha\x06" + magic512 = "sha\x07" + marshaledSize = len(magic512) + 8*8 + chunk + 8 +) + +func (d *Digest) MarshalBinary() ([]byte, error) { + return d.AppendBinary(make([]byte, 0, marshaledSize)) +} + +func (d *Digest) AppendBinary(b []byte) ([]byte, error) { + switch d.size { + case size384: + b = append(b, magic384...) + case size224: + b = append(b, magic512_224...) + case size256: + b = append(b, magic512_256...) + case size512: + b = append(b, magic512...) + default: + panic("unknown size") + } + b = byteorder.BEAppendUint64(b, d.h[0]) + b = byteorder.BEAppendUint64(b, d.h[1]) + b = byteorder.BEAppendUint64(b, d.h[2]) + b = byteorder.BEAppendUint64(b, d.h[3]) + b = byteorder.BEAppendUint64(b, d.h[4]) + b = byteorder.BEAppendUint64(b, d.h[5]) + b = byteorder.BEAppendUint64(b, d.h[6]) + b = byteorder.BEAppendUint64(b, d.h[7]) + b = append(b, d.x[:d.nx]...) + b = append(b, make([]byte, len(d.x)-d.nx)...) + b = byteorder.BEAppendUint64(b, d.len) + return b, nil +} + +func (d *Digest) UnmarshalBinary(b []byte) error { + if len(b) < len(magic512) { + return errors.New("crypto/sha512: invalid hash state identifier") + } + switch { + case d.size == size384 && string(b[:len(magic384)]) == magic384: + case d.size == size224 && string(b[:len(magic512_224)]) == magic512_224: + case d.size == size256 && string(b[:len(magic512_256)]) == magic512_256: + case d.size == size512 && string(b[:len(magic512)]) == magic512: + default: + return errors.New("crypto/sha512: invalid hash state identifier") + } + if len(b) != marshaledSize { + return errors.New("crypto/sha512: invalid hash state size") + } + b = b[len(magic512):] + b, d.h[0] = consumeUint64(b) + b, d.h[1] = consumeUint64(b) + b, d.h[2] = consumeUint64(b) + b, d.h[3] = consumeUint64(b) + b, d.h[4] = consumeUint64(b) + b, d.h[5] = consumeUint64(b) + b, d.h[6] = consumeUint64(b) + b, d.h[7] = consumeUint64(b) + b = b[copy(d.x[:], b):] + b, d.len = consumeUint64(b) + d.nx = int(d.len % chunk) + return nil +} + +func consumeUint64(b []byte) ([]byte, uint64) { + return b[8:], byteorder.BEUint64(b) +} + +// New returns a new Digest computing the SHA-512 hash. +func New() *Digest { + d := &Digest{size: size512} + d.Reset() + return d +} + +// New512_224 returns a new Digest computing the SHA-512/224 hash. +func New512_224() *Digest { + d := &Digest{size: size224} + d.Reset() + return d +} + +// New512_256 returns a new Digest computing the SHA-512/256 hash. +func New512_256() *Digest { + d := &Digest{size: size256} + d.Reset() + return d +} + +// New384 returns a new Digest computing the SHA-384 hash. +func New384() *Digest { + d := &Digest{size: size384} + d.Reset() + return d +} + +func (d *Digest) Size() int { + return d.size +} + +func (d *Digest) BlockSize() int { return blockSize } + +func (d *Digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx:], p) + d.nx += n + if d.nx == chunk { + block(d, d.x[:]) + d.nx = 0 + } + p = p[n:] + } + if len(p) >= chunk { + n := len(p) &^ (chunk - 1) + block(d, p[:n]) + p = p[n:] + } + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d *Digest) Sum(in []byte) []byte { + fips140.RecordApproved() + // Make a copy of d so that caller can keep writing and summing. + d0 := new(Digest) + *d0 = *d + hash := d0.checkSum() + return append(in, hash[:d.size]...) +} + +func (d *Digest) checkSum() [size512]byte { + // Padding. Add a 1 bit and 0 bits until 112 bytes mod 128. + len := d.len + var tmp [128 + 16]byte // padding + length buffer + tmp[0] = 0x80 + var t uint64 + if len%128 < 112 { + t = 112 - len%128 + } else { + t = 128 + 112 - len%128 + } + + // Length in bits. + len <<= 3 + padlen := tmp[:t+16] + // Upper 64 bits are always zero, because len variable has type uint64, + // and tmp is already zeroed at that index, so we can skip updating it. + // byteorder.BEPutUint64(padlen[t+0:], 0) + byteorder.BEPutUint64(padlen[t+8:], len) + d.Write(padlen) + + if d.nx != 0 { + panic("d.nx != 0") + } + + var digest [size512]byte + byteorder.BEPutUint64(digest[0:], d.h[0]) + byteorder.BEPutUint64(digest[8:], d.h[1]) + byteorder.BEPutUint64(digest[16:], d.h[2]) + byteorder.BEPutUint64(digest[24:], d.h[3]) + byteorder.BEPutUint64(digest[32:], d.h[4]) + byteorder.BEPutUint64(digest[40:], d.h[5]) + if d.size != size384 { + byteorder.BEPutUint64(digest[48:], d.h[6]) + byteorder.BEPutUint64(digest[56:], d.h[7]) + } + + return digest +} diff --git a/crypto/sha512/sha512block.go b/crypto/internal/fips140/sha512/sha512block.go similarity index 98% rename from crypto/sha512/sha512block.go rename to crypto/internal/fips140/sha512/sha512block.go index 81569c5f84e..517e8389f7e 100644 --- a/crypto/sha512/sha512block.go +++ b/crypto/internal/fips140/sha512/sha512block.go @@ -10,7 +10,7 @@ package sha512 import "math/bits" -var _K = []uint64{ +var _K = [...]uint64{ 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, @@ -93,7 +93,7 @@ var _K = []uint64{ 0x6c44198c4a475817, } -func blockGeneric(dig *digest, p []byte) { +func blockGeneric(dig *Digest, p []byte) { var w [80]uint64 h0, h1, h2, h3, h4, h5, h6, h7 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4], dig.h[5], dig.h[6], dig.h[7] for len(p) >= chunk { diff --git a/crypto/internal/fips140/sha512/sha512block_amd64.go b/crypto/internal/fips140/sha512/sha512block_amd64.go new file mode 100644 index 00000000000..1fb611663ec --- /dev/null +++ b/crypto/internal/fips140/sha512/sha512block_amd64.go @@ -0,0 +1,32 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha512 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var useAVX2 = cpu.X86HasAVX && cpu.X86HasAVX2 && cpu.X86HasBMI2 + +func init() { + impl.Register("sha512", "AVX2", &useAVX2) +} + +//go:noescape +func blockAVX2(dig *Digest, p []byte) + +//go:noescape +func blockAMD64(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if useAVX2 { + blockAVX2(dig, p) + } else { + blockAMD64(dig, p) + } +} diff --git a/crypto/sha512/sha512block_amd64.s b/crypto/internal/fips140/sha512/sha512block_amd64.s similarity index 99% rename from crypto/sha512/sha512block_amd64.s rename to crypto/internal/fips140/sha512/sha512block_amd64.s index fdcef222150..ce77d20c048 100644 --- a/crypto/sha512/sha512block_amd64.s +++ b/crypto/internal/fips140/sha512/sha512block_amd64.s @@ -1,10 +1,10 @@ -// Code generated by command: go run sha512block_amd64_asm.go -out ../sha512block_amd64.s -pkg sha512. DO NOT EDIT. +// Code generated by command: go run sha512block_amd64_asm.go -out ../sha512block_amd64.s. DO NOT EDIT. //go:build !purego #include "textflag.h" -// func blockAMD64(dig *digest, p []byte) +// func blockAMD64(dig *Digest, p []byte) TEXT ·blockAMD64(SB), $648-32 MOVQ p_base+8(FP), SI MOVQ p_len+16(FP), DX @@ -4504,7 +4504,7 @@ DATA PSHUFFLE_BYTE_FLIP_MASK<>+16(SB)/8, $0x1011121314151617 DATA PSHUFFLE_BYTE_FLIP_MASK<>+24(SB)/8, $0x18191a1b1c1d1e1f GLOBL PSHUFFLE_BYTE_FLIP_MASK<>(SB), RODATA|NOPTR, $32 -// func blockAVX2(dig *digest, p []byte) +// func blockAVX2(dig *Digest, p []byte) // Requires: AVX, AVX2, BMI2 TEXT ·blockAVX2(SB), NOSPLIT, $56-32 MOVQ dig+0(FP), SI @@ -4526,7 +4526,7 @@ TEXT ·blockAVX2(SB), NOSPLIT, $56-32 VMOVDQU PSHUFFLE_BYTE_FLIP_MASK<>+0(SB), Y9 loop0: - MOVQ ·_K+0(SB), BP + MOVQ $·_K+0(SB), BP VMOVDQU (DI), Y4 VPSHUFB Y9, Y4, Y4 VMOVDQU 32(DI), Y5 diff --git a/crypto/internal/fips140/sha512/sha512block_arm64.go b/crypto/internal/fips140/sha512/sha512block_arm64.go new file mode 100644 index 00000000000..e58277e96a3 --- /dev/null +++ b/crypto/internal/fips140/sha512/sha512block_arm64.go @@ -0,0 +1,29 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha512 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var useSHA512 = cpu.ARM64HasSHA512 + +func init() { + impl.Register("sha512", "Armv8.2", &useSHA512) +} + +//go:noescape +func blockSHA512(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if useSHA512 { + blockSHA512(dig, p) + } else { + blockGeneric(dig, p) + } +} diff --git a/crypto/sha512/sha512block_arm64.s b/crypto/internal/fips140/sha512/sha512block_arm64.s similarity index 98% rename from crypto/sha512/sha512block_arm64.s rename to crypto/internal/fips140/sha512/sha512block_arm64.s index 25f3dbfe43d..cabe262548c 100644 --- a/crypto/sha512/sha512block_arm64.s +++ b/crypto/internal/fips140/sha512/sha512block_arm64.s @@ -40,12 +40,12 @@ VADD i3.D2, i1.D2, i4.D2 \ SHA512H2 i0.D2, i1, i3 -// func blockAsm(dig *digest, p []byte) -TEXT ·blockAsm(SB),NOSPLIT,$0 +// func blockSHA512(dig *Digest, p []byte) +TEXT ·blockSHA512(SB),NOSPLIT,$0 MOVD dig+0(FP), R0 MOVD p_base+8(FP), R1 MOVD p_len+16(FP), R2 - MOVD ·_K+0(SB), R3 + MOVD $·_K+0(SB), R3 // long enough to prefetch PRFM (R3), PLDL3KEEP diff --git a/crypto/sha512/sha512block_decl.go b/crypto/internal/fips140/sha512/sha512block_asm.go similarity index 64% rename from crypto/sha512/sha512block_decl.go rename to crypto/internal/fips140/sha512/sha512block_asm.go index b8a7854e4dd..532345108f8 100644 --- a/crypto/sha512/sha512block_decl.go +++ b/crypto/internal/fips140/sha512/sha512block_asm.go @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (loong64 || ppc64 || ppc64le || riscv64 || s390x) && !purego +//go:build (loong64 || riscv64) && !purego package sha512 //go:noescape -func block(dig *digest, p []byte) +func block(dig *Digest, p []byte) diff --git a/crypto/sha512/sha512block_loong64.s b/crypto/internal/fips140/sha512/sha512block_loong64.s similarity index 99% rename from crypto/sha512/sha512block_loong64.s rename to crypto/internal/fips140/sha512/sha512block_loong64.s index e508f23c58d..00f686c9f73 100644 --- a/crypto/sha512/sha512block_loong64.s +++ b/crypto/internal/fips140/sha512/sha512block_loong64.s @@ -104,7 +104,7 @@ // the frame size used for data expansion is 128 bytes. // See the definition of the macro LOAD1 above (8 bytes * 16 entries). // -// func block(dig *digest, p []byte) +// func block(dig *Digest, p []byte) TEXT ·block(SB),NOSPLIT,$128-32 MOVV p_len+16(FP), R6 MOVV p_base+8(FP), R5 diff --git a/crypto/sha512/sha512block_generic.go b/crypto/internal/fips140/sha512/sha512block_noasm.go similarity index 89% rename from crypto/sha512/sha512block_generic.go rename to crypto/internal/fips140/sha512/sha512block_noasm.go index 5d556606ed6..a1051ca2db0 100644 --- a/crypto/sha512/sha512block_generic.go +++ b/crypto/internal/fips140/sha512/sha512block_noasm.go @@ -6,6 +6,6 @@ package sha512 -func block(dig *digest, p []byte) { +func block(dig *Digest, p []byte) { blockGeneric(dig, p) } diff --git a/crypto/internal/fips140/sha512/sha512block_ppc64x.go b/crypto/internal/fips140/sha512/sha512block_ppc64x.go new file mode 100644 index 00000000000..c39fb7e9301 --- /dev/null +++ b/crypto/internal/fips140/sha512/sha512block_ppc64x.go @@ -0,0 +1,33 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build (ppc64 || ppc64le) && !purego + +package sha512 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/godebug" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +// The POWER architecture doesn't have a way to turn off SHA-512 support at +// runtime with GODEBUG=cpu.something=off, so introduce a new GODEBUG knob for +// that. It's intentionally only checked at init() time, to avoid the +// performance overhead of checking it on every block. +var ppc64sha512 = godebug.Value("#ppc64sha512") != "off" + +func init() { + impl.Register("sha512", "POWER8", &ppc64sha512) +} + +//go:noescape +func blockPOWER(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if ppc64sha512 { + blockPOWER(dig, p) + } else { + blockGeneric(dig, p) + } +} diff --git a/crypto/sha512/sha512block_ppc64x.s b/crypto/internal/fips140/sha512/sha512block_ppc64x.s similarity index 99% rename from crypto/sha512/sha512block_ppc64x.s rename to crypto/internal/fips140/sha512/sha512block_ppc64x.s index 87aab80903c..fd2c47bc7e3 100644 --- a/crypto/sha512/sha512block_ppc64x.s +++ b/crypto/internal/fips140/sha512/sha512block_ppc64x.s @@ -304,8 +304,8 @@ GLOBL ·kcon(SB), RODATA, $1312 VADDUDM S0, h, h; \ VADDUDM s1, xj, xj -// func block(dig *digest, p []byte) -TEXT ·block(SB),0,$0-32 +// func blockPOWER(dig *Digest, p []byte) +TEXT ·blockPOWER(SB),0,$0-32 MOVD dig+0(FP), CTX MOVD p_base+8(FP), INP MOVD p_len+16(FP), LEN diff --git a/crypto/sha512/sha512block_riscv64.s b/crypto/internal/fips140/sha512/sha512block_riscv64.s similarity index 96% rename from crypto/sha512/sha512block_riscv64.s rename to crypto/internal/fips140/sha512/sha512block_riscv64.s index 7dcb0f80d0a..2b156271e67 100644 --- a/crypto/sha512/sha512block_riscv64.s +++ b/crypto/internal/fips140/sha512/sha512block_riscv64.s @@ -100,38 +100,38 @@ // T1 = h + BIGSIGMA1(e) + Ch(e, f, g) + Kt + Wt // BIGSIGMA1(x) = ROTR(14,x) XOR ROTR(18,x) XOR ROTR(41,x) // Ch(x, y, z) = (x AND y) XOR (NOT x AND z) +// = ((y XOR z) AND x) XOR z #define SHA512T1(index, e, f, g, h) \ MOV (index*8)(X18), X8; \ ADD X5, h; \ ROR $14, e, X6; \ ADD X8, h; \ ROR $18, e, X7; \ - XOR X7, X6; \ ROR $41, e, X8; \ + XOR X7, X6; \ + XOR f, g, X5; \ XOR X8, X6; \ + AND e, X5; \ ADD X6, h; \ - AND e, f, X5; \ - NOT e, X7; \ - AND g, X7; \ - XOR X7, X5; \ + XOR g, X5; \ ADD h, X5 // Calculate T2 in X6. // T2 = BIGSIGMA0(a) + Maj(a, b, c) // BIGSIGMA0(x) = ROTR(28,x) XOR ROTR(34,x) XOR ROTR(39,x) // Maj(x, y, z) = (x AND y) XOR (x AND z) XOR (y AND z) +// = ((y XOR z) AND x) XOR (y AND z) #define SHA512T2(a, b, c) \ ROR $28, a, X6; \ ROR $34, a, X7; \ - XOR X7, X6; \ ROR $39, a, X8; \ + XOR X7, X6; \ + XOR b, c, X9; \ + AND b, c, X7; \ + AND a, X9; \ XOR X8, X6; \ - AND a, b, X7; \ - AND a, c, X8; \ - XOR X8, X7; \ - AND b, c, X9; \ - XOR X9, X7; \ - ADD X7, X6 + XOR X7, X9; \ + ADD X9, X6 // Calculate T1 and T2, then e = d + T1 and a = T1 + T2. // The values for e and a are stored in d and h, ready for rotation. @@ -150,7 +150,7 @@ MSGSCHEDULE1(index); \ SHA512ROUND(index, a, b, c, d, e, f, g, h) -// func block(dig *digest, p []byte) +// func block(dig *Digest, p []byte) TEXT ·block(SB),0,$128-32 MOV p_base+8(FP), X29 MOV p_len+16(FP), X30 @@ -160,7 +160,7 @@ TEXT ·block(SB),0,$128-32 ADD X29, X30, X28 BEQ X28, X29, end - MOV ·_K(SB), X18 // const table + MOV $·_K(SB), X18 // const table ADD $8, X2, X19 // message schedule MOV dig+0(FP), X20 diff --git a/crypto/internal/fips140/sha512/sha512block_s390x.go b/crypto/internal/fips140/sha512/sha512block_s390x.go new file mode 100644 index 00000000000..61a8935ce99 --- /dev/null +++ b/crypto/internal/fips140/sha512/sha512block_s390x.go @@ -0,0 +1,31 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +package sha512 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/cpu" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var useSHA512 = cpu.S390XHasSHA512 + +func init() { + // CP Assist for Cryptographic Functions (CPACF) + // https://www.ibm.com/docs/en/zos/3.1.0?topic=icsf-cp-assist-cryptographic-functions-cpacf + impl.Register("sha512", "CPACF", &useSHA512) +} + +//go:noescape +func blockS390X(dig *Digest, p []byte) + +func block(dig *Digest, p []byte) { + if useSHA512 { + blockS390X(dig, p) + } else { + blockGeneric(dig, p) + } +} diff --git a/crypto/sha512/sha512block_s390x.s b/crypto/internal/fips140/sha512/sha512block_s390x.s similarity index 73% rename from crypto/sha512/sha512block_s390x.s rename to crypto/internal/fips140/sha512/sha512block_s390x.s index 230bd414d38..5e943ed11fc 100644 --- a/crypto/sha512/sha512block_s390x.s +++ b/crypto/internal/fips140/sha512/sha512block_s390x.s @@ -6,17 +6,12 @@ #include "textflag.h" -// func block(dig *digest, p []byte) -TEXT ·block(SB), NOSPLIT|NOFRAME, $0-32 - MOVBZ ·useAsm(SB), R4 +// func blockS390X(dig *Digest, p []byte) +TEXT ·blockS390X(SB), NOSPLIT|NOFRAME, $0-32 LMG dig+0(FP), R1, R3 // R2 = &p[0], R3 = len(p) MOVBZ $3, R0 // SHA-512 function code - CMPBEQ R4, $0, generic loop: KIMD R0, R2 // compute intermediate message digest (KIMD) BVS loop // continue if interrupted RET - -generic: - BR ·blockGeneric(SB) diff --git a/crypto/internal/fips140/ssh/kdf.go b/crypto/internal/fips140/ssh/kdf.go new file mode 100644 index 00000000000..31f000abdce --- /dev/null +++ b/crypto/internal/fips140/ssh/kdf.go @@ -0,0 +1,56 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package ssh implements the SSH KDF as specified in RFC 4253, +// Section 7.2 and allowed by SP 800-135 Revision 1. +package ssh + +import ( + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +type Direction struct { + ivTag []byte + keyTag []byte + macKeyTag []byte +} + +var ServerKeys, ClientKeys Direction + +func init() { + ServerKeys = Direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}} + ClientKeys = Direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}} +} + +func Keys[Hash fips140.Hash](hash func() Hash, d Direction, + K, H, sessionID []byte, + ivKeyLen, keyLen, macKeyLen int, +) (ivKey, key, macKey []byte) { + + h := hash() + generateKeyMaterial := func(tag []byte, length int) []byte { + var key []byte + for len(key) < length { + h.Reset() + h.Write(K) + h.Write(H) + if len(key) == 0 { + h.Write(tag) + h.Write(sessionID) + } else { + h.Write(key) + } + key = h.Sum(key) + } + return key[:length] + } + + ivKey = generateKeyMaterial(d.ivTag, ivKeyLen) + key = generateKeyMaterial(d.keyTag, keyLen) + macKey = generateKeyMaterial(d.macKeyTag, macKeyLen) + + return +} diff --git a/crypto/internal/fips140/subtle/constant_time.go b/crypto/internal/fips140/subtle/constant_time.go new file mode 100644 index 00000000000..9fd3923e761 --- /dev/null +++ b/crypto/internal/fips140/subtle/constant_time.go @@ -0,0 +1,60 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package subtle + +// ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents +// and 0 otherwise. The time taken is a function of the length of the slices and +// is independent of the contents. If the lengths of x and y do not match it +// returns 0 immediately. +func ConstantTimeCompare(x, y []byte) int { + if len(x) != len(y) { + return 0 + } + + var v byte + + for i := 0; i < len(x); i++ { + v |= x[i] ^ y[i] + } + + return ConstantTimeByteEq(v, 0) +} + +// ConstantTimeSelect returns x if v == 1 and y if v == 0. +// Its behavior is undefined if v takes any other value. +func ConstantTimeSelect(v, x, y int) int { return ^(v-1)&x | (v-1)&y } + +// ConstantTimeByteEq returns 1 if x == y and 0 otherwise. +func ConstantTimeByteEq(x, y uint8) int { + return int((uint32(x^y) - 1) >> 31) +} + +// ConstantTimeEq returns 1 if x == y and 0 otherwise. +func ConstantTimeEq(x, y int32) int { + return int((uint64(uint32(x^y)) - 1) >> 63) +} + +// ConstantTimeCopy copies the contents of y into x (a slice of equal length) +// if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v +// takes any other value. +func ConstantTimeCopy(v int, x, y []byte) { + if len(x) != len(y) { + panic("subtle: slices have different lengths") + } + + xmask := byte(v - 1) + ymask := byte(^(v - 1)) + for i := 0; i < len(x); i++ { + x[i] = x[i]&xmask | y[i]&ymask + } +} + +// ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise. +// Its behavior is undefined if x or y are negative or > 2**31 - 1. +func ConstantTimeLessOrEq(x, y int) int { + x32 := int32(x) + y32 := int32(y) + return int(((x32 - y32 - 1) >> 31) & 1) +} diff --git a/crypto/internal/fips140/subtle/xor.go b/crypto/internal/fips140/subtle/xor.go new file mode 100644 index 00000000000..e0e9c38f4c9 --- /dev/null +++ b/crypto/internal/fips140/subtle/xor.go @@ -0,0 +1,30 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package subtle + +import "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + +// XORBytes sets dst[i] = x[i] ^ y[i] for all i < n = min(len(x), len(y)), +// returning n, the number of bytes written to dst. +// +// If dst does not have length at least n, +// XORBytes panics without writing anything to dst. +// +// dst and x or y may overlap exactly or not at all, +// otherwise XORBytes may panic. +func XORBytes(dst, x, y []byte) int { + n := min(len(x), len(y)) + if n == 0 { + return 0 + } + if n > len(dst) { + panic("subtle.XORBytes: dst too short") + } + if alias.InexactOverlap(dst[:n], x[:n]) || alias.InexactOverlap(dst[:n], y[:n]) { + panic("subtle.XORBytes: invalid overlap") + } + xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific + return n +} diff --git a/crypto/subtle/xor_amd64.s b/crypto/internal/fips140/subtle/xor_amd64.s similarity index 100% rename from crypto/subtle/xor_amd64.s rename to crypto/internal/fips140/subtle/xor_amd64.s diff --git a/crypto/subtle/xor_arm64.s b/crypto/internal/fips140/subtle/xor_arm64.s similarity index 100% rename from crypto/subtle/xor_arm64.s rename to crypto/internal/fips140/subtle/xor_arm64.s diff --git a/crypto/subtle/xor_ppc64x.go b/crypto/internal/fips140/subtle/xor_asm.go similarity index 73% rename from crypto/subtle/xor_ppc64x.go rename to crypto/internal/fips140/subtle/xor_asm.go index 760463c7e50..9a5da424aed 100644 --- a/crypto/subtle/xor_ppc64x.go +++ b/crypto/internal/fips140/subtle/xor_asm.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (ppc64 || ppc64le) && !purego +//go:build (amd64 || arm64 || loong64 || ppc64 || ppc64le || riscv64) && !purego package subtle diff --git a/crypto/subtle/xor_generic.go b/crypto/internal/fips140/subtle/xor_generic.go similarity index 95% rename from crypto/subtle/xor_generic.go rename to crypto/internal/fips140/subtle/xor_generic.go index e575c356960..0b31eec6019 100644 --- a/crypto/subtle/xor_generic.go +++ b/crypto/internal/fips140/subtle/xor_generic.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (!amd64 && !arm64 && !loong64 && !ppc64 && !ppc64le) || purego +//go:build (!amd64 && !arm64 && !loong64 && !ppc64 && !ppc64le && !riscv64) || purego package subtle diff --git a/crypto/subtle/xor_loong64.s b/crypto/internal/fips140/subtle/xor_loong64.s similarity index 100% rename from crypto/subtle/xor_loong64.s rename to crypto/internal/fips140/subtle/xor_loong64.s diff --git a/crypto/subtle/xor_ppc64x.s b/crypto/internal/fips140/subtle/xor_ppc64x.s similarity index 100% rename from crypto/subtle/xor_ppc64x.s rename to crypto/internal/fips140/subtle/xor_ppc64x.s diff --git a/crypto/internal/fips140/subtle/xor_riscv64.s b/crypto/internal/fips140/subtle/xor_riscv64.s new file mode 100644 index 00000000000..b5fa5dcef45 --- /dev/null +++ b/crypto/internal/fips140/subtle/xor_riscv64.s @@ -0,0 +1,169 @@ +// Copyright 2025 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !purego + +#include "textflag.h" + +// func xorBytes(dst, a, b *byte, n int) +TEXT ·xorBytes(SB), NOSPLIT|NOFRAME, $0 + MOV dst+0(FP), X10 + MOV a+8(FP), X11 + MOV b+16(FP), X12 + MOV n+24(FP), X13 + + MOV $32, X15 + BLT X13, X15, loop4_check + + // Check alignment - if alignment differs we have to do one byte at a time. + AND $7, X10, X5 + AND $7, X11, X6 + AND $7, X12, X7 + BNE X5, X6, loop4_check + BNE X5, X7, loop4_check + BEQZ X5, loop64_check + + // Check one byte at a time until we reach 8 byte alignment. + MOV $8, X8 + SUB X5, X8 + SUB X8, X13 +align: + MOVBU 0(X11), X16 + MOVBU 0(X12), X17 + XOR X16, X17 + MOVB X17, 0(X10) + ADD $1, X10 + ADD $1, X11 + ADD $1, X12 + SUB $1, X8 + BNEZ X8, align + +loop64_check: + MOV $64, X15 + BLT X13, X15, tail32_check + PCALIGN $16 +loop64: + MOV 0(X11), X16 + MOV 0(X12), X17 + MOV 8(X11), X18 + MOV 8(X12), X19 + XOR X16, X17 + XOR X18, X19 + MOV X17, 0(X10) + MOV X19, 8(X10) + MOV 16(X11), X20 + MOV 16(X12), X21 + MOV 24(X11), X22 + MOV 24(X12), X23 + XOR X20, X21 + XOR X22, X23 + MOV X21, 16(X10) + MOV X23, 24(X10) + MOV 32(X11), X16 + MOV 32(X12), X17 + MOV 40(X11), X18 + MOV 40(X12), X19 + XOR X16, X17 + XOR X18, X19 + MOV X17, 32(X10) + MOV X19, 40(X10) + MOV 48(X11), X20 + MOV 48(X12), X21 + MOV 56(X11), X22 + MOV 56(X12), X23 + XOR X20, X21 + XOR X22, X23 + MOV X21, 48(X10) + MOV X23, 56(X10) + ADD $64, X10 + ADD $64, X11 + ADD $64, X12 + SUB $64, X13 + BGE X13, X15, loop64 + BEQZ X13, done + +tail32_check: + MOV $32, X15 + BLT X13, X15, tail16_check + MOV 0(X11), X16 + MOV 0(X12), X17 + MOV 8(X11), X18 + MOV 8(X12), X19 + XOR X16, X17 + XOR X18, X19 + MOV X17, 0(X10) + MOV X19, 8(X10) + MOV 16(X11), X20 + MOV 16(X12), X21 + MOV 24(X11), X22 + MOV 24(X12), X23 + XOR X20, X21 + XOR X22, X23 + MOV X21, 16(X10) + MOV X23, 24(X10) + ADD $32, X10 + ADD $32, X11 + ADD $32, X12 + SUB $32, X13 + BEQZ X13, done + +tail16_check: + MOV $16, X15 + BLT X13, X15, loop4_check + MOV 0(X11), X16 + MOV 0(X12), X17 + MOV 8(X11), X18 + MOV 8(X12), X19 + XOR X16, X17 + XOR X18, X19 + MOV X17, 0(X10) + MOV X19, 8(X10) + ADD $16, X10 + ADD $16, X11 + ADD $16, X12 + SUB $16, X13 + BEQZ X13, done + +loop4_check: + MOV $4, X15 + BLT X13, X15, loop1 + PCALIGN $16 +loop4: + MOVBU 0(X11), X16 + MOVBU 0(X12), X17 + MOVBU 1(X11), X18 + MOVBU 1(X12), X19 + XOR X16, X17 + XOR X18, X19 + MOVB X17, 0(X10) + MOVB X19, 1(X10) + MOVBU 2(X11), X20 + MOVBU 2(X12), X21 + MOVBU 3(X11), X22 + MOVBU 3(X12), X23 + XOR X20, X21 + XOR X22, X23 + MOVB X21, 2(X10) + MOVB X23, 3(X10) + ADD $4, X10 + ADD $4, X11 + ADD $4, X12 + SUB $4, X13 + BGE X13, X15, loop4 + + PCALIGN $16 +loop1: + BEQZ X13, done + MOVBU 0(X11), X16 + MOVBU 0(X12), X17 + XOR X16, X17 + MOVB X17, 0(X10) + ADD $1, X10 + ADD $1, X11 + ADD $1, X12 + SUB $1, X13 + JMP loop1 + +done: + RET diff --git a/crypto/internal/fips140/tls12/cast.go b/crypto/internal/fips140/tls12/cast.go new file mode 100644 index 00000000000..51607175dbe --- /dev/null +++ b/crypto/internal/fips140/tls12/cast.go @@ -0,0 +1,40 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls12 + +import ( + "bytes" + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" +) + +func init() { + fips140.CAST("TLSv1.2-SHA2-256", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + transcript := []byte{ + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + } + want := []byte{ + 0x8c, 0x3e, 0xed, 0xa7, 0x1c, 0x1b, 0x4c, 0xc0, + 0xa0, 0x44, 0x90, 0x75, 0xa8, 0x8e, 0xbc, 0x7c, + 0x5e, 0x1c, 0x4b, 0x1e, 0x4f, 0xe3, 0xc1, 0x06, + 0xeb, 0xdc, 0xc0, 0x5d, 0xc0, 0xc8, 0xec, 0xf3, + 0xe2, 0xb9, 0xd1, 0x03, 0x5e, 0xb2, 0x60, 0x5d, + 0x12, 0x68, 0x4f, 0x49, 0xdf, 0xa9, 0x9d, 0xcc, + } + if got := MasterSecret(sha256.New, input, transcript); !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/tls12/tls12.go b/crypto/internal/fips140/tls12/tls12.go new file mode 100644 index 00000000000..3a9f59aa2f4 --- /dev/null +++ b/crypto/internal/fips140/tls12/tls12.go @@ -0,0 +1,69 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls12 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" +) + +// PRF implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, +// Section 5 and allowed by SP 800-135, Revision 1, Section 4.2.2. +func PRF[H fips140.Hash](hash func() H, secret []byte, label string, seed []byte, keyLen int) []byte { + labelAndSeed := make([]byte, len(label)+len(seed)) + copy(labelAndSeed, label) + copy(labelAndSeed[len(label):], seed) + + result := make([]byte, keyLen) + pHash(hash, result, secret, labelAndSeed) + return result +} + +// pHash implements the P_hash function, as defined in RFC 5246, Section 5. +func pHash[H fips140.Hash](hash func() H, result, secret, seed []byte) { + h := hmac.New(hash, secret) + h.Write(seed) + a := h.Sum(nil) + + for len(result) > 0 { + h.Reset() + h.Write(a) + h.Write(seed) + b := h.Sum(nil) + n := copy(result, b) + result = result[n:] + + h.Reset() + h.Write(a) + a = h.Sum(nil) + } +} + +const masterSecretLength = 48 +const extendedMasterSecretLabel = "extended master secret" + +// MasterSecret implements the TLS 1.2 extended master secret derivation, as +// defined in RFC 7627 and allowed by SP 800-135, Revision 1, Section 4.2.2. +func MasterSecret[H fips140.Hash](hash func() H, preMasterSecret, transcript []byte) []byte { + // "The TLS 1.2 KDF is an approved KDF when the following conditions are + // satisfied: [...] (3) P_HASH uses either SHA-256, SHA-384 or SHA-512." + h := hash() + switch any(h).(type) { + case *sha256.Digest: + if h.Size() != 32 { + fips140.RecordNonApproved() + } + case *sha512.Digest: + if h.Size() != 46 && h.Size() != 64 { + fips140.RecordNonApproved() + } + default: + fips140.RecordNonApproved() + } + + return PRF(hash, preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength) +} diff --git a/crypto/internal/fips140/tls13/cast.go b/crypto/internal/fips140/tls13/cast.go new file mode 100644 index 00000000000..b6e924e0b09 --- /dev/null +++ b/crypto/internal/fips140/tls13/cast.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls13 + +import ( + "bytes" + "errors" + + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" +) + +func init() { + fips140.CAST("TLSv1.3-SHA2-256", func() error { + input := []byte{ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + want := []byte{ + 0x78, 0x20, 0x71, 0x75, 0x52, 0xfd, 0x47, 0x67, + 0xe1, 0x07, 0x5c, 0x83, 0x74, 0x2e, 0x49, 0x43, + 0xf7, 0xe3, 0x08, 0x6a, 0x2a, 0xcb, 0x96, 0xc7, + 0xa3, 0x1f, 0xe3, 0x23, 0x56, 0x6e, 0x14, 0x5b, + } + es := NewEarlySecret(sha256.New, nil) + hs := es.HandshakeSecret(nil) + ms := hs.MasterSecret() + transcript := sha256.New() + transcript.Write(input) + if got := ms.ResumptionMasterSecret(transcript); !bytes.Equal(got, want) { + return errors.New("unexpected result") + } + return nil + }) +} diff --git a/crypto/internal/fips140/tls13/tls13.go b/crypto/internal/fips140/tls13/tls13.go new file mode 100644 index 00000000000..1b998690685 --- /dev/null +++ b/crypto/internal/fips140/tls13/tls13.go @@ -0,0 +1,178 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package tls13 implements the TLS 1.3 Key Schedule as specified in RFC 8446, +// Section 7.1 and allowed by FIPS 140-3 IG 2.4.B Resolution 7. +package tls13 + +import ( + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + "github.com/runZeroInc/excrypto/crypto/internal/fips140deps/byteorder" +) + +// We don't set the service indicator in this package but we delegate that to +// the underlying functions because the TLS 1.3 KDF does not have a standard of +// its own. + +// ExpandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1. +func ExpandLabel[H fips140.Hash](hash func() H, secret []byte, label string, context []byte, length int) []byte { + if len("tls13 ")+len(label) > 255 || len(context) > 255 { + // It should be impossible for this to panic: labels are fixed strings, + // and context is either a fixed-length computed hash, or parsed from a + // field which has the same length limitation. + // + // Another reasonable approach might be to return a randomized slice if + // we encounter an error, which would break the connection, but avoid + // panicking. This would perhaps be safer but significantly more + // confusing to users. + panic("tls13: label or context too long") + } + hkdfLabel := make([]byte, 0, 2+1+len("tls13 ")+len(label)+1+len(context)) + hkdfLabel = byteorder.BEAppendUint16(hkdfLabel, uint16(length)) + hkdfLabel = append(hkdfLabel, byte(len("tls13 ")+len(label))) + hkdfLabel = append(hkdfLabel, "tls13 "...) + hkdfLabel = append(hkdfLabel, label...) + hkdfLabel = append(hkdfLabel, byte(len(context))) + hkdfLabel = append(hkdfLabel, context...) + return hkdf.Expand(hash, secret, string(hkdfLabel), length) +} + +func extract[H fips140.Hash](hash func() H, newSecret, currentSecret []byte) []byte { + if newSecret == nil { + newSecret = make([]byte, hash().Size()) + } + return hkdf.Extract(hash, newSecret, currentSecret) +} + +func deriveSecret[H fips140.Hash](hash func() H, secret []byte, label string, transcript fips140.Hash) []byte { + if transcript == nil { + transcript = hash() + } + return ExpandLabel(hash, secret, label, transcript.Sum(nil), transcript.Size()) +} + +const ( + resumptionBinderLabel = "res binder" + clientEarlyTrafficLabel = "c e traffic" + clientHandshakeTrafficLabel = "c hs traffic" + serverHandshakeTrafficLabel = "s hs traffic" + clientApplicationTrafficLabel = "c ap traffic" + serverApplicationTrafficLabel = "s ap traffic" + earlyExporterLabel = "e exp master" + exporterLabel = "exp master" + resumptionLabel = "res master" +) + +type EarlySecret struct { + secret []byte + hash func() fips140.Hash +} + +func NewEarlySecret[H fips140.Hash](hash func() H, psk []byte) *EarlySecret { + return &EarlySecret{ + secret: extract(hash, psk, nil), + hash: func() fips140.Hash { return hash() }, + } +} + +func (s *EarlySecret) ResumptionBinderKey() []byte { + return deriveSecret(s.hash, s.secret, resumptionBinderLabel, nil) +} + +// ClientEarlyTrafficSecret derives the client_early_traffic_secret from the +// early secret and the transcript up to the ClientHello. +func (s *EarlySecret) ClientEarlyTrafficSecret(transcript fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientEarlyTrafficLabel, transcript) +} + +type HandshakeSecret struct { + secret []byte + hash func() fips140.Hash +} + +func (s *EarlySecret) HandshakeSecret(sharedSecret []byte) *HandshakeSecret { + derived := deriveSecret(s.hash, s.secret, "derived", nil) + return &HandshakeSecret{ + secret: extract(s.hash, sharedSecret, derived), + hash: s.hash, + } +} + +// ClientHandshakeTrafficSecret derives the client_handshake_traffic_secret from +// the handshake secret and the transcript up to the ServerHello. +func (s *HandshakeSecret) ClientHandshakeTrafficSecret(transcript fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientHandshakeTrafficLabel, transcript) +} + +// ServerHandshakeTrafficSecret derives the server_handshake_traffic_secret from +// the handshake secret and the transcript up to the ServerHello. +func (s *HandshakeSecret) ServerHandshakeTrafficSecret(transcript fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, serverHandshakeTrafficLabel, transcript) +} + +type MasterSecret struct { + secret []byte + hash func() fips140.Hash +} + +func (s *HandshakeSecret) MasterSecret() *MasterSecret { + derived := deriveSecret(s.hash, s.secret, "derived", nil) + return &MasterSecret{ + secret: extract(s.hash, nil, derived), + hash: s.hash, + } +} + +// ClientApplicationTrafficSecret derives the client_application_traffic_secret_0 +// from the master secret and the transcript up to the server Finished. +func (s *MasterSecret) ClientApplicationTrafficSecret(transcript fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, clientApplicationTrafficLabel, transcript) +} + +// ServerApplicationTrafficSecret derives the server_application_traffic_secret_0 +// from the master secret and the transcript up to the server Finished. +func (s *MasterSecret) ServerApplicationTrafficSecret(transcript fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, serverApplicationTrafficLabel, transcript) +} + +// ResumptionMasterSecret derives the resumption_master_secret from the master secret +// and the transcript up to the client Finished. +func (s *MasterSecret) ResumptionMasterSecret(transcript fips140.Hash) []byte { + return deriveSecret(s.hash, s.secret, resumptionLabel, transcript) +} + +type ExporterMasterSecret struct { + secret []byte + hash func() fips140.Hash +} + +// ExporterMasterSecret derives the exporter_master_secret from the master secret +// and the transcript up to the server Finished. +func (s *MasterSecret) ExporterMasterSecret(transcript fips140.Hash) *ExporterMasterSecret { + return &ExporterMasterSecret{ + secret: deriveSecret(s.hash, s.secret, exporterLabel, transcript), + hash: s.hash, + } +} + +// EarlyExporterMasterSecret derives the exporter_master_secret from the early secret +// and the transcript up to the ClientHello. +func (s *EarlySecret) EarlyExporterMasterSecret(transcript fips140.Hash) *ExporterMasterSecret { + return &ExporterMasterSecret{ + secret: deriveSecret(s.hash, s.secret, earlyExporterLabel, transcript), + hash: s.hash, + } +} + +func (s *ExporterMasterSecret) Exporter(label string, context []byte, length int) []byte { + secret := deriveSecret(s.hash, s.secret, label, nil) + h := s.hash() + h.Write(context) + return ExpandLabel(s.hash, secret, "exporter", h.Sum(nil), length) +} + +func TestingOnlyExporterSecret(s *ExporterMasterSecret) []byte { + return s.secret +} diff --git a/crypto/internal/fips140deps/byteorder/byteorder.go b/crypto/internal/fips140deps/byteorder/byteorder.go new file mode 100644 index 00000000000..c064fc3176a --- /dev/null +++ b/crypto/internal/fips140deps/byteorder/byteorder.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package byteorder + +import ( + "internal/byteorder" +) + +func LEUint16(b []byte) uint16 { + return byteorder.LEUint16(b) +} + +func BEUint32(b []byte) uint32 { + return byteorder.BEUint32(b) +} + +func BEUint64(b []byte) uint64 { + return byteorder.BEUint64(b) +} + +func LEUint64(b []byte) uint64 { + return byteorder.LEUint64(b) +} + +func BEPutUint16(b []byte, v uint16) { + byteorder.BEPutUint16(b, v) +} + +func BEPutUint32(b []byte, v uint32) { + byteorder.BEPutUint32(b, v) +} + +func BEPutUint64(b []byte, v uint64) { + byteorder.BEPutUint64(b, v) +} + +func LEPutUint64(b []byte, v uint64) { + byteorder.LEPutUint64(b, v) +} + +func BEAppendUint16(b []byte, v uint16) []byte { + return byteorder.BEAppendUint16(b, v) +} + +func BEAppendUint32(b []byte, v uint32) []byte { + return byteorder.BEAppendUint32(b, v) +} + +func BEAppendUint64(b []byte, v uint64) []byte { + return byteorder.BEAppendUint64(b, v) +} diff --git a/crypto/internal/fips140deps/cpu/cpu.go b/crypto/internal/fips140deps/cpu/cpu.go new file mode 100644 index 00000000000..5470cfdb7d8 --- /dev/null +++ b/crypto/internal/fips140deps/cpu/cpu.go @@ -0,0 +1,39 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package cpu + +import ( + "internal/cpu" + "internal/goarch" +) + +const BigEndian = goarch.BigEndian +const AMD64 = goarch.IsAmd64 == 1 +const ARM64 = goarch.IsArm64 == 1 +const PPC64 = goarch.IsPpc64 == 1 +const PPC64le = goarch.IsPpc64le == 1 + +var ARM64HasAES = cpu.ARM64.HasAES +var ARM64HasPMULL = cpu.ARM64.HasPMULL +var ARM64HasSHA2 = cpu.ARM64.HasSHA2 +var ARM64HasSHA512 = cpu.ARM64.HasSHA512 +var S390XHasAES = cpu.S390X.HasAES +var S390XHasAESCBC = cpu.S390X.HasAESCBC +var S390XHasAESCTR = cpu.S390X.HasAESCTR +var S390XHasAESGCM = cpu.S390X.HasAESGCM +var S390XHasECDSA = cpu.S390X.HasECDSA +var S390XHasGHASH = cpu.S390X.HasGHASH +var S390XHasSHA256 = cpu.S390X.HasSHA256 +var S390XHasSHA3 = cpu.S390X.HasSHA3 +var S390XHasSHA512 = cpu.S390X.HasSHA512 +var X86HasAES = cpu.X86.HasAES +var X86HasADX = cpu.X86.HasADX +var X86HasAVX = cpu.X86.HasAVX +var X86HasAVX2 = cpu.X86.HasAVX2 +var X86HasBMI2 = cpu.X86.HasBMI2 +var X86HasPCLMULQDQ = cpu.X86.HasPCLMULQDQ +var X86HasSHA = cpu.X86.HasSHA +var X86HasSSE41 = cpu.X86.HasSSE41 +var X86HasSSSE3 = cpu.X86.HasSSSE3 diff --git a/crypto/internal/fips140deps/fipsdeps.go b/crypto/internal/fips140deps/fipsdeps.go new file mode 100644 index 00000000000..307144339f4 --- /dev/null +++ b/crypto/internal/fips140deps/fipsdeps.go @@ -0,0 +1,9 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fipsdeps contains wrapper packages for internal APIs that are exposed +// to the FIPS module. Since modules are frozen upon validation and supported +// for a number of future versions, APIs exposed by crypto/internal/fips140deps/... +// must not be changed until the modules that use them are no longer supported. +package fipsdeps diff --git a/crypto/internal/fips140deps/fipsdeps_test.go b/crypto/internal/fips140deps/fipsdeps_test.go new file mode 100644 index 00000000000..58a934a0a36 --- /dev/null +++ b/crypto/internal/fips140deps/fipsdeps_test.go @@ -0,0 +1,108 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipsdeps + +import ( + "internal/testenv" + "strings" + "testing" +) + +// AllowedInternalPackages are internal packages that can be imported from the +// FIPS module. The API of these packages ends up locked for the lifetime of the +// validated module, which can be years. +// +// DO NOT add new packages here just to make the tests pass. +var AllowedInternalPackages = map[string]bool{ + // entropy.Depleted is the external passive entropy source, and sysrand.Read + // is the actual (but uncredited!) random bytes source. + "github.com/runZeroInc/excrypto/crypto/internal/entropy": true, + "github.com/runZeroInc/excrypto/crypto/internal/sysrand": true, + + // impl.Register is how the packages expose their alternative + // implementations to tests outside the module. + "github.com/runZeroInc/excrypto/crypto/internal/impl": true, + + // randutil.MaybeReadByte is used in non-FIPS mode by GenerateKey functions. + "github.com/runZeroInc/excrypto/crypto/internal/randutil": true, +} + +func TestImports(t *testing.T) { + cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", `{{$path := .ImportPath -}} +{{range .Imports -}} +{{$path}} {{.}} +{{end -}} +{{range .TestImports -}} +{{$path}} {{.}} +{{end -}} +{{range .XTestImports -}} +{{$path}} {{.}} +{{end -}}`, "crypto/internal/fips140/...") + bout, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go list: %v\n%s", err, bout) + } + out := string(bout) + + // In a snapshot, all the paths are crypto/internal/fips140/v1.2.3/... + // Determine the version number and remove it for the test. + _, v, _ := strings.Cut(out, "crypto/internal/fips140/") + v, _, _ = strings.Cut(v, "/") + v, _, _ = strings.Cut(v, " ") + if strings.HasPrefix(v, "v") && strings.Count(v, ".") == 2 { + out = strings.ReplaceAll(out, "crypto/internal/fips140/"+v, "crypto/internal/fips140") + } + + allPackages := make(map[string]bool) + + // importCheck is the set of packages that import crypto/internal/fips140/check. + importCheck := make(map[string]bool) + + for _, line := range strings.Split(out, "\n") { + if line == "" { + continue + } + pkg, importedPkg, _ := strings.Cut(line, " ") + + allPackages[pkg] = true + + if importedPkg == "crypto/internal/fips140/check" { + importCheck[pkg] = true + } + + // Ensure we don't import any unexpected internal package from the FIPS + // module, since we can't change the module source after it starts + // validation. This locks in the API of otherwise internal packages. + if importedPkg == "crypto/internal/fips140" || + strings.HasPrefix(importedPkg, "crypto/internal/fips140/") || + strings.HasPrefix(importedPkg, "crypto/internal/fips140deps/") { + continue + } + if AllowedInternalPackages[importedPkg] { + continue + } + if strings.Contains(importedPkg, "internal") { + t.Errorf("unexpected import of internal package: %s -> %s", pkg, importedPkg) + } + } + + // Ensure that all packages except check and check's dependencies import check. + for pkg := range allPackages { + switch pkg { + case "crypto/internal/fips140/check": + case "crypto/internal/fips140": + case "crypto/internal/fips140/alias": + case "crypto/internal/fips140/subtle": + case "crypto/internal/fips140/hmac": + case "crypto/internal/fips140/sha3": + case "crypto/internal/fips140/sha256": + case "crypto/internal/fips140/sha512": + default: + if !importCheck[pkg] { + t.Errorf("package %s does not import crypto/internal/fips140/check", pkg) + } + } + } +} diff --git a/crypto/internal/fips140deps/godebug/godebug.go b/crypto/internal/fips140deps/godebug/godebug.go new file mode 100644 index 00000000000..de50d612082 --- /dev/null +++ b/crypto/internal/fips140deps/godebug/godebug.go @@ -0,0 +1,23 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package godebug + +import ( + "internal/godebug" +) + +type Setting godebug.Setting + +func New(name string) *Setting { + return (*Setting)(godebug.New(name)) +} + +func (s *Setting) Value() string { + return (*godebug.Setting)(s).Value() +} + +func Value(name string) string { + return godebug.New(name).Value() +} diff --git a/crypto/internal/fips140hash/hash.go b/crypto/internal/fips140hash/hash.go new file mode 100644 index 00000000000..24e39c04984 --- /dev/null +++ b/crypto/internal/fips140hash/hash.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140hash + +import ( + fsha3 "crypto/internal/fips140/sha3" + "hash" + _ "unsafe" + + "github.com/runZeroInc/excrypto/crypto/sha3" +) + +//go:linkname sha3Unwrap +func sha3Unwrap(*sha3.SHA3) *fsha3.Digest + +// Unwrap returns h, or a crypto/internal/fips140 inner implementation of h. +// +// The return value can be type asserted to one of +// [crypto/internal/fips140/sha256.Digest], +// [crypto/internal/fips140/sha512.Digest], or +// [crypto/internal/fips140/sha3.Digest] if it is a FIPS 140-3 approved hash. +func Unwrap(h hash.Hash) hash.Hash { + if sha3, ok := h.(*sha3.SHA3); ok { + return sha3Unwrap(sha3) + } + return h +} + +// UnwrapNew returns a function that calls newHash and applies [Unwrap] to the +// return value. +func UnwrapNew[Hash hash.Hash](newHash func() Hash) func() hash.Hash { + return func() hash.Hash { return Unwrap(newHash()) } +} diff --git a/crypto/internal/fips140only/fips140only.go b/crypto/internal/fips140only/fips140only.go new file mode 100644 index 00000000000..c4ed2ad5e98 --- /dev/null +++ b/crypto/internal/fips140only/fips140only.go @@ -0,0 +1,34 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fips140only + +import ( + "hash" + "internal/godebug" + "io" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" +) + +// Enabled reports whether FIPS 140-only mode is enabled, in which non-approved +// cryptography returns an error or panics. +var Enabled = godebug.New("fips140").Value() == "only" + +func ApprovedHash(h hash.Hash) bool { + switch h.(type) { + case *sha256.Digest, *sha512.Digest, *sha3.Digest: + return true + default: + return false + } +} + +func ApprovedRandomReader(r io.Reader) bool { + _, ok := r.(drbg.DefaultReader) + return ok +} diff --git a/crypto/internal/fips140test/acvp_capabilities.json b/crypto/internal/fips140test/acvp_capabilities.json new file mode 100644 index 00000000000..b2007438ec1 --- /dev/null +++ b/crypto/internal/fips140test/acvp_capabilities.json @@ -0,0 +1,82 @@ +[ + {"algorithm":"SHA2-224","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-256","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-384","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-512","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-512/224","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + {"algorithm":"SHA2-512/256","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"1.0"}, + + {"algorithm":"SHA3-224","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + {"algorithm":"SHA3-256","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + {"algorithm":"SHA3-384","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + {"algorithm":"SHA3-512","messageLength":[{"increment":8,"max":65528,"min":0}],"revision":"2.0"}, + + {"algorithm":"SHAKE-128","inBit":false,"outBit":false,"inEmpty":true,"outputLen":[{"min":16,"max":65536,"increment":8}],"revision":"1.0"}, + {"algorithm":"SHAKE-256","inBit":false,"outBit":false,"inEmpty":true,"outputLen":[{"min":16,"max":65536,"increment":8}],"revision":"1.0"}, + {"algorithm":"cSHAKE-128","hexCustomization":false,"outputLen":[{"min":16,"max":65536,"increment":8}],"msgLen":[{"min":0,"max":65536,"increment":8}],"revision":"1.0"}, + {"algorithm":"cSHAKE-256","hexCustomization":false,"outputLen":[{"min":16,"max":65536,"increment":8}],"msgLen":[{"min":0,"max":65536,"increment":8}],"revision":"1.0"}, + + {"algorithm":"HMAC-SHA2-224","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":224,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-256","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":256,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-384","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":384,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-512","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":512,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-512/224","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":224,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA2-512/256","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":256,"min":32}],"revision":"1.0"}, + + {"algorithm":"HMAC-SHA3-224","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":224,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA3-256","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":256,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA3-384","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":384,"min":32}],"revision":"1.0"}, + {"algorithm":"HMAC-SHA3-512","keyLen":[{"increment":8,"max":524288,"min":8}],"macLen":[{"increment":8,"max":512,"min":32}],"revision":"1.0"}, + + {"algorithm":"KDA","mode":"HKDF","revision":"Sp800-56Cr1","fixedInfoPattern":"uPartyInfo||vPartyInfo","encoding":["concatenation"],"hmacAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"],"macSaltMethods":["default","random"],"l":2048,"z":[{"min":224,"max":65336,"increment":8}]}, + {"algorithm":"KDA","mode":"OneStepNoCounter","revision":"Sp800-56Cr2","auxFunctions":[{"auxFunctionName":"HMAC-SHA2-224","l":224,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-256","l":256,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-384","l":384,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-512","l":512,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-512/224","l":224,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA2-512/256","l":256,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-224","l":224,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-256","l":256,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-384","l":384,"macSaltMethods":["default","random"]},{"auxFunctionName":"HMAC-SHA3-512","l":512,"macSaltMethods":["default","random"]}],"fixedInfoPattern":"uPartyInfo||vPartyInfo","encoding":["concatenation"],"z":[{"min":224,"max":65336,"increment":8}]}, + + {"algorithm":"PBKDF","capabilities":[{"iterationCount":[{"min":1,"max":10000,"increment":1}],"keyLen":[{"min":112,"max":4096,"increment":8}],"passwordLen":[{"min":8,"max":64,"increment":1}],"saltLen":[{"min":128,"max":512,"increment":8}],"hmacAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}],"revision":"1.0"}, + + {"algorithm":"ML-KEM","mode":"keyGen","revision":"FIPS203","parameterSets":["ML-KEM-768","ML-KEM-1024"]}, + {"algorithm":"ML-KEM","mode":"encapDecap","revision":"FIPS203","parameterSets":["ML-KEM-768","ML-KEM-1024"],"functions":["encapsulation","decapsulation"]}, + + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-224","derFuncEnabled":false,"entropyInputLen":[192],"nonceLen":[96],"persoStringLen":[192],"additionalInputLen":[0],"returnedBitsLen":224}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-256","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":256}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-384","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":384}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-512","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":512}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-512/224","derFuncEnabled":false,"entropyInputLen":[192],"nonceLen":[96],"persoStringLen":[192],"additionalInputLen":[0],"returnedBitsLen":224}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA2-512/256","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":256}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-224","derFuncEnabled":false,"entropyInputLen":[192],"nonceLen":[96],"persoStringLen":[192],"additionalInputLen":[0],"returnedBitsLen":224}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-256","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":256}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-384","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":384}]}, + {"algorithm":"hmacDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":false,"capabilities":[{"mode":"SHA3-512","derFuncEnabled":false,"entropyInputLen":[256],"nonceLen":[128],"persoStringLen":[256],"additionalInputLen":[0],"returnedBitsLen":512}]}, + + {"algorithm":"ctrDRBG","revision":"1.0","predResistanceEnabled":[false],"reseedImplemented":true,"capabilities":[{"mode":"AES-256","derFuncEnabled":false,"entropyInputLen":[384],"nonceLen":[0],"persoStringLen":[0],"additionalInputLen":[384],"returnedBitsLen":128}]}, + + {"algorithm":"EDDSA","mode":"keyGen","revision":"1.0","curve":["ED-25519"]}, + {"algorithm":"EDDSA","mode":"keyVer","revision":"1.0","curve":["ED-25519"]}, + {"algorithm":"EDDSA","mode":"sigGen","revision":"1.0","pure":true,"preHash":true,"contextLength":[{"min":0,"max":255,"increment":1}],"curve":["ED-25519"]}, + {"algorithm":"EDDSA","mode":"sigVer","revision":"1.0","pure":true,"preHash":true,"curve":["ED-25519"]}, + + {"algorithm":"ECDSA","mode":"keyGen","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"],"secretGenerationMode":["testing candidates"]}, + {"algorithm":"ECDSA","mode":"keyVer","revision":"FIPS186-5","curve":["P-224","P-256","P-384","P-521"]}, + {"algorithm":"ECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}, + {"algorithm":"ECDSA","mode":"sigVer","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}, + {"algorithm":"DetECDSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"curve":["P-224","P-256","P-384","P-521"],"hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"]}]}, + + {"algorithm":"ACVP-AES-CBC","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"revision":"1.0"}, + {"algorithm":"ACVP-AES-CTR","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":8,"max":128,"increment":8}],"incrementalCounter":true,"overflowCounter":true,"performCounterTests":true,"revision":"1.0"}, + {"algorithm":"ACVP-AES-GCM","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":0,"max":65536,"increment":8}],"aadLen":[{"min":0,"max":65536,"increment":8}],"tagLen":[96,104,112,120,128],"ivLen":[96],"ivGen":"external","revision":"1.0"}, + {"algorithm":"ACVP-AES-GCM","direction":["encrypt","decrypt"],"keyLen":[128,192,256],"payloadLen":[{"min":0,"max":65536,"increment":8}],"aadLen":[{"min":0,"max":65536,"increment":8}],"tagLen":[128],"ivLen":[96],"ivGen":"internal","ivGenMode":"8.2.2","revision":"1.0"}, + {"algorithm":"CMAC-AES","capabilities":[{"direction":["gen","ver"],"msgLen":[{"min":0,"max":524288,"increment":8}],"keyLen":[128,256],"macLen":[{"min":8,"max":128,"increment":8}]}],"revision":"1.0"}, + + {"algorithm":"TLS-v1.2","mode":"KDF","revision":"RFC7627","hashAlg":["SHA2-256","SHA2-384","SHA2-512"]}, + {"algorithm":"TLS-v1.3","mode":"KDF","revision":"RFC8446","hmacAlg":["SHA2-256","SHA2-384"],"runningMode":["DHE","PSK","PSK-DHE"]}, + {"algorithm":"kdf-components","mode":"ssh","revision":"1.0","hashAlg":["SHA2-224","SHA2-256","SHA2-384","SHA2-512"],"cipher":["AES-128","AES-192","AES-256"]}, + + {"algorithm":"KAS-ECC-SSC","revision":"Sp800-56Ar3","scheme":{"ephemeralUnified":{"kasRole":["initiator","responder"]},"staticUnified":{"kasRole":["initiator","responder"]}},"domainParameterGenerationMethods":["P-224","P-256","P-384","P-521"]}, + + {"algorithm":"KDF","revision":"1.0","capabilities":[{"kdfMode":"counter","macMode":["CMAC-AES128","CMAC-AES192","CMAC-AES256"],"supportedLengths":[256],"fixedDataOrder":["before fixed data"],"counterLength":[16]},{"kdfMode":"feedback","macMode":["HMAC-SHA2-224","HMAC-SHA2-256","HMAC-SHA2-384","HMAC-SHA2-512","HMAC-SHA2-512/224","HMAC-SHA2-512/256","HMAC-SHA3-224","HMAC-SHA3-256","HMAC-SHA3-384","HMAC-SHA3-512"],"customKeyInLength":0,"supportedLengths":[{"min":8,"max":4096,"increment":8}],"fixedDataOrder":["after fixed data"],"counterLength":[8],"supportsEmptyIv":true,"requiresEmptyIv":true}]}, + + {"algorithm":"RSA","mode":"keyGen","revision":"FIPS186-5","infoGeneratedByServer":true,"pubExpMode":"fixed","fixedPubExp":"010001","keyFormat":"standard","capabilities":[{"randPQ":"probable","properties":[{"modulo":2048,"primeTest":["2powSecStr"]},{"modulo":3072,"primeTest":["2powSecStr"]},{"modulo":4096,"primeTest":["2powSecStr"]}]}]}, + {"algorithm":"RSA","mode":"sigGen","revision":"FIPS186-5","capabilities":[{"sigType":"pkcs1v1.5","properties":[{"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]},{"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]},{"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]},{"maskFunction":["mgf1"],"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]},{"maskFunction":["mgf1"],"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]}]}, + {"algorithm":"RSA","mode":"sigVer","revision":"FIPS186-5","pubExpMode":"fixed","fixedPubExp":"010001","capabilities":[{"sigType":"pkcs1v1.5","properties":[{"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pkcs1v1.5","properties":[{"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pkcs1v1.5","properties":[{"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224"},{"hashAlg":"SHA2-256"},{"hashAlg":"SHA2-384"},{"hashAlg":"SHA2-512"}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":2048,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":3072,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]},{"sigType":"pss","properties":[{"maskFunction":["mgf1"],"modulo":4096,"hashPair":[{"hashAlg":"SHA2-224","saltLen":28},{"hashAlg":"SHA2-256","saltLen":32},{"hashAlg":"SHA2-384","saltLen":48},{"hashAlg":"SHA2-512","saltLen":64}]}]}]}, + + {"algorithm":"KTS-IFC","revision":"Sp800-56Br2","fixedPubExp":"010001","iutId":"C0FFEE","modulo":[2048,3072,4096],"keyGenerationMethods":["rsakpg1-basic"],"scheme":{"KTS-OAEP-basic":{"l":1024,"kasRole":["responder","initiator"],"ktsMethod":{"hashAlgs":["SHA2-224","SHA2-256","SHA2-384","SHA2-512","SHA2-512/224","SHA2-512/256","SHA3-224","SHA3-256","SHA3-384","SHA3-512"],"supportsNullAssociatedData":true,"encoding":["concatenation"]}}}} +] diff --git a/crypto/internal/fips140test/acvp_test.config.json b/crypto/internal/fips140test/acvp_test.config.json new file mode 100644 index 00000000000..e14a2812671 --- /dev/null +++ b/crypto/internal/fips140test/acvp_test.config.json @@ -0,0 +1,57 @@ +[ + {"Wrapper": "go", "In": "vectors/SHA2-224.bz2", "Out": "expected/SHA2-224.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-256.bz2", "Out": "expected/SHA2-256.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-384.bz2", "Out": "expected/SHA2-384.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-512.bz2", "Out": "expected/SHA2-512.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-512-224.bz2", "Out": "expected/SHA2-512-224.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA2-512-256.bz2", "Out": "expected/SHA2-512-256.bz2"}, + + {"Wrapper": "go", "In": "vectors/SHA3-224.bz2", "Out": "expected/SHA3-224.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA3-256.bz2", "Out": "expected/SHA3-256.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA3-384.bz2", "Out": "expected/SHA3-384.bz2"}, + {"Wrapper": "go", "In": "vectors/SHA3-512.bz2", "Out": "expected/SHA3-512.bz2"}, + + {"Wrapper": "go", "In": "vectors/SHAKE-128.bz2", "Out": "expected/SHAKE-128.bz2"}, + {"Wrapper": "go", "In": "vectors/SHAKE-256.bz2", "Out": "expected/SHAKE-256.bz2"}, + {"Wrapper": "go", "In": "vectors/cSHAKE-128.bz2", "Out": "expected/cSHAKE-128.bz2"}, + {"Wrapper": "go", "In": "vectors/cSHAKE-256.bz2", "Out": "expected/cSHAKE-256.bz2"}, + + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-224.bz2", "Out": "expected/HMAC-SHA2-224.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-256.bz2", "Out": "expected/HMAC-SHA2-256.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-384.bz2", "Out": "expected/HMAC-SHA2-384.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-512.bz2", "Out": "expected/HMAC-SHA2-512.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-512-224.bz2", "Out": "expected/HMAC-SHA2-512-224.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA2-512-256.bz2", "Out": "expected/HMAC-SHA2-512-256.bz2"}, + + {"Wrapper": "go", "In": "vectors/KDA.bz2", "Out": "expected/KDA.bz2"}, + + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-224.bz2", "Out": "expected/HMAC-SHA3-224.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-256.bz2", "Out": "expected/HMAC-SHA3-256.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-384.bz2", "Out": "expected/HMAC-SHA3-384.bz2"}, + {"Wrapper": "go", "In": "vectors/HMAC-SHA3-512.bz2", "Out": "expected/HMAC-SHA3-512.bz2"}, + + {"Wrapper": "go", "In": "vectors/PBKDF.bz2", "Out": "expected/PBKDF.bz2"}, + + {"Wrapper": "go", "In": "vectors/ML-KEM.bz2", "Out": "expected/ML-KEM.bz2"}, + + {"Wrapper": "go", "In": "vectors/hmacDRBG.bz2", "Out": "expected/hmacDRBG.bz2"}, + + {"Wrapper": "go", "In": "vectors/ctrDRBG.bz2", "Out": "expected/ctrDRBG.bz2"}, + + {"Wrapper": "go", "In": "vectors/EDDSA.bz2", "Out": "expected/EDDSA.bz2"}, + + {"Wrapper": "go", "In": "vectors/ECDSA.bz2", "Out": "expected/ECDSA.bz2"}, + + {"Wrapper": "go", "In": "vectors/ACVP-AES-CBC.bz2", "Out": "expected/ACVP-AES-CBC.bz2"}, + {"Wrapper": "go", "In": "vectors/ACVP-AES-CTR.bz2", "Out": "expected/ACVP-AES-CTR.bz2"}, + {"Wrapper": "go", "In": "vectors/ACVP-AES-GCM.bz2", "Out": "expected/ACVP-AES-GCM.bz2"}, + + {"Wrapper": "go", "In": "vectors/CMAC-AES.bz2", "Out": "expected/CMAC-AES.bz2"}, + + {"Wrapper": "go", "In": "vectors/TLS-v1.2.bz2", "Out": "expected/TLS-v1.2.bz2"}, + {"Wrapper": "go", "In": "vectors/TLS-v1.3.bz2", "Out": "expected/TLS-v1.3.bz2"}, + + {"Wrapper": "go", "In": "vectors/kdf-components.bz2", "Out": "expected/kdf-components.bz2"}, + + {"Wrapper": "go", "In": "vectors/RSA.bz2", "Out": "expected/RSA.bz2"} +] diff --git a/crypto/internal/fips140test/acvp_test.go b/crypto/internal/fips140test/acvp_test.go new file mode 100644 index 00000000000..80c14412469 --- /dev/null +++ b/crypto/internal/fips140test/acvp_test.go @@ -0,0 +1,2258 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +// A module wrapper adapting the Go FIPS module to the protocol used by the +// BoringSSL project's `acvptool`. +// +// The `acvptool` "lowers" the NIST ACVP server JSON test vectors into a simpler +// stdin/stdout protocol that can be implemented by a module shim. The tool +// will fork this binary, request the supported configuration, and then provide +// test cases over stdin, expecting results to be returned on stdout. +// +// See "Testing other FIPS modules"[0] from the BoringSSL ACVP.md documentation +// for a more detailed description of the protocol used between the acvptool +// and module wrappers. +// +// [0]: https://boringssl.googlesource.com/boringssl/+/refs/heads/master/util/fipstools/acvp/ACVP.md#testing-other-fips-modules + +import ( + "bufio" + "bytes" + _ "embed" + "encoding/binary" + "errors" + "fmt" + "internal/testenv" + "io" + "math/big" + "os" + "path/filepath" + "strings" + "testing" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdh" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ed25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/edwards25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/pbkdf2" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ssh" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls12" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" + "github.com/runZeroInc/excrypto/crypto/internal/impl" +) + +var noPAAPAI = os.Getenv("GONOPAAPAI") == "1" + +func TestMain(m *testing.M) { + if noPAAPAI { + for _, p := range impl.Packages() { + impl.Select(p, "") + } + } + if os.Getenv("ACVP_WRAPPER") == "1" { + wrapperMain() + } else { + os.Exit(m.Run()) + } +} + +func wrapperMain() { + if !fips140.Enabled { + fmt.Fprintln(os.Stderr, "ACVP wrapper must be run with GODEBUG=fips140=on") + os.Exit(2) + } + if err := processingLoop(bufio.NewReader(os.Stdin), os.Stdout); err != nil { + fmt.Fprintf(os.Stderr, "processing error: %v\n", err) + os.Exit(1) + } +} + +type request struct { + name string + args [][]byte +} + +type commandHandler func([][]byte) ([][]byte, error) + +type command struct { + // requiredArgs enforces that an exact number of arguments are provided to the handler. + requiredArgs int + handler commandHandler +} + +type ecdsaSigType int + +const ( + ecdsaSigTypeNormal ecdsaSigType = iota + ecdsaSigTypeDeterministic +) + +type aesDirection int + +const ( + aesEncrypt aesDirection = iota + aesDecrypt +) + +var ( + // SHA2 algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#section-7.2 + // SHA3 and SHAKE algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#name-sha3-and-shake-algorithm-ca + // cSHAKE algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-xof.html#section-7.2 + // HMAC algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#section-7 + // PBKDF2 algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-pbkdf.html#section-7.3 + // ML-KEM algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-ml-kem.html#section-7.3 + // HMAC DRBG algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2 + // EDDSA algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-7 + // ECDSA and DetECDSA algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-fussell-acvp-ecdsa.html#section-7 + // AES algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-symmetric.html#section-7.3 + // HKDF KDA algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-hkdf.html#section-7.3 + // OneStepNoCounter KDA algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-kdf-onestepnocounter.html#section-7.2 + // TLS 1.2 KDF algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-tls.html#section-7.2 + // TLS 1.3 KDF algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-hammett-acvp-kdf-tls-v1.3.html#section-7.2 + // SSH KDF algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#section-7.2 + // ECDH algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-hammett-acvp-kas-ssc-ecc.html#section-7.3 + // HMAC DRBG and CTR DRBG algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-vassilev-acvp-drbg.html#section-7.2 + // KDF-Counter and KDF-Feedback algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-kbkdf.html#section-7.3 + // RSA algorithm capabilities: + // https://pages.nist.gov/ACVP/draft-celi-acvp-rsa.html#section-7.3 + //go:embed acvp_capabilities.json + capabilitiesJson []byte + + // commands should reflect what config says we support. E.g. adding a command here will be a NOP + // unless the configuration/acvp_capabilities.json indicates the command's associated algorithm + // is supported. + commands = map[string]command{ + "getConfig": cmdGetConfig(), + + "SHA2-224": cmdHashAft(sha256.New224()), + "SHA2-224/MCT": cmdHashMct(sha256.New224()), + "SHA2-256": cmdHashAft(sha256.New()), + "SHA2-256/MCT": cmdHashMct(sha256.New()), + "SHA2-384": cmdHashAft(sha512.New384()), + "SHA2-384/MCT": cmdHashMct(sha512.New384()), + "SHA2-512": cmdHashAft(sha512.New()), + "SHA2-512/MCT": cmdHashMct(sha512.New()), + "SHA2-512/224": cmdHashAft(sha512.New512_224()), + "SHA2-512/224/MCT": cmdHashMct(sha512.New512_224()), + "SHA2-512/256": cmdHashAft(sha512.New512_256()), + "SHA2-512/256/MCT": cmdHashMct(sha512.New512_256()), + + "SHA3-256": cmdHashAft(sha3.New256()), + "SHA3-256/MCT": cmdSha3Mct(sha3.New256()), + "SHA3-224": cmdHashAft(sha3.New224()), + "SHA3-224/MCT": cmdSha3Mct(sha3.New224()), + "SHA3-384": cmdHashAft(sha3.New384()), + "SHA3-384/MCT": cmdSha3Mct(sha3.New384()), + "SHA3-512": cmdHashAft(sha3.New512()), + "SHA3-512/MCT": cmdSha3Mct(sha3.New512()), + + // Note: SHAKE AFT and VOT test types can be handled by the same command + // handler impl, but use distinct acvptool command names, and so are + // registered twice with the same digest: once under "SHAKE-xxx" for AFT, + // and once under"SHAKE-xxx/VOT" for VOT. + "SHAKE-128": cmdShakeAftVot(sha3.NewShake128()), + "SHAKE-128/VOT": cmdShakeAftVot(sha3.NewShake128()), + "SHAKE-128/MCT": cmdShakeMct(sha3.NewShake128()), + "SHAKE-256": cmdShakeAftVot(sha3.NewShake256()), + "SHAKE-256/VOT": cmdShakeAftVot(sha3.NewShake256()), + "SHAKE-256/MCT": cmdShakeMct(sha3.NewShake256()), + + "cSHAKE-128": cmdCShakeAft(func(N, S []byte) *sha3.SHAKE { return sha3.NewCShake128(N, S) }), + "cSHAKE-128/MCT": cmdCShakeMct(func(N, S []byte) *sha3.SHAKE { return sha3.NewCShake128(N, S) }), + "cSHAKE-256": cmdCShakeAft(func(N, S []byte) *sha3.SHAKE { return sha3.NewCShake256(N, S) }), + "cSHAKE-256/MCT": cmdCShakeMct(func(N, S []byte) *sha3.SHAKE { return sha3.NewCShake256(N, S) }), + + "HMAC-SHA2-224": cmdHmacAft(func() fips140.Hash { return sha256.New224() }), + "HMAC-SHA2-256": cmdHmacAft(func() fips140.Hash { return sha256.New() }), + "HMAC-SHA2-384": cmdHmacAft(func() fips140.Hash { return sha512.New384() }), + "HMAC-SHA2-512": cmdHmacAft(func() fips140.Hash { return sha512.New() }), + "HMAC-SHA2-512/224": cmdHmacAft(func() fips140.Hash { return sha512.New512_224() }), + "HMAC-SHA2-512/256": cmdHmacAft(func() fips140.Hash { return sha512.New512_256() }), + "HMAC-SHA3-224": cmdHmacAft(func() fips140.Hash { return sha3.New224() }), + "HMAC-SHA3-256": cmdHmacAft(func() fips140.Hash { return sha3.New256() }), + "HMAC-SHA3-384": cmdHmacAft(func() fips140.Hash { return sha3.New384() }), + "HMAC-SHA3-512": cmdHmacAft(func() fips140.Hash { return sha3.New512() }), + + "HKDF/SHA2-224": cmdHkdfAft(func() fips140.Hash { return sha256.New224() }), + "HKDF/SHA2-256": cmdHkdfAft(func() fips140.Hash { return sha256.New() }), + "HKDF/SHA2-384": cmdHkdfAft(func() fips140.Hash { return sha512.New384() }), + "HKDF/SHA2-512": cmdHkdfAft(func() fips140.Hash { return sha512.New() }), + "HKDF/SHA2-512/224": cmdHkdfAft(func() fips140.Hash { return sha512.New512_224() }), + "HKDF/SHA2-512/256": cmdHkdfAft(func() fips140.Hash { return sha512.New512_256() }), + "HKDF/SHA3-224": cmdHkdfAft(func() fips140.Hash { return sha3.New224() }), + "HKDF/SHA3-256": cmdHkdfAft(func() fips140.Hash { return sha3.New256() }), + "HKDF/SHA3-384": cmdHkdfAft(func() fips140.Hash { return sha3.New384() }), + "HKDF/SHA3-512": cmdHkdfAft(func() fips140.Hash { return sha3.New512() }), + + "HKDFExtract/SHA2-256": cmdHkdfExtractAft(func() fips140.Hash { return sha256.New() }), + "HKDFExtract/SHA2-384": cmdHkdfExtractAft(func() fips140.Hash { return sha512.New384() }), + "HKDFExpandLabel/SHA2-256": cmdHkdfExpandLabelAft(func() fips140.Hash { return sha256.New() }), + "HKDFExpandLabel/SHA2-384": cmdHkdfExpandLabelAft(func() fips140.Hash { return sha512.New384() }), + + "PBKDF": cmdPbkdf(), + + "ML-KEM-768/keyGen": cmdMlKem768KeyGenAft(), + "ML-KEM-768/encap": cmdMlKem768EncapAft(), + "ML-KEM-768/decap": cmdMlKem768DecapAft(), + "ML-KEM-1024/keyGen": cmdMlKem1024KeyGenAft(), + "ML-KEM-1024/encap": cmdMlKem1024EncapAft(), + "ML-KEM-1024/decap": cmdMlKem1024DecapAft(), + + "hmacDRBG/SHA2-224": cmdHmacDrbgAft(func() fips140.Hash { return sha256.New224() }), + "hmacDRBG/SHA2-256": cmdHmacDrbgAft(func() fips140.Hash { return sha256.New() }), + "hmacDRBG/SHA2-384": cmdHmacDrbgAft(func() fips140.Hash { return sha512.New384() }), + "hmacDRBG/SHA2-512": cmdHmacDrbgAft(func() fips140.Hash { return sha512.New() }), + "hmacDRBG/SHA2-512/224": cmdHmacDrbgAft(func() fips140.Hash { return sha512.New512_224() }), + "hmacDRBG/SHA2-512/256": cmdHmacDrbgAft(func() fips140.Hash { return sha512.New512_256() }), + "hmacDRBG/SHA3-224": cmdHmacDrbgAft(func() fips140.Hash { return sha3.New224() }), + "hmacDRBG/SHA3-256": cmdHmacDrbgAft(func() fips140.Hash { return sha3.New256() }), + "hmacDRBG/SHA3-384": cmdHmacDrbgAft(func() fips140.Hash { return sha3.New384() }), + "hmacDRBG/SHA3-512": cmdHmacDrbgAft(func() fips140.Hash { return sha3.New512() }), + + "EDDSA/keyGen": cmdEddsaKeyGenAft(), + "EDDSA/keyVer": cmdEddsaKeyVerAft(), + "EDDSA/sigGen": cmdEddsaSigGenAftBft(), + "EDDSA/sigVer": cmdEddsaSigVerAft(), + + "ECDSA/keyGen": cmdEcdsaKeyGenAft(), + "ECDSA/keyVer": cmdEcdsaKeyVerAft(), + "ECDSA/sigGen": cmdEcdsaSigGenAft(ecdsaSigTypeNormal), + "ECDSA/sigVer": cmdEcdsaSigVerAft(), + "DetECDSA/sigGen": cmdEcdsaSigGenAft(ecdsaSigTypeDeterministic), + + "AES-CBC/encrypt": cmdAesCbc(aesEncrypt), + "AES-CBC/decrypt": cmdAesCbc(aesDecrypt), + "AES-CTR/encrypt": cmdAesCtr(aesEncrypt), + "AES-CTR/decrypt": cmdAesCtr(aesDecrypt), + "AES-GCM/seal": cmdAesGcmSeal(false), + "AES-GCM/open": cmdAesGcmOpen(false), + "AES-GCM-randnonce/seal": cmdAesGcmSeal(true), + "AES-GCM-randnonce/open": cmdAesGcmOpen(true), + + "CMAC-AES": cmdCmacAesAft(), + "CMAC-AES/verify": cmdCmacAesVerifyAft(), + + // Note: Only SHA2-256, SHA2-384 and SHA2-512 are valid hash functions for TLSKDF. + // See https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-tls.html#section-7.2.1 + "TLSKDF/1.2/SHA2-256": cmdTlsKdf12Aft(func() fips140.Hash { return sha256.New() }), + "TLSKDF/1.2/SHA2-384": cmdTlsKdf12Aft(func() fips140.Hash { return sha512.New384() }), + "TLSKDF/1.2/SHA2-512": cmdTlsKdf12Aft(func() fips140.Hash { return sha512.New() }), + + // Note: only SHA2-224, SHA2-256, SHA2-384 and SHA2-512 are valid hash functions for SSHKDF. + // See https://pages.nist.gov/ACVP/draft-celi-acvp-kdf-ssh.html#section-7.2.1 + "SSHKDF/SHA2-224/client": cmdSshKdfAft(func() fips140.Hash { return sha256.New224() }, ssh.ClientKeys), + "SSHKDF/SHA2-224/server": cmdSshKdfAft(func() fips140.Hash { return sha256.New224() }, ssh.ServerKeys), + "SSHKDF/SHA2-256/client": cmdSshKdfAft(func() fips140.Hash { return sha256.New() }, ssh.ClientKeys), + "SSHKDF/SHA2-256/server": cmdSshKdfAft(func() fips140.Hash { return sha256.New() }, ssh.ServerKeys), + "SSHKDF/SHA2-384/client": cmdSshKdfAft(func() fips140.Hash { return sha512.New384() }, ssh.ClientKeys), + "SSHKDF/SHA2-384/server": cmdSshKdfAft(func() fips140.Hash { return sha512.New384() }, ssh.ServerKeys), + "SSHKDF/SHA2-512/client": cmdSshKdfAft(func() fips140.Hash { return sha512.New() }, ssh.ClientKeys), + "SSHKDF/SHA2-512/server": cmdSshKdfAft(func() fips140.Hash { return sha512.New() }, ssh.ServerKeys), + + "ECDH/P-224": cmdEcdhAftVal(ecdh.P224()), + "ECDH/P-256": cmdEcdhAftVal(ecdh.P256()), + "ECDH/P-384": cmdEcdhAftVal(ecdh.P384()), + "ECDH/P-521": cmdEcdhAftVal(ecdh.P521()), + + "ctrDRBG/AES-256": cmdCtrDrbgAft(), + "ctrDRBG-reseed/AES-256": cmdCtrDrbgReseedAft(), + + "RSA/keyGen": cmdRsaKeyGenAft(), + + "RSA/sigGen/SHA2-224/pkcs1v1.5": cmdRsaSigGenAft(func() fips140.Hash { return sha256.New224() }, "SHA-224", false), + "RSA/sigGen/SHA2-256/pkcs1v1.5": cmdRsaSigGenAft(func() fips140.Hash { return sha256.New() }, "SHA-256", false), + "RSA/sigGen/SHA2-384/pkcs1v1.5": cmdRsaSigGenAft(func() fips140.Hash { return sha512.New384() }, "SHA-384", false), + "RSA/sigGen/SHA2-512/pkcs1v1.5": cmdRsaSigGenAft(func() fips140.Hash { return sha512.New() }, "SHA-512", false), + "RSA/sigGen/SHA2-224/pss": cmdRsaSigGenAft(func() fips140.Hash { return sha256.New224() }, "SHA-224", true), + "RSA/sigGen/SHA2-256/pss": cmdRsaSigGenAft(func() fips140.Hash { return sha256.New() }, "SHA-256", true), + "RSA/sigGen/SHA2-384/pss": cmdRsaSigGenAft(func() fips140.Hash { return sha512.New384() }, "SHA-384", true), + "RSA/sigGen/SHA2-512/pss": cmdRsaSigGenAft(func() fips140.Hash { return sha512.New() }, "SHA-512", true), + + "RSA/sigVer/SHA2-224/pkcs1v1.5": cmdRsaSigVerAft(func() fips140.Hash { return sha256.New224() }, "SHA-224", false), + "RSA/sigVer/SHA2-256/pkcs1v1.5": cmdRsaSigVerAft(func() fips140.Hash { return sha256.New() }, "SHA-256", false), + "RSA/sigVer/SHA2-384/pkcs1v1.5": cmdRsaSigVerAft(func() fips140.Hash { return sha512.New384() }, "SHA-384", false), + "RSA/sigVer/SHA2-512/pkcs1v1.5": cmdRsaSigVerAft(func() fips140.Hash { return sha512.New() }, "SHA-512", false), + "RSA/sigVer/SHA2-224/pss": cmdRsaSigVerAft(func() fips140.Hash { return sha256.New224() }, "SHA-224", true), + "RSA/sigVer/SHA2-256/pss": cmdRsaSigVerAft(func() fips140.Hash { return sha256.New() }, "SHA-256", true), + "RSA/sigVer/SHA2-384/pss": cmdRsaSigVerAft(func() fips140.Hash { return sha512.New384() }, "SHA-384", true), + "RSA/sigVer/SHA2-512/pss": cmdRsaSigVerAft(func() fips140.Hash { return sha512.New() }, "SHA-512", true), + + "KDF-counter": cmdKdfCounterAft(), + "KDF-feedback": cmdKdfFeedbackAft(), + + "OneStepNoCounter/HMAC-SHA2-224": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha256.New224() }), + "OneStepNoCounter/HMAC-SHA2-256": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha256.New() }), + "OneStepNoCounter/HMAC-SHA2-384": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha512.New384() }), + "OneStepNoCounter/HMAC-SHA2-512": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha512.New() }), + "OneStepNoCounter/HMAC-SHA2-512/224": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha512.New512_224() }), + "OneStepNoCounter/HMAC-SHA2-512/256": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha512.New512_256() }), + "OneStepNoCounter/HMAC-SHA3-224": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha3.New224() }), + "OneStepNoCounter/HMAC-SHA3-256": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha3.New256() }), + "OneStepNoCounter/HMAC-SHA3-384": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha3.New384() }), + "OneStepNoCounter/HMAC-SHA3-512": cmdOneStepNoCounterHmacAft(func() fips140.Hash { return sha3.New512() }), + + "KTS-IFC/SHA2-224/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha256.New224() }), + "KTS-IFC/SHA2-224/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha256.New224() }), + "KTS-IFC/SHA2-256/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha256.New() }), + "KTS-IFC/SHA2-256/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha256.New() }), + "KTS-IFC/SHA2-384/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha512.New384() }), + "KTS-IFC/SHA2-384/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha512.New384() }), + "KTS-IFC/SHA2-512/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha512.New() }), + "KTS-IFC/SHA2-512/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha512.New() }), + "KTS-IFC/SHA2-512/224/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha512.New512_224() }), + "KTS-IFC/SHA2-512/224/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha512.New512_224() }), + "KTS-IFC/SHA2-512/256/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha512.New512_256() }), + "KTS-IFC/SHA2-512/256/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha512.New512_256() }), + "KTS-IFC/SHA3-224/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha3.New224() }), + "KTS-IFC/SHA3-224/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha3.New224() }), + "KTS-IFC/SHA3-256/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha3.New256() }), + "KTS-IFC/SHA3-256/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha3.New256() }), + "KTS-IFC/SHA3-384/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha3.New384() }), + "KTS-IFC/SHA3-384/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha3.New384() }), + "KTS-IFC/SHA3-512/initiator": cmdKtsIfcInitiatorAft(func() fips140.Hash { return sha3.New512() }), + "KTS-IFC/SHA3-512/responder": cmdKtsIfcResponderAft(func() fips140.Hash { return sha3.New512() }), + } +) + +func processingLoop(reader io.Reader, writer io.Writer) error { + // Per ACVP.md: + // The protocol is request–response: the subprocess only speaks in response to a request + // and there is exactly one response for every request. + for { + req, err := readRequest(reader) + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return fmt.Errorf("reading request: %w", err) + } + + cmd, exists := commands[req.name] + if !exists { + return fmt.Errorf("unknown command: %q", req.name) + } + + if gotArgs := len(req.args); gotArgs != cmd.requiredArgs { + return fmt.Errorf("command %q expected %d args, got %d", req.name, cmd.requiredArgs, gotArgs) + } + + response, err := cmd.handler(req.args) + if err != nil { + return fmt.Errorf("command %q failed: %w", req.name, err) + } + + if err = writeResponse(writer, response); err != nil { + return fmt.Errorf("command %q response failed: %w", req.name, err) + } + } + + return nil +} + +func readRequest(reader io.Reader) (*request, error) { + // Per ACVP.md: + // Requests consist of one or more byte strings and responses consist + // of zero or more byte strings. A request contains: the number of byte + // strings, the length of each byte string, and the contents of each byte + // string. All numbers are 32-bit little-endian and values are + // concatenated in the order specified. + var numArgs uint32 + if err := binary.Read(reader, binary.LittleEndian, &numArgs); err != nil { + return nil, err + } + if numArgs == 0 { + return nil, errors.New("invalid request: zero args") + } + + args, err := readArgs(reader, numArgs) + if err != nil { + return nil, err + } + + return &request{ + name: string(args[0]), + args: args[1:], + }, nil +} + +func readArgs(reader io.Reader, requiredArgs uint32) ([][]byte, error) { + argLengths := make([]uint32, requiredArgs) + args := make([][]byte, requiredArgs) + + for i := range argLengths { + if err := binary.Read(reader, binary.LittleEndian, &argLengths[i]); err != nil { + return nil, fmt.Errorf("invalid request: failed to read %d-th arg len: %w", i, err) + } + } + + for i, length := range argLengths { + buf := make([]byte, length) + if _, err := io.ReadFull(reader, buf); err != nil { + return nil, fmt.Errorf("invalid request: failed to read %d-th arg data: %w", i, err) + } + args[i] = buf + } + + return args, nil +} + +func writeResponse(writer io.Writer, args [][]byte) error { + // See `readRequest` for details on the base format. Per ACVP.md: + // A response has the same format except that there may be zero byte strings + // and the first byte string has no special meaning. + numArgs := uint32(len(args)) + if err := binary.Write(writer, binary.LittleEndian, numArgs); err != nil { + return fmt.Errorf("writing arg count: %w", err) + } + + for i, arg := range args { + if err := binary.Write(writer, binary.LittleEndian, uint32(len(arg))); err != nil { + return fmt.Errorf("writing %d-th arg length: %w", i, err) + } + } + + for i, b := range args { + if _, err := writer.Write(b); err != nil { + return fmt.Errorf("writing %d-th arg data: %w", i, err) + } + } + + return nil +} + +// "All implementations must support the getConfig command +// which takes no arguments and returns a single byte string +// which is a JSON blob of ACVP algorithm configuration." +func cmdGetConfig() command { + return command{ + handler: func(args [][]byte) ([][]byte, error) { + return [][]byte{capabilitiesJson}, nil + }, + } +} + +// cmdHashAft returns a command handler for the specified hash +// algorithm for algorithm functional test (AFT) test cases. +// +// This shape of command expects a message as the sole argument, +// and writes the resulting digest as a response. +// +// See https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html +func cmdHashAft(h fips140.Hash) command { + return command{ + requiredArgs: 1, // Message to hash. + handler: func(args [][]byte) ([][]byte, error) { + h.Reset() + h.Write(args[0]) + digest := make([]byte, 0, h.Size()) + digest = h.Sum(digest) + + return [][]byte{digest}, nil + }, + } +} + +// cmdHashMct returns a command handler for the specified hash +// algorithm for monte carlo test (MCT) test cases. +// +// This shape of command expects a seed as the sole argument, +// and writes the resulting digest as a response. It implements +// the "standard" flavour of the MCT, not the "alternative". +// +// This algorithm was ported from `HashMCT` in BSSL's `modulewrapper.cc` +// Note that it differs slightly from the upstream NIST MCT[0] algorithm +// in that it does not perform the outer 100 iterations itself. See +// footnote #1 in the ACVP.md docs[1], the acvptool handles this. +// +// [0]: https://pages.nist.gov/ACVP/draft-celi-acvp-sha.html#section-6.2 +// [1]: https://boringssl.googlesource.com/boringssl/+/refs/heads/master/util/fipstools/acvp/ACVP.md#testing-other-fips-modules +func cmdHashMct(h fips140.Hash) command { + return command{ + requiredArgs: 1, // Seed message. + handler: func(args [][]byte) ([][]byte, error) { + hSize := h.Size() + seed := args[0] + + if seedLen := len(seed); seedLen != hSize { + return nil, fmt.Errorf("invalid seed size: expected %d got %d", hSize, seedLen) + } + + digest := make([]byte, 0, hSize) + buf := make([]byte, 0, 3*hSize) + buf = append(buf, seed...) + buf = append(buf, seed...) + buf = append(buf, seed...) + + for i := 0; i < 1000; i++ { + h.Reset() + h.Write(buf) + digest = h.Sum(digest[:0]) + + copy(buf, buf[hSize:]) + copy(buf[2*hSize:], digest) + } + + return [][]byte{buf[hSize*2:]}, nil + }, + } +} + +// cmdSha3Mct returns a command handler for the specified hash +// algorithm for SHA-3 monte carlo test (MCT) test cases. +// +// This shape of command expects a seed as the sole argument, +// and writes the resulting digest as a response. It implements +// the "standard" flavour of the MCT, not the "alternative". +// +// This algorithm was ported from the "standard" MCT algorithm +// specified in draft-celi-acvp-sha3[0]. Note this differs from +// the SHA2-* family of MCT tests handled by cmdHashMct. However, +// like that handler it does not perform the outer 100 iterations. +// +// [0]: https://pages.nist.gov/ACVP/draft-celi-acvp-sha3.html#section-6.2.1 +func cmdSha3Mct(h fips140.Hash) command { + return command{ + requiredArgs: 1, // Seed message. + handler: func(args [][]byte) ([][]byte, error) { + seed := args[0] + md := make([][]byte, 1001) + md[0] = seed + + for i := 1; i <= 1000; i++ { + h.Reset() + h.Write(md[i-1]) + md[i] = h.Sum(nil) + } + + return [][]byte{md[1000]}, nil + }, + } +} + +func cmdShakeAftVot(h *sha3.SHAKE) command { + return command{ + requiredArgs: 2, // Message, output length (bytes) + handler: func(args [][]byte) ([][]byte, error) { + msg := args[0] + + outLenBytes := binary.LittleEndian.Uint32(args[1]) + digest := make([]byte, outLenBytes) + + h.Reset() + h.Write(msg) + h.Read(digest) + + return [][]byte{digest}, nil + }, + } +} + +func cmdShakeMct(h *sha3.SHAKE) command { + return command{ + requiredArgs: 4, // Seed message, min output length (bytes), max output length (bytes), output length (bytes) + handler: func(args [][]byte) ([][]byte, error) { + md := args[0] + minOutBytes := binary.LittleEndian.Uint32(args[1]) + maxOutBytes := binary.LittleEndian.Uint32(args[2]) + + outputLenBytes := binary.LittleEndian.Uint32(args[3]) + if outputLenBytes < 2 { + return nil, fmt.Errorf("invalid output length: %d", outputLenBytes) + } + + rangeBytes := maxOutBytes - minOutBytes + 1 + if rangeBytes == 0 { + return nil, fmt.Errorf("invalid maxOutBytes and minOutBytes: %d, %d", maxOutBytes, minOutBytes) + } + + for i := 0; i < 1000; i++ { + // "The MSG[i] input to SHAKE MUST always contain at least 128 bits. If this is not the case + // as the previous digest was too short, append empty bits to the rightmost side of the digest." + boundary := min(len(md), 16) + msg := make([]byte, 16) + copy(msg, md[:boundary]) + + // MD[i] = SHAKE(MSG[i], OutputLen * 8) + h.Reset() + h.Write(msg) + digest := make([]byte, outputLenBytes) + h.Read(digest) + md = digest + + // RightmostOutputBits = 16 rightmost bits of MD[i] as an integer + // OutputLen = minOutBytes + (RightmostOutputBits % Range) + rightmostOutput := uint32(md[outputLenBytes-2])<<8 | uint32(md[outputLenBytes-1]) + outputLenBytes = minOutBytes + (rightmostOutput % rangeBytes) + } + + encodedOutputLenBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(encodedOutputLenBytes, outputLenBytes) + + return [][]byte{md, encodedOutputLenBytes}, nil + }, + } +} + +func cmdCShakeAft(hFn func(N, S []byte) *sha3.SHAKE) command { + return command{ + requiredArgs: 4, // Message, output length bytes, function name, customization + handler: func(args [][]byte) ([][]byte, error) { + msg := args[0] + outLenBytes := binary.LittleEndian.Uint32(args[1]) + functionName := args[2] + customization := args[3] + + h := hFn(functionName, customization) + h.Write(msg) + + out := make([]byte, outLenBytes) + h.Read(out) + + return [][]byte{out}, nil + }, + } +} + +func cmdCShakeMct(hFn func(N, S []byte) *sha3.SHAKE) command { + return command{ + requiredArgs: 6, // Message, min output length (bits), max output length (bits), output length (bits), increment (bits), customization + handler: func(args [][]byte) ([][]byte, error) { + message := args[0] + minOutLenBytes := binary.LittleEndian.Uint32(args[1]) + maxOutLenBytes := binary.LittleEndian.Uint32(args[2]) + outputLenBytes := binary.LittleEndian.Uint32(args[3]) + incrementBytes := binary.LittleEndian.Uint32(args[4]) + customization := args[5] + + if outputLenBytes < 2 { + return nil, fmt.Errorf("invalid output length: %d", outputLenBytes) + } + + rangeBits := (maxOutLenBytes*8 - minOutLenBytes*8) + 1 + if rangeBits == 0 { + return nil, fmt.Errorf("invalid maxOutLenBytes and minOutLenBytes: %d, %d", maxOutLenBytes, minOutLenBytes) + } + + // cSHAKE Monte Carlo test inner loop: + // https://pages.nist.gov/ACVP/draft-celi-acvp-xof.html#section-6.2.1 + for i := 0; i < 1000; i++ { + // InnerMsg = Left(Output[i-1] || ZeroBits(128), 128); + boundary := min(len(message), 16) + innerMsg := make([]byte, 16) + copy(innerMsg, message[:boundary]) + + // Output[i] = CSHAKE(InnerMsg, OutputLen, FunctionName, Customization); + h := hFn(nil, customization) // Note: function name fixed to "" for MCT. + h.Write(innerMsg) + digest := make([]byte, outputLenBytes) + h.Read(digest) + message = digest + + // Rightmost_Output_bits = Right(Output[i], 16); + rightmostOutput := digest[outputLenBytes-2:] + // IMPORTANT: the specification says: + // NOTE: For the "Rightmost_Output_bits % Range" operation, the Rightmost_Output_bits bit string + // should be interpretted as a little endian-encoded number. + // This is **a lie**! It has to be interpreted as a big-endian number. + rightmostOutputBE := binary.BigEndian.Uint16(rightmostOutput) + + // OutputLen = MinOutLen + (floor((Rightmost_Output_bits % Range) / OutLenIncrement) * OutLenIncrement); + incrementBits := incrementBytes * 8 + outputLenBits := (minOutLenBytes * 8) + (((uint32)(rightmostOutputBE)%rangeBits)/incrementBits)*incrementBits + outputLenBytes = outputLenBits / 8 + + // Customization = BitsToString(InnerMsg || Rightmost_Output_bits); + msgWithBits := append(innerMsg, rightmostOutput...) + customization = make([]byte, len(msgWithBits)) + for i, b := range msgWithBits { + customization[i] = (b % 26) + 65 + } + } + + encodedOutputLenBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(encodedOutputLenBytes, outputLenBytes) + + return [][]byte{message, encodedOutputLenBytes, customization}, nil + }, + } +} + +func cmdHmacAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 2, // Message and key + handler: func(args [][]byte) ([][]byte, error) { + msg := args[0] + key := args[1] + mac := hmac.New(h, key) + mac.Write(msg) + return [][]byte{mac.Sum(nil)}, nil + }, + } +} + +func cmdHkdfAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 4, // Key, salt, info, length bytes + handler: func(args [][]byte) ([][]byte, error) { + key := args[0] + salt := args[1] + info := args[2] + keyLen := int(binary.LittleEndian.Uint32(args[3])) + + return [][]byte{hkdf.Key(h, key, salt, string(info), keyLen)}, nil + }, + } +} + +func cmdHkdfExtractAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 2, // secret, salt + handler: func(args [][]byte) ([][]byte, error) { + secret := args[0] + salt := args[1] + + return [][]byte{hkdf.Extract(h, secret, salt)}, nil + }, + } +} + +func cmdHkdfExpandLabelAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 4, // output length, secret, label, transcript hash + handler: func(args [][]byte) ([][]byte, error) { + keyLen := int(binary.LittleEndian.Uint32(args[0])) + secret := args[1] + label := args[2] + transcriptHash := args[3] + + return [][]byte{tls13.ExpandLabel(h, secret, string(label), transcriptHash, keyLen)}, nil + }, + } +} + +func cmdPbkdf() command { + return command{ + // Hash name, key length, salt, password, iteration count + requiredArgs: 5, + handler: func(args [][]byte) ([][]byte, error) { + h, err := lookupHash(string(args[0])) + if err != nil { + return nil, fmt.Errorf("PBKDF2 failed: %w", err) + } + + keyLen := binary.LittleEndian.Uint32(args[1]) / 8 + salt := args[2] + password := args[3] + iterationCount := binary.LittleEndian.Uint32(args[4]) + + derivedKey, err := pbkdf2.Key(h, string(password), salt, int(iterationCount), int(keyLen)) + if err != nil { + return nil, fmt.Errorf("PBKDF2 failed: %w", err) + } + + return [][]byte{derivedKey}, nil + }, + } +} + +func cmdEddsaKeyGenAft() command { + return command{ + requiredArgs: 1, // Curve name + handler: func(args [][]byte) ([][]byte, error) { + if string(args[0]) != "ED-25519" { + return nil, fmt.Errorf("unsupported EDDSA curve: %q", args[0]) + } + + sk, err := ed25519.GenerateKey() + if err != nil { + return nil, fmt.Errorf("generating EDDSA keypair: %w", err) + } + + // EDDSA/keyGen/AFT responses are d & q, described[0] as: + // d The encoded private key point + // q The encoded public key point + // + // Contrary to the description of a "point", d is the private key + // seed bytes per FIPS.186-5[1] A.2.3. + // + // [0]: https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-9.1 + // [1]: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf + return [][]byte{sk.Seed(), sk.PublicKey()}, nil + }, + } +} + +func cmdEddsaKeyVerAft() command { + return command{ + requiredArgs: 2, // Curve name, Q + handler: func(args [][]byte) ([][]byte, error) { + if string(args[0]) != "ED-25519" { + return nil, fmt.Errorf("unsupported EDDSA curve: %q", args[0]) + } + + // Verify the point is on the curve. The higher-level ed25519 API does + // this at signature verification time so we have to use the lower-level + // edwards25519 package to do it here in absence of a signature to verify. + if _, err := new(edwards25519.Point).SetBytes(args[1]); err != nil { + return [][]byte{{0}}, nil + } + + return [][]byte{{1}}, nil + }, + } +} + +func cmdEddsaSigGenAftBft() command { + return command{ + requiredArgs: 5, // Curve name, private key seed, message, prehash, context + handler: func(args [][]byte) ([][]byte, error) { + if string(args[0]) != "ED-25519" { + return nil, fmt.Errorf("unsupported EDDSA curve: %q", args[0]) + } + + sk, err := ed25519.NewPrivateKeyFromSeed(args[1]) + if err != nil { + return nil, fmt.Errorf("error creating private key: %w", err) + } + msg := args[2] + prehash := args[3] + context := string(args[4]) + + var sig []byte + if prehash[0] == 1 { + h := sha512.New() + h.Write(msg) + msg = h.Sum(nil) + + // With ed25519 the context is only specified for sigGen tests when using prehashing. + // See https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-8.6 + sig, err = ed25519.SignPH(sk, msg, context) + if err != nil { + return nil, fmt.Errorf("error signing message: %w", err) + } + } else { + sig = ed25519.Sign(sk, msg) + } + + return [][]byte{sig}, nil + }, + } +} + +func cmdEddsaSigVerAft() command { + return command{ + requiredArgs: 5, // Curve name, message, public key, signature, prehash + handler: func(args [][]byte) ([][]byte, error) { + if string(args[0]) != "ED-25519" { + return nil, fmt.Errorf("unsupported EDDSA curve: %q", args[0]) + } + + msg := args[1] + pk, err := ed25519.NewPublicKey(args[2]) + if err != nil { + return nil, fmt.Errorf("invalid public key: %w", err) + } + sig := args[3] + prehash := args[4] + + if prehash[0] == 1 { + h := sha512.New() + h.Write(msg) + msg = h.Sum(nil) + // Context is only specified for sigGen, not sigVer. + // See https://pages.nist.gov/ACVP/draft-celi-acvp-eddsa.html#section-8.6 + err = ed25519.VerifyPH(pk, msg, sig, "") + } else { + err = ed25519.Verify(pk, msg, sig) + } + + if err != nil { + return [][]byte{{0}}, nil + } + + return [][]byte{{1}}, nil + }, + } +} + +func cmdEcdsaKeyGenAft() command { + return command{ + requiredArgs: 1, // Curve name + handler: func(args [][]byte) ([][]byte, error) { + curve, err := lookupCurve(string(args[0])) + if err != nil { + return nil, err + } + + var sk *ecdsa.PrivateKey + switch curve.Params() { + case elliptic.P224().Params(): + sk, err = ecdsa.GenerateKey(ecdsa.P224(), rand.Reader) + case elliptic.P256().Params(): + sk, err = ecdsa.GenerateKey(ecdsa.P256(), rand.Reader) + case elliptic.P384().Params(): + sk, err = ecdsa.GenerateKey(ecdsa.P384(), rand.Reader) + case elliptic.P521().Params(): + sk, err = ecdsa.GenerateKey(ecdsa.P521(), rand.Reader) + default: + return nil, fmt.Errorf("unsupported curve: %v", curve) + } + + if err != nil { + return nil, err + } + + pubBytes := sk.PublicKey().Bytes() + byteLen := (curve.Params().BitSize + 7) / 8 + + return [][]byte{ + sk.Bytes(), + pubBytes[1 : 1+byteLen], + pubBytes[1+byteLen:], + }, nil + }, + } +} + +func cmdEcdsaKeyVerAft() command { + return command{ + requiredArgs: 3, // Curve name, X, Y + handler: func(args [][]byte) ([][]byte, error) { + curve, err := lookupCurve(string(args[0])) + if err != nil { + return nil, err + } + + x := new(big.Int).SetBytes(args[1]) + y := new(big.Int).SetBytes(args[2]) + + if curve.IsOnCurve(x, y) { + return [][]byte{{1}}, nil + } + + return [][]byte{{0}}, nil + }, + } +} + +// pointFromAffine is used to convert the PublicKey to a nistec SetBytes input. +// Duplicated from crypto/ecdsa.go's pointFromAffine. +func pointFromAffine(curve elliptic.Curve, x, y *big.Int) ([]byte, error) { + bitSize := curve.Params().BitSize + // Reject values that would not get correctly encoded. + if x.Sign() < 0 || y.Sign() < 0 { + return nil, errors.New("negative coordinate") + } + if x.BitLen() > bitSize || y.BitLen() > bitSize { + return nil, errors.New("overflowing coordinate") + } + // Encode the coordinates and let SetBytes reject invalid points. + byteLen := (bitSize + 7) / 8 + buf := make([]byte, 1+2*byteLen) + buf[0] = 4 // uncompressed point + x.FillBytes(buf[1 : 1+byteLen]) + y.FillBytes(buf[1+byteLen : 1+2*byteLen]) + return buf, nil +} + +func signEcdsa[P ecdsa.Point[P], H fips140.Hash](c *ecdsa.Curve[P], h func() H, sigType ecdsaSigType, q []byte, sk []byte, digest []byte) (*ecdsa.Signature, error) { + priv, err := ecdsa.NewPrivateKey(c, sk, q) + if err != nil { + return nil, fmt.Errorf("invalid private key: %w", err) + } + + var sig *ecdsa.Signature + switch sigType { + case ecdsaSigTypeNormal: + sig, err = ecdsa.Sign(c, h, priv, rand.Reader, digest) + case ecdsaSigTypeDeterministic: + sig, err = ecdsa.SignDeterministic(c, h, priv, digest) + default: + return nil, fmt.Errorf("unsupported signature type: %v", sigType) + } + if err != nil { + return nil, fmt.Errorf("signing failed: %w", err) + } + + return sig, nil +} + +func cmdEcdsaSigGenAft(sigType ecdsaSigType) command { + return command{ + requiredArgs: 4, // Curve name, private key, hash name, message + handler: func(args [][]byte) ([][]byte, error) { + curve, err := lookupCurve(string(args[0])) + if err != nil { + return nil, err + } + + sk := args[1] + + newH, err := lookupHash(string(args[2])) + if err != nil { + return nil, err + } + + msg := args[3] + hashFunc := newH() + hashFunc.Write(msg) + digest := hashFunc.Sum(nil) + + d := new(big.Int).SetBytes(sk) + x, y := curve.ScalarBaseMult(d.Bytes()) + q, err := pointFromAffine(curve, x, y) + if err != nil { + return nil, err + } + + var sig *ecdsa.Signature + switch curve.Params() { + case elliptic.P224().Params(): + sig, err = signEcdsa(ecdsa.P224(), newH, sigType, q, sk, digest) + case elliptic.P256().Params(): + sig, err = signEcdsa(ecdsa.P256(), newH, sigType, q, sk, digest) + case elliptic.P384().Params(): + sig, err = signEcdsa(ecdsa.P384(), newH, sigType, q, sk, digest) + case elliptic.P521().Params(): + sig, err = signEcdsa(ecdsa.P521(), newH, sigType, q, sk, digest) + default: + return nil, fmt.Errorf("unsupported curve: %v", curve) + } + if err != nil { + return nil, err + } + + return [][]byte{sig.R, sig.S}, nil + }, + } +} + +func cmdEcdsaSigVerAft() command { + return command{ + requiredArgs: 7, // Curve name, hash name, message, X, Y, R, S + handler: func(args [][]byte) ([][]byte, error) { + curve, err := lookupCurve(string(args[0])) + if err != nil { + return nil, err + } + + newH, err := lookupHash(string(args[1])) + if err != nil { + return nil, err + } + + msg := args[2] + hashFunc := newH() + hashFunc.Write(msg) + digest := hashFunc.Sum(nil) + + x, y := args[3], args[4] + q, err := pointFromAffine(curve, new(big.Int).SetBytes(x), new(big.Int).SetBytes(y)) + if err != nil { + return nil, fmt.Errorf("invalid x/y coordinates: %v", err) + } + + signature := &ecdsa.Signature{R: args[5], S: args[6]} + + switch curve.Params() { + case elliptic.P224().Params(): + err = verifyEcdsa(ecdsa.P224(), q, digest, signature) + case elliptic.P256().Params(): + err = verifyEcdsa(ecdsa.P256(), q, digest, signature) + case elliptic.P384().Params(): + err = verifyEcdsa(ecdsa.P384(), q, digest, signature) + case elliptic.P521().Params(): + err = verifyEcdsa(ecdsa.P521(), q, digest, signature) + default: + return nil, fmt.Errorf("unsupported curve: %v", curve) + } + + if err == nil { + return [][]byte{{1}}, nil + } + + return [][]byte{{0}}, nil + }, + } +} + +func verifyEcdsa[P ecdsa.Point[P]](c *ecdsa.Curve[P], q []byte, digest []byte, sig *ecdsa.Signature) error { + pub, err := ecdsa.NewPublicKey(c, q) + if err != nil { + return fmt.Errorf("invalid public key: %w", err) + } + + return ecdsa.Verify(c, pub, digest, sig) +} + +func lookupHash(name string) (func() fips140.Hash, error) { + var h func() fips140.Hash + + switch name { + case "SHA2-224": + h = func() fips140.Hash { return sha256.New224() } + case "SHA2-256": + h = func() fips140.Hash { return sha256.New() } + case "SHA2-384": + h = func() fips140.Hash { return sha512.New384() } + case "SHA2-512": + h = func() fips140.Hash { return sha512.New() } + case "SHA2-512/224": + h = func() fips140.Hash { return sha512.New512_224() } + case "SHA2-512/256": + h = func() fips140.Hash { return sha512.New512_256() } + case "SHA3-224": + h = func() fips140.Hash { return sha3.New224() } + case "SHA3-256": + h = func() fips140.Hash { return sha3.New256() } + case "SHA3-384": + h = func() fips140.Hash { return sha3.New384() } + case "SHA3-512": + h = func() fips140.Hash { return sha3.New512() } + default: + return nil, fmt.Errorf("unknown hash name: %q", name) + } + + return h, nil +} + +func cmdMlKem768KeyGenAft() command { + return command{ + requiredArgs: 1, // Seed + handler: func(args [][]byte) ([][]byte, error) { + seed := args[0] + + dk, err := mlkem.NewDecapsulationKey768(seed) + if err != nil { + return nil, fmt.Errorf("generating ML-KEM 768 decapsulation key: %w", err) + } + + // Important: we must return the full encoding of dk, not the seed. + return [][]byte{dk.EncapsulationKey().Bytes(), mlkem.TestingOnlyExpandedBytes768(dk)}, nil + }, + } +} + +func cmdMlKem768EncapAft() command { + return command{ + requiredArgs: 2, // Public key, entropy + handler: func(args [][]byte) ([][]byte, error) { + pk := args[0] + entropy := args[1] + + ek, err := mlkem.NewEncapsulationKey768(pk) + if err != nil { + return nil, fmt.Errorf("generating ML-KEM 768 encapsulation key: %w", err) + } + + if len(entropy) != 32 { + return nil, fmt.Errorf("wrong entropy length: got %d, want 32", len(entropy)) + } + + sharedKey, ct := ek.EncapsulateInternal((*[32]byte)(entropy[:32])) + + return [][]byte{ct, sharedKey}, nil + }, + } +} + +func cmdMlKem768DecapAft() command { + return command{ + requiredArgs: 2, // Private key, ciphertext + handler: func(args [][]byte) ([][]byte, error) { + pk := args[0] + ct := args[1] + + dk, err := mlkem.TestingOnlyNewDecapsulationKey768(pk) + if err != nil { + return nil, fmt.Errorf("generating ML-KEM 768 decapsulation key: %w", err) + } + + sharedKey, err := dk.Decapsulate(ct) + if err != nil { + return nil, fmt.Errorf("decapsulating ML-KEM 768 ciphertext: %w", err) + } + + return [][]byte{sharedKey}, nil + }, + } +} + +func cmdMlKem1024KeyGenAft() command { + return command{ + requiredArgs: 1, // Seed + handler: func(args [][]byte) ([][]byte, error) { + seed := args[0] + + dk, err := mlkem.NewDecapsulationKey1024(seed) + if err != nil { + return nil, fmt.Errorf("generating ML-KEM 1024 decapsulation key: %w", err) + } + + // Important: we must return the full encoding of dk, not the seed. + return [][]byte{dk.EncapsulationKey().Bytes(), mlkem.TestingOnlyExpandedBytes1024(dk)}, nil + }, + } +} + +func cmdMlKem1024EncapAft() command { + return command{ + requiredArgs: 2, // Public key, entropy + handler: func(args [][]byte) ([][]byte, error) { + pk := args[0] + entropy := args[1] + + ek, err := mlkem.NewEncapsulationKey1024(pk) + if err != nil { + return nil, fmt.Errorf("generating ML-KEM 1024 encapsulation key: %w", err) + } + + if len(entropy) != 32 { + return nil, fmt.Errorf("wrong entropy length: got %d, want 32", len(entropy)) + } + + sharedKey, ct := ek.EncapsulateInternal((*[32]byte)(entropy[:32])) + + return [][]byte{ct, sharedKey}, nil + }, + } +} + +func cmdMlKem1024DecapAft() command { + return command{ + requiredArgs: 2, // Private key, ciphertext + handler: func(args [][]byte) ([][]byte, error) { + pk := args[0] + ct := args[1] + + dk, err := mlkem.TestingOnlyNewDecapsulationKey1024(pk) + if err != nil { + return nil, fmt.Errorf("generating ML-KEM 1024 decapsulation key: %w", err) + } + + sharedKey, err := dk.Decapsulate(ct) + if err != nil { + return nil, fmt.Errorf("decapsulating ML-KEM 1024 ciphertext: %w", err) + } + + return [][]byte{sharedKey}, nil + }, + } +} + +func lookupCurve(name string) (elliptic.Curve, error) { + var c elliptic.Curve + + switch name { + case "P-224": + c = elliptic.P224() + case "P-256": + c = elliptic.P256() + case "P-384": + c = elliptic.P384() + case "P-521": + c = elliptic.P521() + default: + return nil, fmt.Errorf("unknown curve name: %q", name) + } + + return c, nil +} + +func cmdAesCbc(direction aesDirection) command { + return command{ + requiredArgs: 4, // Key, ciphertext or plaintext, IV, num iterations + handler: func(args [][]byte) ([][]byte, error) { + if direction != aesEncrypt && direction != aesDecrypt { + panic("invalid AES direction") + } + + key := args[0] + input := args[1] + iv := args[2] + numIterations := binary.LittleEndian.Uint32(args[3]) + + blockCipher, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err) + } + + if len(input)%blockCipher.BlockSize() != 0 || len(input) == 0 { + return nil, fmt.Errorf("invalid ciphertext/plaintext size %d: not a multiple of block size %d", + len(input), blockCipher.BlockSize()) + } + + if blockCipher.BlockSize() != len(iv) { + return nil, fmt.Errorf("invalid IV size: expected %d, got %d", blockCipher.BlockSize(), len(iv)) + } + + result := make([]byte, len(input)) + prevResult := make([]byte, len(input)) + prevInput := make([]byte, len(input)) + + for i := uint32(0); i < numIterations; i++ { + copy(prevResult, result) + + if i > 0 { + if direction == aesEncrypt { + copy(iv, result) + } else { + copy(iv, prevInput) + } + } + + if direction == aesEncrypt { + cbcEnc := aes.NewCBCEncrypter(blockCipher, [16]byte(iv)) + cbcEnc.CryptBlocks(result, input) + } else { + cbcDec := aes.NewCBCDecrypter(blockCipher, [16]byte(iv)) + cbcDec.CryptBlocks(result, input) + } + + if direction == aesDecrypt { + copy(prevInput, input) + } + + if i == 0 { + copy(input, iv) + } else { + copy(input, prevResult) + } + } + + return [][]byte{result, prevResult}, nil + }, + } +} + +func cmdAesCtr(direction aesDirection) command { + return command{ + requiredArgs: 4, // Key, ciphertext or plaintext, initial counter, num iterations (constant 1) + handler: func(args [][]byte) ([][]byte, error) { + if direction != aesEncrypt && direction != aesDecrypt { + panic("invalid AES direction") + } + + key := args[0] + input := args[1] + iv := args[2] + numIterations := binary.LittleEndian.Uint32(args[3]) + if numIterations != 1 { + return nil, fmt.Errorf("invalid num iterations: expected 1, got %d", numIterations) + } + + if len(iv) != aes.BlockSize { + return nil, fmt.Errorf("invalid IV size: expected %d, got %d", aes.BlockSize, len(iv)) + } + + blockCipher, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err) + } + + result := make([]byte, len(input)) + stream := aes.NewCTR(blockCipher, iv) + stream.XORKeyStream(result, input) + + return [][]byte{result}, nil + }, + } +} + +func cmdAesGcmSeal(randNonce bool) command { + return command{ + requiredArgs: 5, // tag len, key, plaintext, nonce (empty for randNonce), additional data + handler: func(args [][]byte) ([][]byte, error) { + tagLen := binary.LittleEndian.Uint32(args[0]) + key := args[1] + plaintext := args[2] + nonce := args[3] + additionalData := args[4] + + blockCipher, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err) + } + + aesGCM, err := gcm.New(blockCipher, 12, int(tagLen)) + if err != nil { + return nil, fmt.Errorf("creating AES-GCM with tag len %d: %w", tagLen, err) + } + + var ct []byte + if !randNonce { + ct = aesGCM.Seal(nil, nonce, plaintext, additionalData) + } else { + var internalNonce [12]byte + ct = make([]byte, len(plaintext)+16) + gcm.SealWithRandomNonce(aesGCM, internalNonce[:], ct, plaintext, additionalData) + // acvptool expects the internally generated nonce to be appended to the end of the ciphertext. + ct = append(ct, internalNonce[:]...) + } + + return [][]byte{ct}, nil + }, + } +} + +func cmdAesGcmOpen(randNonce bool) command { + return command{ + requiredArgs: 5, // tag len, key, ciphertext, nonce (empty for randNonce), additional data + handler: func(args [][]byte) ([][]byte, error) { + + tagLen := binary.LittleEndian.Uint32(args[0]) + key := args[1] + ciphertext := args[2] + nonce := args[3] + additionalData := args[4] + + blockCipher, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err) + } + + aesGCM, err := gcm.New(blockCipher, 12, int(tagLen)) + if err != nil { + return nil, fmt.Errorf("creating AES-GCM with tag len %d: %w", tagLen, err) + } + + if randNonce { + // for randNonce tests acvptool appends the nonce to the end of the ciphertext. + nonce = ciphertext[len(ciphertext)-12:] + ciphertext = ciphertext[:len(ciphertext)-12] + } + + pt, err := aesGCM.Open(nil, nonce, ciphertext, additionalData) + if err != nil { + return [][]byte{{0}, nil}, nil + } + + return [][]byte{{1}, pt}, nil + }, + } +} + +func cmdCmacAesAft() command { + return command{ + requiredArgs: 3, // Number of output bytes, key, message + handler: func(args [][]byte) ([][]byte, error) { + // safe to truncate to int based on our capabilities describing a max MAC output len of 128 bits. + outputLen := int(binary.LittleEndian.Uint32(args[0])) + key := args[1] + message := args[2] + + blockCipher, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err) + } + + cmac := gcm.NewCMAC(blockCipher) + tag := cmac.MAC(message) + + if outputLen > len(tag) { + return nil, fmt.Errorf("invalid output length: expected %d, got %d", outputLen, len(tag)) + } + + return [][]byte{tag[:outputLen]}, nil + }, + } +} + +func cmdCmacAesVerifyAft() command { + return command{ + requiredArgs: 3, // Key, message, claimed MAC + handler: func(args [][]byte) ([][]byte, error) { + key := args[0] + message := args[1] + claimedMAC := args[2] + + blockCipher, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("creating AES block cipher with key len %d: %w", len(key), err) + } + + cmac := gcm.NewCMAC(blockCipher) + tag := cmac.MAC(message) + + if subtle.ConstantTimeCompare(tag[:len(claimedMAC)], claimedMAC) != 1 { + return [][]byte{{0}}, nil + } + + return [][]byte{{1}}, nil + }, + } +} + +func cmdTlsKdf12Aft(h func() fips140.Hash) command { + return command{ + requiredArgs: 5, // Number output bytes, secret, label, seed1, seed2 + handler: func(args [][]byte) ([][]byte, error) { + outputLen := binary.LittleEndian.Uint32(args[0]) + secret := args[1] + label := string(args[2]) + seed1 := args[3] + seed2 := args[4] + + return [][]byte{tls12.PRF(h, secret, label, append(seed1, seed2...), int(outputLen))}, nil + }, + } +} + +func cmdSshKdfAft(hFunc func() fips140.Hash, direction ssh.Direction) command { + return command{ + requiredArgs: 4, // K, H, SessionID, cipher + handler: func(args [][]byte) ([][]byte, error) { + k := args[0] + h := args[1] + sessionID := args[2] + cipher := string(args[3]) + + var keyLen int + switch cipher { + case "AES-128": + keyLen = 16 + case "AES-192": + keyLen = 24 + case "AES-256": + keyLen = 32 + default: + return nil, fmt.Errorf("unsupported cipher: %q", cipher) + } + + ivKey, encKey, intKey := ssh.Keys(hFunc, direction, k, h, sessionID, 16, keyLen, hFunc().Size()) + return [][]byte{ivKey, encKey, intKey}, nil + }, + } +} + +func cmdEcdhAftVal[P ecdh.Point[P]](curve *ecdh.Curve[P]) command { + return command{ + requiredArgs: 3, // X, Y, private key (empty for Val type tests) + handler: func(args [][]byte) ([][]byte, error) { + peerX := args[0] + peerY := args[1] + rawSk := args[2] + + uncompressedPk := append([]byte{4}, append(peerX, peerY...)...) // 4 for uncompressed point format + pk, err := ecdh.NewPublicKey(curve, uncompressedPk) + if err != nil { + return nil, fmt.Errorf("invalid peer public key x,y: %v", err) + } + + var sk *ecdh.PrivateKey + if len(rawSk) > 0 { + sk, err = ecdh.NewPrivateKey(curve, rawSk) + } else { + sk, err = ecdh.GenerateKey(curve, rand.Reader) + } + if err != nil { + return nil, fmt.Errorf("private key error: %v", err) + } + + pubBytes := sk.PublicKey().Bytes() + coordLen := (len(pubBytes) - 1) / 2 + x := pubBytes[1 : 1+coordLen] + y := pubBytes[1+coordLen:] + + secret, err := ecdh.ECDH(curve, sk, pk) + if err != nil { + return nil, fmt.Errorf("key agreement failed: %v", err) + } + + return [][]byte{x, y, secret}, nil + }, + } +} + +func cmdHmacDrbgAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce + handler: func(args [][]byte) ([][]byte, error) { + outLen := binary.LittleEndian.Uint32(args[0]) + entropy := args[1] + personalization := args[2] + ad1 := args[3] + ad2 := args[4] + nonce := args[5] + + // Our capabilities describe no additional data support. + if len(ad1) != 0 || len(ad2) != 0 { + return nil, errors.New("additional data not supported") + } + + // Our capabilities describe no prediction resistance (requires reseed) and no reseed. + // So the test procedure is: + // * Instantiate DRBG + // * Generate but don't output + // * Generate output + // * Uninstantiate + // See Table 7 in draft-vassilev-acvp-drbg + out := make([]byte, outLen) + drbg := ecdsa.TestingOnlyNewDRBG(h, entropy, nonce, personalization) + drbg.Generate(out) + drbg.Generate(out) + + return [][]byte{out}, nil + }, + } +} + +func cmdCtrDrbgAft() command { + return command{ + requiredArgs: 6, // Output length, entropy, personalization, ad1, ad2, nonce + handler: func(args [][]byte) ([][]byte, error) { + return acvpCtrDrbg{ + outLen: binary.LittleEndian.Uint32(args[0]), + entropy: args[1], + personalization: args[2], + ad1: args[3], + ad2: args[4], + nonce: args[5], + }.process() + }, + } +} + +func cmdCtrDrbgReseedAft() command { + return command{ + requiredArgs: 8, // Output length, entropy, personalization, reseedAD, reseedEntropy, ad1, ad2, nonce + handler: func(args [][]byte) ([][]byte, error) { + return acvpCtrDrbg{ + outLen: binary.LittleEndian.Uint32(args[0]), + entropy: args[1], + personalization: args[2], + reseedAd: args[3], + reseedEntropy: args[4], + ad1: args[5], + ad2: args[6], + nonce: args[7], + }.process() + }, + } +} + +type acvpCtrDrbg struct { + outLen uint32 + entropy []byte + personalization []byte + ad1 []byte + ad2 []byte + nonce []byte + reseedAd []byte // May be empty for no reseed + reseedEntropy []byte // May be empty for no reseed +} + +func (args acvpCtrDrbg) process() ([][]byte, error) { + // Our capability describes no personalization support. + if len(args.personalization) > 0 { + return nil, errors.New("personalization string not supported") + } + + // Our capability describes no derivation function support, so the nonce + // should be empty. + if len(args.nonce) > 0 { + return nil, errors.New("unexpected nonce value") + } + + // Our capability describes entropy input len of 384 bits. + entropy, err := require48Bytes(args.entropy) + if err != nil { + return nil, fmt.Errorf("entropy: %w", err) + } + + // Our capability describes additional input len of 384 bits. + ad1, err := require48Bytes(args.ad1) + if err != nil { + return nil, fmt.Errorf("AD1: %w", err) + } + ad2, err := require48Bytes(args.ad2) + if err != nil { + return nil, fmt.Errorf("AD2: %w", err) + } + + withReseed := len(args.reseedAd) > 0 + var reseedAd, reseedEntropy *[48]byte + if withReseed { + // Ditto RE: entropy and additional data lengths for reseeding. + if reseedAd, err = require48Bytes(args.reseedAd); err != nil { + return nil, fmt.Errorf("reseed AD: %w", err) + } + if reseedEntropy, err = require48Bytes(args.reseedEntropy); err != nil { + return nil, fmt.Errorf("reseed entropy: %w", err) + } + } + + // Our capabilities describe no prediction resistance and allow both + // reseed and no reseed, so the test procedure is: + // * Instantiate DRBG + // * Reseed (if enabled) + // * Generate but don't output + // * Generate output + // * Uninstantiate + // See Table 7 in draft-vassilev-acvp-drbg + out := make([]byte, args.outLen) + ctrDrbg := drbg.NewCounter(entropy) + if withReseed { + ctrDrbg.Reseed(reseedEntropy, reseedAd) + } + ctrDrbg.Generate(out, ad1) + ctrDrbg.Generate(out, ad2) + + return [][]byte{out}, nil +} + +// Verify input is 48 byte slice, and cast it to a pointer to a fixed-size array +// of 48 bytes, or return an error. +func require48Bytes(input []byte) (*[48]byte, error) { + if inputLen := len(input); inputLen != 48 { + return nil, fmt.Errorf("invalid length: %d", inputLen) + } + return (*[48]byte)(input), nil +} + +func cmdKdfCounterAft() command { + return command{ + requiredArgs: 5, // Number output bytes, PRF name, counter location string, key, number of counter bits + handler: func(args [][]byte) ([][]byte, error) { + outputBytes := binary.LittleEndian.Uint32(args[0]) + prf := args[1] + counterLocation := args[2] + key := args[3] + counterBits := binary.LittleEndian.Uint32(args[4]) + + if outputBytes != 32 { + return nil, fmt.Errorf("KDF received unsupported output length %d bytes", outputBytes) + } + if !bytes.Equal(prf, []byte("CMAC-AES128")) && !bytes.Equal(prf, []byte("CMAC-AES192")) && !bytes.Equal(prf, []byte("CMAC-AES256")) { + return nil, fmt.Errorf("KDF received unsupported PRF %q", string(prf)) + } + if !bytes.Equal(counterLocation, []byte("before fixed data")) { + return nil, fmt.Errorf("KDF received unsupported counter location %q", string(counterLocation)) + } + // The spec doesn't describe the "deferred" property for a KDF counterMode test case. + // BoringSSL's acvptool sends an empty key when deferred=true, but with the capabilities + // we register all test cases ahve deferred=false and provide a key from the populated + // keyIn property. + if len(key) == 0 { + return nil, errors.New("deferred test cases are not supported") + } + if counterBits != 16 { + return nil, fmt.Errorf("KDF received unsupported counter length %d", counterBits) + } + + block, err := aes.New(key) + if err != nil { + return nil, fmt.Errorf("failed to create cipher: %v", err) + } + kdf := gcm.NewCounterKDF(block) + + var label byte + var context [12]byte + rand.Reader.Read(context[:]) + + result := kdf.DeriveKey(label, context) + + fixedData := make([]byte, 1+1+12) // 1 byte label, 1 null byte, 12 bytes context. + fixedData[0] = label + copy(fixedData[2:], context[:]) + + return [][]byte{key, fixedData, result[:]}, nil + }, + } +} + +func cmdKdfFeedbackAft() command { + return command{ + requiredArgs: 5, // Number output bytes, PRF name, counter location string, key, number of counter bits, IV + handler: func(args [][]byte) ([][]byte, error) { + // The max supported output len for the KDF algorithm type is 4096 bits, making an int cast + // here safe. + // See https://pages.nist.gov/ACVP/draft-celi-acvp-kbkdf.html#section-7.3.2 + outputBytes := int(binary.LittleEndian.Uint32(args[0])) + prf := string(args[1]) + counterLocation := args[2] + key := args[3] + counterBits := binary.LittleEndian.Uint32(args[4]) + + if !strings.HasPrefix(prf, "HMAC-") { + return nil, fmt.Errorf("feedback KDF received unsupported PRF %q", prf) + } + prf = prf[len("HMAC-"):] + + h, err := lookupHash(prf) + if err != nil { + return nil, fmt.Errorf("feedback KDF received unsupported PRF %q: %w", prf, err) + } + + if !bytes.Equal(counterLocation, []byte("after fixed data")) { + return nil, fmt.Errorf("feedback KDF received unsupported counter location %q", string(counterLocation)) + } + + // The spec doesn't describe the "deferred" property for a KDF counterMode test case. + // BoringSSL's acvptool sends an empty key when deferred=true, but with the capabilities + // we register all test cases have deferred=false and provide a key from the populated + // keyIn property. + if len(key) == 0 { + return nil, errors.New("deferred test cases are not supported") + } + + if counterBits != 8 { + return nil, fmt.Errorf("feedback KDF received unsupported counter length %d", counterBits) + } + + var context [12]byte + rand.Reader.Read(context[:]) + fixedData := make([]byte, 1+1+12) // 1 byte label (we pick null), 1 null byte, 12 bytes context. + copy(fixedData[2:], context[:]) + + result := hkdf.Expand(h, key, string(fixedData[:]), outputBytes) + + return [][]byte{key, fixedData[:], result[:]}, nil + }, + } +} + +func cmdRsaKeyGenAft() command { + return command{ + requiredArgs: 1, // Modulus bit-size + handler: func(args [][]byte) ([][]byte, error) { + bitSize := binary.LittleEndian.Uint32(args[0]) + + key, err := getRSAKey((int)(bitSize)) + if err != nil { + return nil, fmt.Errorf("generating RSA key: %w", err) + } + + N, e, d, P, Q, _, _, _ := key.Export() + + eBytes := make([]byte, 4) + binary.BigEndian.PutUint32(eBytes, uint32(e)) + + return [][]byte{eBytes, P, Q, N, d}, nil + }, + } +} + +func cmdRsaSigGenAft(hashFunc func() fips140.Hash, hashName string, pss bool) command { + return command{ + requiredArgs: 2, // Modulus bit-size, message + handler: func(args [][]byte) ([][]byte, error) { + bitSize := binary.LittleEndian.Uint32(args[0]) + msg := args[1] + + key, err := getRSAKey((int)(bitSize)) + if err != nil { + return nil, fmt.Errorf("generating RSA key: %w", err) + } + + h := hashFunc() + h.Write(msg) + digest := h.Sum(nil) + + var sig []byte + if !pss { + sig, err = rsa.SignPKCS1v15(key, hashName, digest) + if err != nil { + return nil, fmt.Errorf("signing RSA message: %w", err) + } + } else { + sig, err = rsa.SignPSS(rand.Reader, key, hashFunc(), digest, h.Size()) + if err != nil { + return nil, fmt.Errorf("signing RSA message: %w", err) + } + } + + N, e, _, _, _, _, _, _ := key.Export() + eBytes := make([]byte, 4) + binary.BigEndian.PutUint32(eBytes, uint32(e)) + + return [][]byte{N, eBytes, sig}, nil + }, + } +} + +func cmdRsaSigVerAft(hashFunc func() fips140.Hash, hashName string, pss bool) command { + return command{ + requiredArgs: 4, // n, e, message, signature + handler: func(args [][]byte) ([][]byte, error) { + nBytes := args[0] + eBytes := args[1] + msg := args[2] + sig := args[3] + + paddedE := make([]byte, 4) + copy(paddedE[4-len(eBytes):], eBytes) + e := int(binary.BigEndian.Uint32(paddedE)) + + n, err := bigmod.NewModulus(nBytes) + if err != nil { + return nil, fmt.Errorf("invalid RSA modulus: %w", err) + } + + pub := &rsa.PublicKey{ + N: n, + E: e, + } + + h := hashFunc() + h.Write(msg) + digest := h.Sum(nil) + + if !pss { + err = rsa.VerifyPKCS1v15(pub, hashName, digest, sig) + } else { + err = rsa.VerifyPSS(pub, hashFunc(), digest, sig) + } + if err != nil { + return [][]byte{{0}}, nil + } + + return [][]byte{{1}}, nil + }, + } +} + +// rsaKeyCache caches generated keys by modulus bit-size. +var rsaKeyCache = map[int]*rsa.PrivateKey{} + +// getRSAKey returns a cached RSA private key with the specified modulus bit-size +// or generates one if necessary. +func getRSAKey(bits int) (*rsa.PrivateKey, error) { + if key, exists := rsaKeyCache[bits]; exists { + return key, nil + } + + key, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return nil, err + } + + rsaKeyCache[bits] = key + return key, nil +} + +func cmdOneStepNoCounterHmacAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 4, // key, info, salt, outBytes + handler: func(args [][]byte) ([][]byte, error) { + key := args[0] + info := args[1] + salt := args[2] + outBytes := binary.LittleEndian.Uint32(args[3]) + + mac := hmac.New(h, salt) + mac.Size() + + if outBytes != uint32(mac.Size()) { + return nil, fmt.Errorf("invalid output length: got %d, want %d", outBytes, mac.Size()) + } + + data := make([]byte, 0, len(key)+len(info)) + data = append(data, key...) + data = append(data, info...) + + mac.Write(data) + out := mac.Sum(nil) + + return [][]byte{out}, nil + }, + } +} + +func cmdKtsIfcInitiatorAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 3, // output bytes, n bytes, e bytes + handler: func(args [][]byte) ([][]byte, error) { + outputBytes := binary.LittleEndian.Uint32(args[0]) + nBytes := args[1] + eBytes := args[2] + + n, err := bigmod.NewModulus(nBytes) + if err != nil { + return nil, fmt.Errorf("invalid RSA modulus: %w", err) + } + + paddedE := make([]byte, 4) + copy(paddedE[4-len(eBytes):], eBytes) + e := int(binary.BigEndian.Uint32(paddedE)) + if e != 0x10001 { + return nil, errors.New("e must be 0x10001") + } + + pub := &rsa.PublicKey{ + N: n, + E: e, + } + + dkm := make([]byte, outputBytes) + if _, err := rand.Read(dkm); err != nil { + return nil, fmt.Errorf("failed to generate random DKM: %v", err) + } + + iutC, err := rsa.EncryptOAEP(h(), h(), rand.Reader, pub, dkm, nil) + if err != nil { + return nil, fmt.Errorf("OAEP encryption failed: %v", err) + } + + return [][]byte{iutC, dkm}, nil + }, + } +} + +func cmdKtsIfcResponderAft(h func() fips140.Hash) command { + return command{ + requiredArgs: 6, // n bytes, e bytes, p bytes, q bytes, d bytes, c bytes + handler: func(args [][]byte) ([][]byte, error) { + nBytes := args[0] + eBytes := args[1] + + pBytes := args[2] + qBytes := args[3] + dBytes := args[4] + + cBytes := args[5] + + paddedE := make([]byte, 4) + copy(paddedE[4-len(eBytes):], eBytes) + e := int(binary.BigEndian.Uint32(paddedE)) + if e != 0x10001 { + return nil, errors.New("e must be 0x10001") + } + + priv, err := rsa.NewPrivateKey(nBytes, int(e), dBytes, pBytes, qBytes) + if err != nil { + return nil, fmt.Errorf("failed to create private key: %v", err) + } + + dkm, err := rsa.DecryptOAEP(h(), h(), priv, cBytes, nil) + if err != nil { + return nil, fmt.Errorf("OAEP decryption failed: %v", err) + } + + return [][]byte{dkm}, nil + }, + } +} + +func TestACVP(t *testing.T) { + testenv.SkipIfShortAndSlow(t) + + const ( + bsslModule = "boringssl.googlesource.com/boringssl.git" + bsslVersion = "v0.0.0-20250207174145-0bb19f6126cb" + goAcvpModule = "github.com/cpu/go-acvp" + goAcvpVersion = "v0.0.0-20250126154732-de1ba727a0be" + ) + + // In crypto/tls/bogo_shim_test.go the test is skipped if run on a builder with runtime.GOOS == "windows" + // due to flaky networking. It may be necessary to do the same here. + + // Stat the acvp test config file so the test will be re-run if it changes, invalidating cached results + // from the old config. + if _, err := os.Stat("acvp_test.config.json"); err != nil { + t.Fatalf("failed to stat config file: %s", err) + } + + // Fetch the BSSL module and use the JSON output to find the absolute path to the dir. + bsslDir := cryptotest.FetchModule(t, bsslModule, bsslVersion) + + t.Log("building acvptool") + + // Build the acvptool binary. + toolPath := filepath.Join(t.TempDir(), "acvptool.exe") + goTool := testenv.GoToolPath(t) + cmd := testenv.Command(t, goTool, + "build", + "-o", toolPath, + "./util/fipstools/acvp/acvptool") + cmd.Dir = bsslDir + out := &strings.Builder{} + cmd.Stderr = out + if err := cmd.Run(); err != nil { + t.Fatalf("failed to build acvptool: %s\n%s", err, out.String()) + } + + // Similarly, fetch the ACVP data module that has vectors/expected answers. + dataDir := cryptotest.FetchModule(t, goAcvpModule, goAcvpVersion) + + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("failed to fetch cwd: %s", err) + } + configPath := filepath.Join(cwd, "acvp_test.config.json") + t.Logf("running check_expected.go\ncwd: %q\ndata_dir: %q\nconfig: %q\ntool: %q\nmodule-wrapper: %q\n", + cwd, dataDir, configPath, toolPath, os.Args[0]) + + // Run the check_expected test driver using the acvptool we built, and this test binary as the + // module wrapper. The file paths in the config file are specified relative to the dataDir root + // so we run the command from that dir. + args := []string{ + "run", + filepath.Join(bsslDir, "util/fipstools/acvp/acvptool/test/check_expected.go"), + "-tool", + toolPath, + // Note: module prefix must match Wrapper value in acvp_test.config.json. + "-module-wrappers", "go:" + os.Args[0], + "-tests", configPath, + } + cmd = testenv.Command(t, goTool, args...) + cmd.Dir = dataDir + cmd.Env = append(os.Environ(), + "ACVP_WRAPPER=1", + "GODEBUG=fips140=on", + ) + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("failed to run acvp tests: %s\n%s", err, string(output)) + } + t.Log(string(output)) +} + +func TestTooFewArgs(t *testing.T) { + commands["test"] = command{ + requiredArgs: 1, + handler: func(args [][]byte) ([][]byte, error) { + if gotArgs := len(args); gotArgs != 1 { + return nil, fmt.Errorf("expected 1 args, got %d", gotArgs) + } + return nil, nil + }, + } + + var output bytes.Buffer + err := processingLoop(mockRequest(t, "test", nil), &output) + if err == nil { + t.Fatalf("expected error, got nil") + } + expectedErr := "expected 1 args, got 0" + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("expected error to contain %q, got %v", expectedErr, err) + } +} + +func TestTooManyArgs(t *testing.T) { + commands["test"] = command{ + requiredArgs: 1, + handler: func(args [][]byte) ([][]byte, error) { + if gotArgs := len(args); gotArgs != 1 { + return nil, fmt.Errorf("expected 1 args, got %d", gotArgs) + } + return nil, nil + }, + } + + var output bytes.Buffer + err := processingLoop(mockRequest( + t, "test", [][]byte{[]byte("one"), []byte("two")}), &output) + if err == nil { + t.Fatalf("expected error, got nil") + } + expectedErr := "expected 1 args, got 2" + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("expected error to contain %q, got %v", expectedErr, err) + } +} + +func TestGetConfig(t *testing.T) { + var output bytes.Buffer + err := processingLoop(mockRequest(t, "getConfig", nil), &output) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + respArgs := readResponse(t, &output) + if len(respArgs) != 1 { + t.Fatalf("expected 1 response arg, got %d", len(respArgs)) + } + + if !bytes.Equal(respArgs[0], capabilitiesJson) { + t.Errorf("expected config %q, got %q", string(capabilitiesJson), string(respArgs[0])) + } +} + +func TestSha2256(t *testing.T) { + testMessage := []byte("gophers eat grass") + expectedDigest := []byte{ + 188, 142, 10, 214, 48, 236, 72, 143, 70, 216, 223, 205, 219, 69, 53, 29, + 205, 207, 162, 6, 14, 70, 113, 60, 251, 170, 201, 236, 119, 39, 141, 172, + } + + var output bytes.Buffer + err := processingLoop(mockRequest(t, "SHA2-256", [][]byte{testMessage}), &output) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + respArgs := readResponse(t, &output) + if len(respArgs) != 1 { + t.Fatalf("expected 1 response arg, got %d", len(respArgs)) + } + + if !bytes.Equal(respArgs[0], expectedDigest) { + t.Errorf("expected digest %v, got %v", expectedDigest, respArgs[0]) + } +} + +func mockRequest(t *testing.T, cmd string, args [][]byte) io.Reader { + t.Helper() + + msgData := append([][]byte{[]byte(cmd)}, args...) + + var buf bytes.Buffer + if err := writeResponse(&buf, msgData); err != nil { + t.Fatalf("writeResponse error: %v", err) + } + + return &buf +} + +func readResponse(t *testing.T, reader io.Reader) [][]byte { + var numArgs uint32 + if err := binary.Read(reader, binary.LittleEndian, &numArgs); err != nil { + t.Fatalf("failed to read response args count: %v", err) + } + + args, err := readArgs(reader, numArgs) + if err != nil { + t.Fatalf("failed to read %d response args: %v", numArgs, err) + } + + return args +} diff --git a/crypto/internal/alias/alias_test.go b/crypto/internal/fips140test/alias_test.go similarity index 87% rename from crypto/internal/alias/alias_test.go rename to crypto/internal/fips140test/alias_test.go index a68fb33667b..d3a3f823d4a 100644 --- a/crypto/internal/alias/alias_test.go +++ b/crypto/internal/fips140test/alias_test.go @@ -2,9 +2,13 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package alias +package fipstest -import "testing" +import ( + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" +) var a, b [100]byte @@ -28,11 +32,11 @@ var aliasingTests = []struct { } func testAliasing(t *testing.T, i int, x, y []byte, anyOverlap, inexactOverlap bool) { - any := AnyOverlap(x, y) + any := alias.AnyOverlap(x, y) if any != anyOverlap { t.Errorf("%d: wrong AnyOverlap result, expected %v, got %v", i, anyOverlap, any) } - inexact := InexactOverlap(x, y) + inexact := alias.InexactOverlap(x, y) if inexact != inexactOverlap { t.Errorf("%d: wrong InexactOverlap result, expected %v, got %v", i, inexactOverlap, any) } diff --git a/crypto/internal/fips140test/cast_test.go b/crypto/internal/fips140test/cast_test.go new file mode 100644 index 00000000000..7ec62dc411c --- /dev/null +++ b/crypto/internal/fips140test/cast_test.go @@ -0,0 +1,191 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "encoding/pem" + "fmt" + "internal/testenv" + "io/fs" + "os" + "regexp" + "slices" + "strings" + "testing" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/x509" + + // Import packages that define CASTs to test them. + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls12" + _ "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdh" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ed25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" +) + +var allCASTs = []string{ + "AES-CBC", + "CTR_DRBG", + "CounterKDF", + "DetECDSA P-256 SHA2-512 sign", + "ECDH PCT", + "ECDSA P-256 SHA2-512 sign and verify", + "ECDSA PCT", + "Ed25519 sign and verify", + "Ed25519 sign and verify PCT", + "HKDF-SHA2-256", + "HMAC-SHA2-256", + "KAS-ECC-SSC P-256", + "ML-KEM PCT", + "ML-KEM PCT", + "ML-KEM PCT", + "ML-KEM PCT", + "ML-KEM-768", + "PBKDF2", + "RSA sign and verify PCT", + "RSASSA-PKCS-v1.5 2048-bit sign and verify", + "SHA2-256", + "SHA2-512", + "TLSv1.2-SHA2-256", + "TLSv1.3-SHA2-256", + "cSHAKE128", +} + +func TestAllCASTs(t *testing.T) { + testenv.MustHaveSource(t) + + // Ask "go list" for the location of the crypto/internal/fips140 tree, as it + // might be the unpacked frozen tree selected with GOFIPS140. + cmd := testenv.Command(t, testenv.GoToolPath(t), "list", "-f", `{{.Dir}}`, "crypto/internal/fips140") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("go list: %v\n%s", err, out) + } + fipsDir := strings.TrimSpace(string(out)) + t.Logf("FIPS module directory: %s", fipsDir) + + // Find all invocations of fips140.CAST or fips140.PCT. + var foundCASTs []string + castRe := regexp.MustCompile(`fips140\.(CAST|PCT)\("([^"]+)"`) + if err := fs.WalkDir(os.DirFS(fipsDir), ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() || !strings.HasSuffix(path, ".go") { + return nil + } + data, err := os.ReadFile(fipsDir + "/" + path) + if err != nil { + return err + } + for _, m := range castRe.FindAllSubmatch(data, -1) { + foundCASTs = append(foundCASTs, string(m[2])) + } + return nil + }); err != nil { + t.Fatalf("WalkDir: %v", err) + } + + slices.Sort(foundCASTs) + if !slices.Equal(foundCASTs, allCASTs) { + t.Errorf("AllCASTs is out of date. Found CASTs: %#v", foundCASTs) + } +} + +// TestConditionals causes the conditional CASTs and PCTs to be invoked. +func TestConditionals(t *testing.T) { + mlkem.GenerateKey768() + k, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + ecdh.ECDH(ecdh.P256(), k, k.PublicKey()) + kDSA, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, kDSA, make([]byte, 32)) + k25519, err := ed25519.GenerateKey() + if err != nil { + t.Fatal(err) + } + ed25519.Sign(k25519, make([]byte, 32)) + rsa.VerifyPKCS1v15(&rsa.PublicKey{}, "", nil, nil) + // Parse an RSA key to hit the PCT rather than generating one (which is slow). + block, _ := pem.Decode([]byte(strings.ReplaceAll( + `-----BEGIN RSA TESTING KEY----- +MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso +tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE +89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU +l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s +B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59 +3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+ +dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI +FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J +aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2 +BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx +IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/ +fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u +pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT +Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl +u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD +fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X +Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE +k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo +qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS +CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ +XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw +AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r +UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0 +2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5 +7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3 +-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY"))) + if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { + t.Fatal(err) + } + t.Log("completed successfully") +} + +func TestCASTFailures(t *testing.T) { + moduleStatus(t) + testenv.MustHaveExec(t) + + for _, name := range allCASTs { + t.Run(name, func(t *testing.T) { + // Don't parallelize if running in verbose mode, to produce a less + // confusing recoding for the validation lab. + if !testing.Verbose() { + t.Parallel() + } + t.Logf("CAST/PCT succeeded: %s", name) + t.Logf("Testing CAST/PCT failure...") + cmd := testenv.Command(t, testenv.Executable(t), "-test.run=TestConditionals", "-test.v") + cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=failfipscast=%s,fips140=on", name)) + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + if err == nil { + t.Fatal("Test did not fail as expected") + } + if strings.Contains(string(out), "completed successfully") { + t.Errorf("CAST/PCT %s failure did not stop the program", name) + } else { + t.Logf("CAST/PCT %s failed as expected and caused the program to exit", name) + } + }) + } +} diff --git a/crypto/internal/fips140test/check_test.go b/crypto/internal/fips140test/check_test.go new file mode 100644 index 00000000000..c6903a62330 --- /dev/null +++ b/crypto/internal/fips140test/check_test.go @@ -0,0 +1,169 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "bytes" + . "crypto/internal/fips140/check" + "fmt" + "internal/abi" + "internal/godebug" + "internal/testenv" + "os" + "path/filepath" + "runtime" + "testing" + "unicode" + "unsafe" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/check/checktest" +) + +func TestIntegrityCheck(t *testing.T) { + if Verified { + t.Logf("verified") + return + } + + if godebug.New("fips140").Value() == "on" { + t.Fatalf("GODEBUG=fips140=on but verification did not run") + } + + if err := fips140.Supported(); err != nil { + t.Skipf("skipping: %v", err) + } + + cmd := testenv.Command(t, os.Args[0], "-test.v", "-test.run=TestIntegrityCheck") + cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=on") + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("GODEBUG=fips140=on %v failed: %v\n%s", cmd.Args, err, out) + } + t.Logf("exec'ed GODEBUG=fips140=on and succeeded:\n%s", out) +} + +func TestIntegrityCheckFailure(t *testing.T) { + moduleStatus(t) + testenv.MustHaveExec(t) + if err := fips140.Supported(); err != nil { + t.Skipf("skipping: %v", err) + } + + bin, err := os.ReadFile(os.Args[0]) + if err != nil { + t.Fatal(err) + } + + // Replace the expected module checksum with a different value. + bin = bytes.ReplaceAll(bin, Linkinfo.Sum[:], bytes.Repeat([]byte("X"), len(Linkinfo.Sum))) + + binPath := filepath.Join(t.TempDir(), "fips140test.exe") + if err := os.WriteFile(binPath, bin, 0o755); err != nil { + t.Fatal(err) + } + + if runtime.GOOS == "darwin" { + // Regenerate the macOS ad-hoc code signature. + cmd := testenv.Command(t, "codesign", "-s", "-", "-f", binPath) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("codesign failed: %v\n%s", err, out) + } + } + + t.Logf("running modified binary...") + cmd := testenv.Command(t, binPath, "-test.v", "-test.run=TestIntegrityCheck$") + cmd.Env = append(cmd.Environ(), "GODEBUG=fips140=on") + out, err := cmd.CombinedOutput() + t.Logf("%s", out) + if err == nil { + t.Errorf("modified binary did not fail as expected") + } + if !bytes.Contains(out, []byte("fips140: verification mismatch")) { + t.Errorf("modified binary did not fail with expected message") + } + if bytes.Contains(out, []byte("verified")) { + t.Errorf("modified binary did not exit") + } +} + +func TestIntegrityCheckInfo(t *testing.T) { + if err := fips140.Supported(); err != nil { + t.Skipf("skipping: %v", err) + } + + // Check that the checktest symbols are initialized properly. + if checktest.NOPTRDATA != 1 { + t.Errorf("checktest.NOPTRDATA = %d, want 1", checktest.NOPTRDATA) + } + if checktest.RODATA != 2 { + t.Errorf("checktest.RODATA = %d, want 2", checktest.RODATA) + } + if checktest.DATA.P != &checktest.NOPTRDATA { + t.Errorf("checktest.DATA.P = %p, want &checktest.NOPTRDATA (%p)", checktest.DATA.P, &checktest.NOPTRDATA) + } + if checktest.DATA.X != 3 { + t.Errorf("checktest.DATA.X = %d, want 3", checktest.DATA.X) + } + if checktest.NOPTRBSS != 0 { + t.Errorf("checktest.NOPTRBSS = %d, want 0", checktest.NOPTRBSS) + } + if checktest.BSS != nil { + t.Errorf("checktest.BSS = %p, want nil", checktest.BSS) + } + if p := checktest.PtrStaticData(); p != nil && *p != 10 { + t.Errorf("*checktest.PtrStaticData() = %d, want 10", *p) + } + + // Check that the checktest symbols are in the right go:fipsinfo sections. + sect := func(i int, name string, p unsafe.Pointer) { + s := Linkinfo.Sects[i] + if !(uintptr(s.Start) <= uintptr(p) && uintptr(p) < uintptr(s.End)) { + t.Errorf("checktest.%s (%#x) not in section #%d (%#x..%#x)", name, p, i, s.Start, s.End) + } + } + sect(0, "TEXT", unsafe.Pointer(abi.FuncPCABIInternal(checktest.TEXT))) + if p := checktest.PtrStaticText(); p != nil { + sect(0, "StaticText", p) + } + sect(1, "RODATA", unsafe.Pointer(&checktest.RODATA)) + sect(2, "NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA)) + if p := checktest.PtrStaticData(); p != nil { + sect(2, "StaticData", unsafe.Pointer(p)) + } + sect(3, "DATA", unsafe.Pointer(&checktest.DATA)) + + // Check that some symbols are not in FIPS sections. + no := func(name string, p unsafe.Pointer, ix ...int) { + for _, i := range ix { + s := Linkinfo.Sects[i] + if uintptr(s.Start) <= uintptr(p) && uintptr(p) < uintptr(s.End) { + t.Errorf("%s (%#x) unexpectedly in section #%d (%#x..%#x)", name, p, i, s.Start, s.End) + } + } + } + + // Check that the symbols are not in unexpected sections (that is, no overlaps). + no("checktest.TEXT", unsafe.Pointer(abi.FuncPCABIInternal(checktest.TEXT)), 1, 2, 3) + no("checktest.RODATA", unsafe.Pointer(&checktest.RODATA), 0, 2, 3) + no("checktest.NOPTRDATA", unsafe.Pointer(&checktest.NOPTRDATA), 0, 1, 3) + no("checktest.DATA", unsafe.Pointer(&checktest.DATA), 0, 1, 2) + + // Check that non-FIPS symbols are not in any of the sections. + no("fmt.Printf", unsafe.Pointer(abi.FuncPCABIInternal(fmt.Printf)), 0, 1, 2, 3) // TEXT + no("unicode.Categories", unsafe.Pointer(&unicode.Categories), 0, 1, 2, 3) // BSS + no("unicode.ASCII_Hex_Digit", unsafe.Pointer(&unicode.ASCII_Hex_Digit), 0, 1, 2, 3) // DATA + + // Check that we have enough data in total. + // On arm64 the fips sections in this test currently total 23 kB. + n := uintptr(0) + for _, s := range Linkinfo.Sects { + n += uintptr(s.End) - uintptr(s.Start) + } + if n < 16*1024 { + t.Fatalf("fips sections not big enough: %d, want at least 16 kB", n) + } +} diff --git a/crypto/internal/fips140test/cmac_test.go b/crypto/internal/fips140test/cmac_test.go new file mode 100644 index 00000000000..d3cf53479e1 --- /dev/null +++ b/crypto/internal/fips140test/cmac_test.go @@ -0,0 +1,48 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "bytes" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" +) + +func TestCMAC(t *testing.T) { + // https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/AES_CMAC.pdf + key := "2B7E1516 28AED2A6 ABF71588 09CF4F3C" + tests := []struct { + in, out string + }{ + { + "", + "BB1D6929 E9593728 7FA37D12 9B756746", + }, + { + "6BC1BEE2 2E409F96 E93D7E11 7393172A", + "070A16B4 6B4D4144 F79BDD9D D04A287C", + }, + { + "6BC1BEE2 2E409F96 E93D7E11 7393172A AE2D8A57", + "7D85449E A6EA19C8 23A7BF78 837DFADE", + }, + } + + b, err := aes.New(decodeHex(t, key)) + if err != nil { + t.Fatal(err) + } + c := gcm.NewCMAC(b) + for i, test := range tests { + in := decodeHex(t, test.in) + out := decodeHex(t, test.out) + got := c.MAC(in) + if !bytes.Equal(got[:], out) { + t.Errorf("test %d: got %x, want %x", i, got, out) + } + } +} diff --git a/crypto/internal/fips140test/ctrdrbg_test.go b/crypto/internal/fips140test/ctrdrbg_test.go new file mode 100644 index 00000000000..30a0730f352 --- /dev/null +++ b/crypto/internal/fips140test/ctrdrbg_test.go @@ -0,0 +1,41 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "bytes" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" +) + +func TestCounterDRBG(t *testing.T) { + // https://github.com/usnistgov/ACVP-Server/blob/fb44dce/gen-val/json-files/ctrDRBG-1.0/prompt.json#L4447-L4482 + + entropyInput := decodeHex(t, "9FCBB4CCC0135C484BDED061DA9FD70748682FE84166B97FF53F9AA1909B2E95D3D529C0F453B3AC575D12AA441CC5CD") + persoString := decodeHex(t, "2C9FED0B39556CDBE699EBCA2A0EC7EECB287E8744475050C572FA8AE9ED0A4A7D6F1CABF1C4278532FB20AF7D64BD32") + reseedEntropy := decodeHex(t, "913C0DA19B010EDDD55A7A4F3F713EEF5B1534D34360A7EC376AE71A6B340043CC7726F762CB853453F399B3A645062A") + reseedAdditional := decodeHex(t, "2D9D4EC141A22E6CD2F6EE4F6719CF6BDF95CFE50B8D5EA6C87D38B4B872706FFF80B0380BB90E9C42D11D6526E56C29") + additional1 := decodeHex(t, "A642F06D327828F3E84564A3E37D60C157073B95864CA07981B0189668A0D978CD5DC68F06801CEFF0DC839A312B028E") + additional2 := decodeHex(t, "9DB14BABFA9107C88BA92073C0B4A65E89147EA06D74B894142979482F452915B35B5636F9B8A951759735ADE7C8D5D1") + returnedBits := decodeHex(t, "F10C645683FF0131254052ED4C698122B46B563654C29D728AC191CA4AAEFE649EEFE4C6FC33B25BB739294DD5CF578099F856C98D98000CBF971F1E6EA900822FF8C110118F6520471744D3F8A3F5C7D568494240E57F5488AF9C9F9F4E7322F56CCD843C0DBFCE9170C02E205389420527F23EDB3369D9FCC5E34901B5BA4EB71B973FC7982FFE0899FF7FE53EE0C4F51A3EF93EF9C6D4D279DD7536F8776BE94AAA05E89EF6E6AEE8832B4B42FFCA5FB91EC0273F9EF945865512889B0C5EE141D1B38DF827D2A694835561628C6F9B093A01A835F07ADBB9E03FEBF93389E8F3B86E1E0ABF1F9958FA286AD995289C2F606D1A9043A166C1AFE8D00769C712650819C9068A4BD22717C98338395A7BA6E95B5178BFBF4EFB0F05A91713BA8BF2127A6BA1EDFA6D1CAB05C03EE0D2AFE1DA4EB8F2C579EC872FF4B602027EF4BDCF2F4B01423F8E600A13D7CACB6AB83263BA58F907694AF614A6724FD0E4C627A0D91DDC6716C697FACE6F4808A4F37B731DE4E0CD4766CEADAAAF47992505299C72AC1A6E9A8335B8D7E501B3841188D0DA4DE5267674444DC2B0CF9F010756FA865A25CA3F1B24C34E845B2259926B6A867A7684DE68A6137C4FB0F47A2E54AE9E6455BEBA0B0A9629644FE9E378EE95386443BA977124FFD1192E9F460684C7B09FA99F5F93F04F56FD7955E042187887CE696F1934017E458B16B5C9") + + // We don't support personalization strings, but the pre-generated JSON + // vectors always use them, so just pre-mix them. + var seed [drbg.SeedSize]byte + subtle.XORBytes(seed[:], entropyInput, persoString) + c := drbg.NewCounter(&seed) + + c.Reseed((*[48]byte)(reseedEntropy), (*[48]byte)(reseedAdditional)) + + buf := make([]byte, len(returnedBits)) + c.Generate(buf, (*[48]byte)(additional1)) + + c.Generate(buf, (*[48]byte)(additional2)) + if !bytes.Equal(buf, returnedBits) { + t.Errorf("unexpected output:\n%x\n%x", buf, returnedBits) + } +} diff --git a/crypto/internal/fips140test/edwards25519_test.go b/crypto/internal/fips140test/edwards25519_test.go new file mode 100644 index 00000000000..f2a0f8b136a --- /dev/null +++ b/crypto/internal/fips140test/edwards25519_test.go @@ -0,0 +1,27 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + . "crypto/internal/fips140/edwards25519" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" +) + +var testAllocationsSink byte + +func TestEdwards25519Allocations(t *testing.T) { + cryptotest.SkipTestAllocations(t) + if allocs := testing.AllocsPerRun(100, func() { + p := NewIdentityPoint() + p.Add(p, NewGeneratorPoint()) + s := NewScalar() + testAllocationsSink ^= s.Bytes()[0] + testAllocationsSink ^= p.Bytes()[0] + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1v", allocs) + } +} diff --git a/crypto/internal/fips140test/fips_test.go b/crypto/internal/fips140test/fips_test.go new file mode 100644 index 00000000000..23a4a5876a8 --- /dev/null +++ b/crypto/internal/fips140test/fips_test.go @@ -0,0 +1,443 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fipstest collects external tests that would ordinarily live in +// crypto/internal/fips140/... packages. That tree gets snapshot at each +// validation, while we want tests to evolve and still apply to all versions of +// the module. Also, we can't fix failing tests in a module snapshot, so we need +// to either minimize, skip, or remove them. Finally, the module needs to avoid +// importing internal packages like testenv and cryptotest to avoid locking in +// their APIs. +// +// Also, this package includes the ACVP and functional testing harnesses. +package fipstest + +import ( + "bytes" + "encoding/hex" + "strings" + "testing" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/check" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdh" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ecdsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ed25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/pbkdf2" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls12" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" +) + +func moduleStatus(t *testing.T) { + if fips140.Enabled { + t.Log("FIPS 140-3 mode enabled") + } else { + t.Log("FIPS 140-3 mode not enabled") + } + + t.Logf("Module name: %s", fips140.Name()) + t.Logf("Module version: %s", fips140.Version()) + + if noPAAPAI { + t.Log("PAA/PAI disabled") + } else { + t.Log("PAA/PAI enabled") + } + + if check.Verified { + t.Log("FIPS 140-3 integrity self-check succeeded") + } else { + t.Log("FIPS 140-3 integrity self-check not succeeded") + } +} + +func TestFIPS140(t *testing.T) { + moduleStatus(t) + if boring.Enabled { + t.Skip("Go+BoringCrypto shims prevent the service indicator from being set") + } + + aesKey := make([]byte, 128/8) + aesIV := make([]byte, aes.BlockSize) + plaintext := []byte("Go Cryptographic Module TestFIPS140 plaintext...") + plaintextSHA256 := decodeHex(t, "06b2614e2ef315832b23f5d0ff70294d8ddd3889527dfbe75707fe41da929325") + aesBlock, err := aes.New(aesKey) + fatalIfErr(t, err) + + t.Run("AES-CTR", func(t *testing.T) { + ensureServiceIndicator(t) + ctr := aes.NewCTR(aesBlock, aesIV) + ciphertext := make([]byte, len(plaintext)) + ctr.XORKeyStream(ciphertext, plaintext) + t.Logf("AES-CTR ciphertext: %x", ciphertext) + out := make([]byte, len(plaintext)) + ctr = aes.NewCTR(aesBlock, aesIV) + ctr.XORKeyStream(out, ciphertext) + t.Logf("AES-CTR decrypted plaintext: %s", out) + if !bytes.Equal(plaintext, out) { + t.Errorf("AES-CTR round trip failed") + } + }) + + t.Run("AES-CBC", func(t *testing.T) { + ensureServiceIndicator(t) + cbcEnc := aes.NewCBCEncrypter(aesBlock, [16]byte(aesIV)) + ciphertext := make([]byte, len(plaintext)) + cbcEnc.CryptBlocks(ciphertext, plaintext) + t.Logf("AES-CBC ciphertext: %x", ciphertext) + cbcDec := aes.NewCBCDecrypter(aesBlock, [16]byte(aesIV)) + out := make([]byte, len(plaintext)) + cbcDec.CryptBlocks(out, ciphertext) + t.Logf("AES-CBC decrypted plaintext: %s", out) + if !bytes.Equal(plaintext, out) { + t.Errorf("AES-CBC round trip failed") + } + }) + + t.Run("AES-GCM", func(t *testing.T) { + ensureServiceIndicator(t) + g, err := gcm.New(aesBlock, 12, 16) + fatalIfErr(t, err) + nonce := make([]byte, 12) + ciphertext := make([]byte, len(plaintext)+g.Overhead()) + gcm.SealWithRandomNonce(g, nonce, ciphertext, plaintext, nil) + t.Logf("AES-GCM ciphertext: %x", ciphertext) + out, err := g.Open(nil, nonce, ciphertext, nil) + fatalIfErr(t, err) + t.Logf("AES-GCM decrypted plaintext: %s", out) + if !bytes.Equal(plaintext, out) { + t.Errorf("AES-GCM round trip failed") + } + }) + + t.Run("Counter KDF", func(t *testing.T) { + ensureServiceIndicator(t) + k := gcm.NewCounterKDF(aesBlock) + context := [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + key := k.DeriveKey(0x01, context) + t.Logf("Counter KDF key: %x", key) + }) + + t.Run("KAS-ECC-SSC ephemeralUnified", func(t *testing.T) { + ensureServiceIndicator(t) + k, err := ecdh.GenerateKey(ecdh.P256(), rand.Reader) + fatalIfErr(t, err) + pk := k.PublicKey() + shared, err := ecdh.ECDH(ecdh.P256(), k, pk) + fatalIfErr(t, err) + t.Logf("KAS-ECC-SSC shared secret: %x", shared) + }) + + t.Run("ECDSA KeyGen, SigGen, SigVer", func(t *testing.T) { + ensureServiceIndicator(t) + k, err := ecdsa.GenerateKey(ecdsa.P256(), rand.Reader) + fatalIfErr(t, err) + + sig, err := ecdsa.Sign(ecdsa.P256(), sha256.New, k, rand.Reader, plaintextSHA256) + fatalIfErr(t, err) + t.Logf("ECDSA signature: %x", sig) + err = ecdsa.Verify(ecdsa.P256(), k.PublicKey(), plaintextSHA256, sig) + if err != nil { + t.Errorf("ECDSA signature verification failed") + } + + sig, err = ecdsa.SignDeterministic(ecdsa.P256(), sha256.New, k, plaintextSHA256) + fatalIfErr(t, err) + t.Logf("ECDSA deterministic signature: %x", sig) + err = ecdsa.Verify(ecdsa.P256(), k.PublicKey(), plaintextSHA256, sig) + if err != nil { + t.Errorf("ECDSA deterministic signature verification failed") + } + }) + + t.Run("EDDSA KeyGen, SigGen, SigVer", func(t *testing.T) { + ensureServiceIndicator(t) + k, err := ed25519.GenerateKey() + fatalIfErr(t, err) + + sig := ed25519.Sign(k, plaintext) + t.Logf("EDDSA signature: %x", sig) + + pk, err := ed25519.NewPublicKey(k.PublicKey()) + fatalIfErr(t, err) + err = ed25519.Verify(pk, plaintext, sig) + if err != nil { + t.Errorf("EDDSA signature verification failed") + } + }) + + t.Run("ctrDRBG", func(t *testing.T) { + ensureServiceIndicator(t) + r := drbg.NewCounter((*[48]byte)(plaintext)) + r.Reseed((*[48]byte)(plaintext), (*[48]byte)(plaintext)) + out := make([]byte, 16) + r.Generate(out, (*[48]byte)(plaintext)) + t.Logf("ctrDRBG output: %x", out) + }) + + t.Run("HMAC", func(t *testing.T) { + ensureServiceIndicator(t) + h := hmac.New(sha256.New, plaintext) + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("HMAC output: %x", out) + }) + + t.Run("ML-KEM KeyGen, Encap, Decap", func(t *testing.T) { + ensureServiceIndicator(t) + k, err := mlkem.GenerateKey768() + fatalIfErr(t, err) + + ss, c := k.EncapsulationKey().Encapsulate() + t.Logf("ML-KEM encapsulation: %x", c) + + ss2, err := k.Decapsulate(c) + fatalIfErr(t, err) + t.Logf("ML-KEM shared secret: %x", ss) + if !bytes.Equal(ss, ss2) { + t.Errorf("ML-KEM round trip failed") + } + }) + + var rsaKey *rsa.PrivateKey + t.Run("RSA KeyGen", func(t *testing.T) { + ensureServiceIndicator(t) + var err error + rsaKey, err = rsa.GenerateKey(rand.Reader, 2048) + fatalIfErr(t, err) + t.Log("RSA key generated") + }) + + t.Run("RSA SigGen, SigVer PKCS 1.5", func(t *testing.T) { + ensureServiceIndicator(t) + sig, err := rsa.SignPKCS1v15(rsaKey, "SHA-256", plaintextSHA256) + fatalIfErr(t, err) + t.Logf("RSA PKCS1v15 signature: %x", sig) + + err = rsa.VerifyPKCS1v15(rsaKey.PublicKey(), "SHA-256", plaintextSHA256, sig) + fatalIfErr(t, err) + }) + + t.Run("RSA SigGen, SigVer PSS", func(t *testing.T) { + ensureServiceIndicator(t) + sig, err := rsa.SignPSS(rand.Reader, rsaKey, sha256.New(), plaintextSHA256, 16) + fatalIfErr(t, err) + t.Logf("RSA PSS signature: %x", sig) + + err = rsa.VerifyPSS(rsaKey.PublicKey(), sha256.New(), plaintextSHA256, sig) + fatalIfErr(t, err) + }) + + t.Run("KTS IFC OAEP", func(t *testing.T) { + ensureServiceIndicator(t) + c, err := rsa.EncryptOAEP(sha256.New(), sha256.New(), rand.Reader, rsaKey.PublicKey(), plaintextSHA256, nil) + fatalIfErr(t, err) + t.Logf("RSA OAEP ciphertext: %x", c) + + out, err := rsa.DecryptOAEP(sha256.New(), sha256.New(), rsaKey, c, nil) + fatalIfErr(t, err) + t.Logf("RSA OAEP decrypted plaintext: %x", out) + if !bytes.Equal(plaintextSHA256, out) { + t.Errorf("RSA OAEP round trip failed") + } + }) + + t.Run("SHA2-224", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha256.New224() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA2-224 output: %x", out) + }) + + t.Run("SHA2-256", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha256.New() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA2-256 output: %x", out) + }) + + t.Run("SHA2-384", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha512.New384() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA2-384 output: %x", out) + }) + + t.Run("SHA2-512", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha512.New() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA2-512 output: %x", out) + }) + + t.Run("SHA2-512/224", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha512.New512_224() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA2-512/224 output: %x", out) + }) + + t.Run("SHA2-512/256", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha512.New512_256() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA2-512/256 output: %x", out) + }) + + t.Run("SHA3-224", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.New224() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA3-224 output: %x", out) + }) + + t.Run("SHA3-256", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.New256() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA3-256 output: %x", out) + }) + + t.Run("SHA3-384", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.New384() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA3-384 output: %x", out) + }) + + t.Run("SHA3-512", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.New512() + h.Write(plaintext) + out := h.Sum(nil) + t.Logf("SHA3-512 output: %x", out) + }) + + t.Run("SHAKE-128", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.NewShake128() + h.Write(plaintext) + out := make([]byte, 16) + h.Read(out) + t.Logf("SHAKE-128 output: %x", out) + }) + + t.Run("SHAKE-256", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.NewShake256() + h.Write(plaintext) + out := make([]byte, 16) + h.Read(out) + t.Logf("SHAKE-256 output: %x", out) + }) + + t.Run("cSHAKE-128", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.NewCShake128(nil, []byte("test")) + h.Write(plaintext) + out := make([]byte, 16) + h.Read(out) + t.Logf("cSHAKE-128 output: %x", out) + }) + + t.Run("cSHAKE-256", func(t *testing.T) { + ensureServiceIndicator(t) + h := sha3.NewCShake256(nil, []byte("test")) + h.Write(plaintext) + out := make([]byte, 16) + h.Read(out) + t.Logf("cSHAKE-256 output: %x", out) + }) + + t.Run("KDA HKDF", func(t *testing.T) { + ensureServiceIndicator(t) + key := hkdf.Key(sha256.New, plaintextSHA256, []byte("salt"), "info", 16) + t.Logf("HKDF key: %x", key) + }) + + t.Run("KDA OneStepNoCounter", func(t *testing.T) { + ensureServiceIndicator(t) + key := hkdf.Extract(sha256.New, plaintextSHA256, []byte("salt")) + t.Logf("KDA OneStepNoCounter key: %x", key) + }) + + t.Run("Feedback KDF", func(t *testing.T) { + ensureServiceIndicator(t) + key := hkdf.Expand(sha256.New, plaintextSHA256, "info", 16) + t.Logf("Feedback KDF key: %x", key) + }) + + t.Run("PBKDF", func(t *testing.T) { + ensureServiceIndicator(t) + key, err := pbkdf2.Key(sha256.New, "password", plaintextSHA256, 2, 16) + fatalIfErr(t, err) + t.Logf("PBKDF key: %x", key) + }) + + t.Run("KDF TLS v1.2 CVL", func(t *testing.T) { + ensureServiceIndicator(t) + key := tls12.MasterSecret(sha256.New, plaintextSHA256, []byte("test")) + t.Logf("TLS v1.2 CVL Master Secret: %x", key) + }) + + t.Run("KDF TLS v1.3 CVL", func(t *testing.T) { + ensureServiceIndicator(t) + es := tls13.NewEarlySecret(sha256.New, plaintextSHA256) + hs := es.HandshakeSecret(plaintextSHA256) + ms := hs.MasterSecret() + client := ms.ClientApplicationTrafficSecret(sha256.New()) + server := ms.ServerApplicationTrafficSecret(sha256.New()) + t.Logf("TLS v1.3 CVL Application Traffic Secrets: client %x, server %x", client, server) + }) +} + +func ensureServiceIndicator(t *testing.T) { + fips140.ResetServiceIndicator() + t.Cleanup(func() { + if fips140.ServiceIndicator() { + t.Logf("Service indicator is set") + } else { + t.Errorf("Service indicator is not set") + } + }) +} + +func fatalIfErr(t *testing.T, err error) { + t.Helper() + if err != nil { + t.Fatal(err) + } +} + +func decodeHex(t *testing.T, s string) []byte { + t.Helper() + s = strings.ReplaceAll(s, " ", "") + b, err := hex.DecodeString(s) + if err != nil { + t.Fatal(err) + } + return b +} diff --git a/crypto/internal/fips140test/indicator_test.go b/crypto/internal/fips140test/indicator_test.go new file mode 100644 index 00000000000..2b2b7ff90fd --- /dev/null +++ b/crypto/internal/fips140test/indicator_test.go @@ -0,0 +1,77 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +func TestIndicator(t *testing.T) { + fips140.ResetServiceIndicator() + if fips140.ServiceIndicator() { + t.Error("indicator should be false if no calls are made") + } + + fips140.ResetServiceIndicator() + fips140.RecordApproved() + if !fips140.ServiceIndicator() { + t.Error("indicator should be true if RecordApproved is called") + } + + fips140.ResetServiceIndicator() + fips140.RecordApproved() + fips140.RecordApproved() + if !fips140.ServiceIndicator() { + t.Error("indicator should be true if RecordApproved is called multiple times") + } + + fips140.ResetServiceIndicator() + fips140.RecordNonApproved() + if fips140.ServiceIndicator() { + t.Error("indicator should be false if RecordNonApproved is called") + } + + fips140.ResetServiceIndicator() + fips140.RecordApproved() + fips140.RecordNonApproved() + if fips140.ServiceIndicator() { + t.Error("indicator should be false if both RecordApproved and RecordNonApproved are called") + } + + fips140.ResetServiceIndicator() + fips140.RecordNonApproved() + fips140.RecordApproved() + if fips140.ServiceIndicator() { + t.Error("indicator should be false if both RecordNonApproved and RecordApproved are called") + } + + fips140.ResetServiceIndicator() + fips140.RecordNonApproved() + done := make(chan struct{}) + go func() { + fips140.ResetServiceIndicator() + fips140.RecordApproved() + close(done) + }() + <-done + if fips140.ServiceIndicator() { + t.Error("indicator should be false if RecordApproved is called in a different goroutine") + } + + fips140.ResetServiceIndicator() + fips140.RecordApproved() + done = make(chan struct{}) + go func() { + fips140.ResetServiceIndicator() + fips140.RecordNonApproved() + close(done) + }() + <-done + if !fips140.ServiceIndicator() { + t.Error("indicator should be true if RecordNonApproved is called in a different goroutine") + } +} diff --git a/crypto/internal/nistec/p256_ordinv_test.go b/crypto/internal/fips140test/nistec_ordinv_test.go similarity index 96% rename from crypto/internal/nistec/p256_ordinv_test.go rename to crypto/internal/fips140test/nistec_ordinv_test.go index 0be5a888bba..95175b93908 100644 --- a/crypto/internal/nistec/p256_ordinv_test.go +++ b/crypto/internal/fips140test/nistec_ordinv_test.go @@ -4,14 +4,15 @@ //go:build (amd64 || arm64) && !purego -package nistec_test +package fipstest import ( "bytes" - "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/crypto/internal/nistec" "math/big" "testing" + + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" ) func TestP256OrdInverse(t *testing.T) { diff --git a/crypto/internal/nistec/nistec_test.go b/crypto/internal/fips140test/nistec_test.go similarity index 80% rename from crypto/internal/nistec/nistec_test.go rename to crypto/internal/fips140test/nistec_test.go index 159c104fa34..64fb240aff6 100644 --- a/crypto/internal/nistec/nistec_test.go +++ b/crypto/internal/fips140test/nistec_test.go @@ -2,22 +2,22 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package nistec_test +package fipstest import ( "bytes" "fmt" - "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/crypto/internal/nistec" - "github.com/runZeroInc/excrypto/internal/testenv" "math/big" "math/rand" "testing" -) -func TestAllocations(t *testing.T) { - testenv.SkipIfOptimizationOff(t) + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/nistec" +) +func TestNISTECAllocations(t *testing.T) { + cryptotest.SkipTestAllocations(t) t.Run("P224", func(t *testing.T) { if allocs := testing.AllocsPerRun(10, func() { p := nistec.NewP224Point().SetGenerator() @@ -252,60 +252,3 @@ func testScalarMult[P nistPoint[P]](t *testing.T, newPoint func() P, c elliptic. }) } } - -func fatalIfErr(t *testing.T, err error) { - t.Helper() - if err != nil { - t.Fatal(err) - } -} - -func BenchmarkScalarMult(b *testing.B) { - b.Run("P224", func(b *testing.B) { - benchmarkScalarMult(b, nistec.NewP224Point().SetGenerator(), 28) - }) - b.Run("P256", func(b *testing.B) { - benchmarkScalarMult(b, nistec.NewP256Point().SetGenerator(), 32) - }) - b.Run("P384", func(b *testing.B) { - benchmarkScalarMult(b, nistec.NewP384Point().SetGenerator(), 48) - }) - b.Run("P521", func(b *testing.B) { - benchmarkScalarMult(b, nistec.NewP521Point().SetGenerator(), 66) - }) -} - -func benchmarkScalarMult[P nistPoint[P]](b *testing.B, p P, scalarSize int) { - scalar := make([]byte, scalarSize) - rand.Read(scalar) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - p.ScalarMult(p, scalar) - } -} - -func BenchmarkScalarBaseMult(b *testing.B) { - b.Run("P224", func(b *testing.B) { - benchmarkScalarBaseMult(b, nistec.NewP224Point().SetGenerator(), 28) - }) - b.Run("P256", func(b *testing.B) { - benchmarkScalarBaseMult(b, nistec.NewP256Point().SetGenerator(), 32) - }) - b.Run("P384", func(b *testing.B) { - benchmarkScalarBaseMult(b, nistec.NewP384Point().SetGenerator(), 48) - }) - b.Run("P521", func(b *testing.B) { - benchmarkScalarBaseMult(b, nistec.NewP521Point().SetGenerator(), 66) - }) -} - -func benchmarkScalarBaseMult[P nistPoint[P]](b *testing.B, p P, scalarSize int) { - scalar := make([]byte, scalarSize) - rand.Read(scalar) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - p.ScalarBaseMult(scalar) - } -} diff --git a/crypto/internal/fips140test/sshkdf_test.go b/crypto/internal/fips140test/sshkdf_test.go new file mode 100644 index 00000000000..a82d0d0b497 --- /dev/null +++ b/crypto/internal/fips140test/sshkdf_test.go @@ -0,0 +1,52 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "bytes" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/ssh" + "github.com/runZeroInc/excrypto/crypto/sha256" +) + +func TestSSHACVPVector(t *testing.T) { + // https://github.com/usnistgov/ACVP-Server/blob/3a7333f638/gen-val/json-files/kdf-components-ssh-1.0/prompt.json#L910-L915 + K := decodeHex(t, "0000010100E534CD9780786AF19994DD68C3FD7FE1E1F77C3938B2005C49B080CF88A63A44079774A36F23BA4D73470CB318C30524854D2F36BAB9A45AD73DBB3BC5DD39A547F62BC921052E102E37F3DD0CD79A04EB46ACC14B823B326096A89E33E8846624188BB3C8F16B320E7BB8F5EB05F080DCEE244A445DBED3A9F3BA8C373D8BE62CDFE2FC5876F30F90F01F0A55E5251B23E0DBBFCFB1450715E329BB00FB222E850DDB11201460B8AEF3FC8965D3B6D3AFBB885A6C11F308F10211B82EA2028C7A84DD0BB8D5D6AC3A48D0C2B93609269C585E03889DB3621993E7F7C09A007FB6B5C06FFA532B0DBF11F71F740D9CD8FAD2532E21B9423BF3D85EE4E396BE32") + H := decodeHex(t, "8FB22F0864960DA5679FD377248E41C2D0390E5AB3BB7955A3B6C588FB75B20D") + sessionID := decodeHex(t, "269A512E7B560E13396E0F3F56BDA730E23EE122EE6D59C91C58FB07872BCCCC") + + // https://github.com/usnistgov/ACVP-Server/blob/3a7333f638/gen-val/json-files/kdf-components-ssh-1.0/expectedResults.json#L1306-L1314 + initialIVClient := decodeHex(t, "82321D9FE2ACD958D3F55F4D3FF5C79D") + initialIVServer := decodeHex(t, "03F336F61311770BD5346B41E04CDB1F") + encryptionKeyClient := decodeHex(t, "20E55008D0120C400F42E5D2E148AB75") + encryptionKeyServer := decodeHex(t, "8BF4DEBEC96F4ADBBE5BB43828D56E6D") + integrityKeyClient := decodeHex(t, "15F53BCCE2645D0AD1C539C09BF9054AA3A4B10B71E96B9E3A15672405341BB5") + integrityKeyServer := decodeHex(t, "00BB773FD63AC7B7281A7B54C130CCAD363EE8928104E67CA5A3211EE3BBAB93") + + gotIVClient, gotKeyClient, gotIntegrityClient := ssh.Keys( + sha256.New, ssh.ClientKeys, K, H, sessionID, 16, 16, 32) + gotIVServer, gotKeyServer, gotIntegrityServer := ssh.Keys( + sha256.New, ssh.ServerKeys, K, H, sessionID, 16, 16, 32) + + if !bytes.Equal(gotIVClient, initialIVClient) { + t.Errorf("got IV client %x, want %x", gotIVClient, initialIVClient) + } + if !bytes.Equal(gotKeyClient, encryptionKeyClient) { + t.Errorf("got key client %x, want %x", gotKeyClient, encryptionKeyClient) + } + if !bytes.Equal(gotIntegrityClient, integrityKeyClient) { + t.Errorf("got integrity key client %x, want %x", gotIntegrityClient, integrityKeyClient) + } + if !bytes.Equal(gotIVServer, initialIVServer) { + t.Errorf("got IV server %x, want %x", gotIVServer, initialIVServer) + } + if !bytes.Equal(gotKeyServer, encryptionKeyServer) { + t.Errorf("got key server %x, want %x", gotKeyServer, encryptionKeyServer) + } + if !bytes.Equal(gotIntegrityServer, integrityKeyServer) { + t.Errorf("got integrity key server %x, want %x", gotIntegrityServer, integrityKeyServer) + } +} diff --git a/crypto/internal/fips140test/xaes_test.go b/crypto/internal/fips140test/xaes_test.go new file mode 100644 index 00000000000..77b44a13815 --- /dev/null +++ b/crypto/internal/fips140test/xaes_test.go @@ -0,0 +1,150 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package fipstest + +import ( + "bytes" + "encoding/hex" + "runtime" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/drbg" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" +) + +func TestXAESAllocations(t *testing.T) { + if runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" { + t.Skip("Test reports non-zero allocation count. See issue #70448") + } + cryptotest.SkipTestAllocations(t) + if allocs := testing.AllocsPerRun(10, func() { + key := make([]byte, 32) + nonce := make([]byte, 24) + plaintext := make([]byte, 16) + aad := make([]byte, 16) + ciphertext := make([]byte, 0, 16+16) + ciphertext = xaesSeal(ciphertext, key, nonce, plaintext, aad) + if _, err := xaesOpen(plaintext[:0], key, nonce, ciphertext, aad); err != nil { + t.Fatal(err) + } + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1f", allocs) + } +} + +func TestXAES(t *testing.T) { + key := bytes.Repeat([]byte{0x01}, 32) + plaintext := []byte("XAES-256-GCM") + additionalData := []byte("c2sp.org/XAES-256-GCM") + + nonce := make([]byte, 24) + ciphertext := make([]byte, len(plaintext)+16) + + drbg.Read(nonce[:12]) + c, _ := aes.New(key) + k := gcm.NewCounterKDF(c).DeriveKey(0x58, [12]byte(nonce)) + a, _ := aes.New(k[:]) + g, _ := gcm.New(a, 12, 16) + gcm.SealWithRandomNonce(g, nonce[12:], ciphertext, plaintext, additionalData) + + got, err := xaesOpen(nil, key, nonce, ciphertext, additionalData) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(plaintext, got) { + t.Errorf("plaintext and got are not equal") + } +} + +// ACVP tests consider fixed data part of the output, not part of the input, and +// all the pre-generated vectors at +// https://github.com/usnistgov/ACVP-Server/blob/3a7333f6/gen-val/json-files/KDF-1.0/expectedResults.json +// have a 32-byte fixed data, while ours is always 14 bytes. Instead, test +// against the XAES-256-GCM vectors, which were tested against OpenSSL's Counter +// KDF. This also ensures the KDF will work for XAES-256-GCM. + +func xaesSeal(dst, key, nonce, plaintext, additionalData []byte) []byte { + c, _ := aes.New(key) + k := gcm.NewCounterKDF(c).DeriveKey(0x58, [12]byte(nonce)) + n := nonce[12:] + a, _ := aes.New(k[:]) + g, _ := gcm.New(a, 12, 16) + return g.Seal(dst, n, plaintext, additionalData) +} + +func xaesOpen(dst, key, nonce, ciphertext, additionalData []byte) ([]byte, error) { + c, _ := aes.New(key) + k := gcm.NewCounterKDF(c).DeriveKey(0x58, [12]byte(nonce)) + n := nonce[12:] + a, _ := aes.New(k[:]) + g, _ := gcm.New(a, 12, 16) + return g.Open(dst, n, ciphertext, additionalData) +} + +func TestXAESVectors(t *testing.T) { + key := bytes.Repeat([]byte{0x01}, 32) + nonce := []byte("ABCDEFGHIJKLMNOPQRSTUVWX") + plaintext := []byte("XAES-256-GCM") + ciphertext := xaesSeal(nil, key, nonce, plaintext, nil) + expected := "ce546ef63c9cc60765923609b33a9a1974e96e52daf2fcf7075e2271" + if got := hex.EncodeToString(ciphertext); got != expected { + t.Errorf("got: %s", got) + } + if decrypted, err := xaesOpen(nil, key, nonce, ciphertext, nil); err != nil { + t.Fatal(err) + } else if !bytes.Equal(plaintext, decrypted) { + t.Errorf("plaintext and decrypted are not equal") + } + + key = bytes.Repeat([]byte{0x03}, 32) + aad := []byte("c2sp.org/XAES-256-GCM") + ciphertext = xaesSeal(nil, key, nonce, plaintext, aad) + expected = "986ec1832593df5443a179437fd083bf3fdb41abd740a21f71eb769d" + if got := hex.EncodeToString(ciphertext); got != expected { + t.Errorf("got: %s", got) + } + if decrypted, err := xaesOpen(nil, key, nonce, ciphertext, aad); err != nil { + t.Fatal(err) + } else if !bytes.Equal(plaintext, decrypted) { + t.Errorf("plaintext and decrypted are not equal") + } +} + +func TestXAESAccumulated(t *testing.T) { + iterations := 10_000 + expected := "e6b9edf2df6cec60c8cbd864e2211b597fb69a529160cd040d56c0c210081939" + + s, d := sha3.NewShake128(), sha3.NewShake128() + for i := 0; i < iterations; i++ { + key := make([]byte, 32) + s.Read(key) + nonce := make([]byte, 24) + s.Read(nonce) + lenByte := make([]byte, 1) + s.Read(lenByte) + plaintext := make([]byte, int(lenByte[0])) + s.Read(plaintext) + s.Read(lenByte) + aad := make([]byte, int(lenByte[0])) + s.Read(aad) + + ciphertext := xaesSeal(nil, key, nonce, plaintext, aad) + decrypted, err := xaesOpen(nil, key, nonce, ciphertext, aad) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(plaintext, decrypted) { + t.Errorf("plaintext and decrypted are not equal") + } + + d.Write(ciphertext) + } + if got := hex.EncodeToString(d.Sum(nil)); got != expected { + t.Errorf("got: %s", got) + } +} diff --git a/crypto/internal/hpke/hpke.go b/crypto/internal/hpke/hpke.go index a1453171025..f91ae67feb3 100644 --- a/crypto/internal/hpke/hpke.go +++ b/crypto/internal/hpke/hpke.go @@ -5,19 +5,19 @@ package hpke import ( - "encoding/binary" + "crypto" "errors" + "internal/byteorder" "math/bits" "crypto/rand" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/aes" "github.com/runZeroInc/excrypto/crypto/cipher" "github.com/runZeroInc/excrypto/crypto/ecdh" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" - "github.com/runZeroInc/excrypto/x/crypto/chacha20poly1305" - "github.com/runZeroInc/excrypto/x/crypto/hkdf" + "golang.org/x/crypto/chacha20poly1305" ) // testingOnlyGenerateKey is only used during testing, to provide @@ -28,10 +28,10 @@ type hkdfKDF struct { hash crypto.Hash } -func (kdf *hkdfKDF) LabeledExtract(suiteID []byte, salt []byte, label string, inputKey []byte) []byte { - labeledIKM := make([]byte, 0, 7+len(suiteID)+len(label)+len(inputKey)) +func (kdf *hkdfKDF) LabeledExtract(sid []byte, salt []byte, label string, inputKey []byte) []byte { + labeledIKM := make([]byte, 0, 7+len(sid)+len(label)+len(inputKey)) labeledIKM = append(labeledIKM, []byte("HPKE-v1")...) - labeledIKM = append(labeledIKM, suiteID...) + labeledIKM = append(labeledIKM, sid...) labeledIKM = append(labeledIKM, label...) labeledIKM = append(labeledIKM, inputKey...) return hkdf.Extract(kdf.hash.New, labeledIKM, salt) @@ -39,17 +39,12 @@ func (kdf *hkdfKDF) LabeledExtract(suiteID []byte, salt []byte, label string, in func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string, info []byte, length uint16) []byte { labeledInfo := make([]byte, 0, 2+7+len(suiteID)+len(label)+len(info)) - labeledInfo = binary.BigEndian.AppendUint16(labeledInfo, length) + labeledInfo = byteorder.BEAppendUint16(labeledInfo, length) labeledInfo = append(labeledInfo, []byte("HPKE-v1")...) labeledInfo = append(labeledInfo, suiteID...) labeledInfo = append(labeledInfo, label...) labeledInfo = append(labeledInfo, info...) - out := make([]byte, length) - n, err := hkdf.Expand(kdf.hash.New, randomKey, labeledInfo).Read(out) - if err != nil || n != int(length) { - panic("hpke: LabeledExpand failed unexpectedly") - } - return out + return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length)) } // dhKEM implements the KEM specified in RFC 9180, Section 4.1. @@ -61,13 +56,17 @@ type dhKEM struct { nSecret uint16 } +type KemID uint16 + +const DHKEM_X25519_HKDF_SHA256 = 0x0020 + var SupportedKEMs = map[uint16]struct { curve ecdh.Curve hash crypto.Hash nSecret uint16 }{ // RFC 9180 Section 7.1 - 0x0020: {ecdh.X25519(), crypto.SHA256, 32}, + DHKEM_X25519_HKDF_SHA256: {ecdh.X25519(), crypto.SHA256, 32}, } func newDHKem(kemID uint16) (*dhKEM, error) { @@ -78,7 +77,7 @@ func newDHKem(kemID uint16) (*dhKEM, error) { return &dhKEM{ dh: suite.curve, kdf: hkdfKDF{suite.hash}, - suiteID: binary.BigEndian.AppendUint16([]byte("KEM"), kemID), + suiteID: byteorder.BEAppendUint16([]byte("KEM"), kemID), nSecret: suite.nSecret, }, nil } @@ -110,9 +109,22 @@ func (dh *dhKEM) Encap(pubRecipient *ecdh.PublicKey) (sharedSecret []byte, encap return dh.ExtractAndExpand(dhVal, kemContext), encPubEph, nil } -type Sender struct { +func (dh *dhKEM) Decap(encPubEph []byte, secRecipient *ecdh.PrivateKey) ([]byte, error) { + pubEph, err := dh.dh.NewPublicKey(encPubEph) + if err != nil { + return nil, err + } + dhVal, err := secRecipient.ECDH(pubEph) + if err != nil { + return nil, err + } + kemContext := append(encPubEph, secRecipient.PublicKey().Bytes()...) + + return dh.ExtractAndExpand(dhVal, kemContext), nil +} + +type context struct { aead cipher.AEAD - kem *dhKEM sharedSecret []byte @@ -125,6 +137,14 @@ type Sender struct { seqNum uint128 } +type Sender struct { + *context +} + +type Receipient struct { + *context +} + var aesGCMNew = func(key []byte) (cipher.AEAD, error) { block, err := aes.NewCipher(key) if err != nil { @@ -133,102 +153,148 @@ var aesGCMNew = func(key []byte) (cipher.AEAD, error) { return cipher.NewGCM(block) } +type AEADID uint16 + +const ( + AEAD_AES_128_GCM = 0x0001 + AEAD_AES_256_GCM = 0x0002 + AEAD_ChaCha20Poly1305 = 0x0003 +) + var SupportedAEADs = map[uint16]struct { keySize int nonceSize int aead func([]byte) (cipher.AEAD, error) }{ // RFC 9180, Section 7.3 - 0x0001: {keySize: 16, nonceSize: 12, aead: aesGCMNew}, - 0x0002: {keySize: 32, nonceSize: 12, aead: aesGCMNew}, - 0x0003: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New}, + AEAD_AES_128_GCM: {keySize: 16, nonceSize: 12, aead: aesGCMNew}, + AEAD_AES_256_GCM: {keySize: 32, nonceSize: 12, aead: aesGCMNew}, + AEAD_ChaCha20Poly1305: {keySize: chacha20poly1305.KeySize, nonceSize: chacha20poly1305.NonceSize, aead: chacha20poly1305.New}, } +type KDFID uint16 + +const KDF_HKDF_SHA256 = 0x0001 + var SupportedKDFs = map[uint16]func() *hkdfKDF{ // RFC 9180, Section 7.2 - 0x0001: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} }, + KDF_HKDF_SHA256: func() *hkdfKDF { return &hkdfKDF{crypto.SHA256} }, } -func SetupSender(kemID, kdfID, aeadID uint16, pub crypto.PublicKey, info []byte) ([]byte, *Sender, error) { - suiteID := SuiteID(kemID, kdfID, aeadID) - - kem, err := newDHKem(kemID) - if err != nil { - return nil, nil, err - } - pubRecipient, ok := pub.(*ecdh.PublicKey) - if !ok { - return nil, nil, errors.New("incorrect public key type") - } - sharedSecret, encapsulatedKey, err := kem.Encap(pubRecipient) - if err != nil { - return nil, nil, err - } +func newContext(sharedSecret []byte, kemID, kdfID, aeadID uint16, info []byte) (*context, error) { + sid := suiteID(kemID, kdfID, aeadID) kdfInit, ok := SupportedKDFs[kdfID] if !ok { - return nil, nil, errors.New("unsupported KDF id") + return nil, errors.New("unsupported KDF id") } kdf := kdfInit() aeadInfo, ok := SupportedAEADs[aeadID] if !ok { - return nil, nil, errors.New("unsupported AEAD id") + return nil, errors.New("unsupported AEAD id") } - pskIDHash := kdf.LabeledExtract(suiteID, nil, "psk_id_hash", nil) - infoHash := kdf.LabeledExtract(suiteID, nil, "info_hash", info) + pskIDHash := kdf.LabeledExtract(sid, nil, "psk_id_hash", nil) + infoHash := kdf.LabeledExtract(sid, nil, "info_hash", info) ksContext := append([]byte{0}, pskIDHash...) ksContext = append(ksContext, infoHash...) - secret := kdf.LabeledExtract(suiteID, sharedSecret, "secret", nil) + secret := kdf.LabeledExtract(sid, sharedSecret, "secret", nil) - key := kdf.LabeledExpand(suiteID, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */) - baseNonce := kdf.LabeledExpand(suiteID, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */) - exporterSecret := kdf.LabeledExpand(suiteID, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/) + key := kdf.LabeledExpand(sid, secret, "key", ksContext, uint16(aeadInfo.keySize) /* Nk - key size for AEAD */) + baseNonce := kdf.LabeledExpand(sid, secret, "base_nonce", ksContext, uint16(aeadInfo.nonceSize) /* Nn - nonce size for AEAD */) + exporterSecret := kdf.LabeledExpand(sid, secret, "exp", ksContext, uint16(kdf.hash.Size()) /* Nh - hash output size of the kdf*/) aead, err := aeadInfo.aead(key) if err != nil { - return nil, nil, err + return nil, err } - return encapsulatedKey, &Sender{ - kem: kem, + return &context{ aead: aead, sharedSecret: sharedSecret, - suiteID: suiteID, + suiteID: sid, key: key, baseNonce: baseNonce, exporterSecret: exporterSecret, }, nil } -func (s *Sender) nextNonce() []byte { - nonce := s.seqNum.bytes()[16-s.aead.NonceSize():] - for i := range s.baseNonce { - nonce[i] ^= s.baseNonce[i] +func SetupSender(kemID, kdfID, aeadID uint16, pub *ecdh.PublicKey, info []byte) ([]byte, *Sender, error) { + kem, err := newDHKem(kemID) + if err != nil { + return nil, nil, err } + sharedSecret, encapsulatedKey, err := kem.Encap(pub) + if err != nil { + return nil, nil, err + } + + context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info) + if err != nil { + return nil, nil, err + } + + return encapsulatedKey, &Sender{context}, nil +} + +func SetupReceipient(kemID, kdfID, aeadID uint16, priv *ecdh.PrivateKey, info, encPubEph []byte) (*Receipient, error) { + kem, err := newDHKem(kemID) + if err != nil { + return nil, err + } + sharedSecret, err := kem.Decap(encPubEph, priv) + if err != nil { + return nil, err + } + + context, err := newContext(sharedSecret, kemID, kdfID, aeadID, info) + if err != nil { + return nil, err + } + + return &Receipient{context}, nil +} + +func (ctx *context) nextNonce() []byte { + nonce := ctx.seqNum.bytes()[16-ctx.aead.NonceSize():] + for i := range ctx.baseNonce { + nonce[i] ^= ctx.baseNonce[i] + } + return nonce +} + +func (ctx *context) incrementNonce() { // Message limit is, according to the RFC, 2^95+1, which // is somewhat confusing, but we do as we're told. - if s.seqNum.bitLen() >= (s.aead.NonceSize()*8)-1 { + if ctx.seqNum.bitLen() >= (ctx.aead.NonceSize()*8)-1 { panic("message limit reached") } - s.seqNum = s.seqNum.addOne() - return nonce + ctx.seqNum = ctx.seqNum.addOne() } func (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) { - ciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad) + s.incrementNonce() return ciphertext, nil } -func SuiteID(kemID, kdfID, aeadID uint16) []byte { +func (r *Receipient) Open(aad, ciphertext []byte) ([]byte, error) { + plaintext, err := r.aead.Open(nil, r.nextNonce(), ciphertext, aad) + if err != nil { + return nil, err + } + r.incrementNonce() + return plaintext, nil +} + +func suiteID(kemID, kdfID, aeadID uint16) []byte { suiteID := make([]byte, 0, 4+2+2+2) suiteID = append(suiteID, []byte("HPKE")...) - suiteID = binary.BigEndian.AppendUint16(suiteID, kemID) - suiteID = binary.BigEndian.AppendUint16(suiteID, kdfID) - suiteID = binary.BigEndian.AppendUint16(suiteID, aeadID) + suiteID = byteorder.BEAppendUint16(suiteID, kemID) + suiteID = byteorder.BEAppendUint16(suiteID, kdfID) + suiteID = byteorder.BEAppendUint16(suiteID, aeadID) return suiteID } @@ -240,6 +306,14 @@ func ParseHPKEPublicKey(kemID uint16, bytes []byte) (*ecdh.PublicKey, error) { return kemInfo.curve.NewPublicKey(bytes) } +func ParseHPKEPrivateKey(kemID uint16, bytes []byte) (*ecdh.PrivateKey, error) { + kemInfo, ok := SupportedKEMs[kemID] + if !ok { + return nil, errors.New("unsupported KEM id") + } + return kemInfo.curve.NewPrivateKey(bytes) +} + type uint128 struct { hi, lo uint64 } @@ -255,7 +329,7 @@ func (u uint128) bitLen() int { func (u uint128) bytes() []byte { b := make([]byte, 16) - binary.BigEndian.PutUint64(b[0:], u.hi) - binary.BigEndian.PutUint64(b[8:], u.lo) + byteorder.BEPutUint64(b[0:], u.hi) + byteorder.BEPutUint64(b[8:], u.lo) return b } diff --git a/crypto/internal/hpke/hpke_test.go b/crypto/internal/hpke/hpke_test.go index 91952b52372..663f4e49c1f 100644 --- a/crypto/internal/hpke/hpke_test.go +++ b/crypto/internal/hpke/hpke_test.go @@ -104,7 +104,7 @@ func TestRFC9180Vectors(t *testing.T) { } t.Cleanup(func() { testingOnlyGenerateKey = nil }) - encap, context, err := SetupSender( + encap, sender, err := SetupSender( uint16(kemID), uint16(kdfID), uint16(aeadID), @@ -119,21 +119,42 @@ func TestRFC9180Vectors(t *testing.T) { if !bytes.Equal(encap, expectedEncap) { t.Errorf("unexpected encapsulated key, got: %x, want %x", encap, expectedEncap) } - expectedSharedSecret := mustDecodeHex(t, setup["shared_secret"]) - if !bytes.Equal(context.sharedSecret, expectedSharedSecret) { - t.Errorf("unexpected shared secret, got: %x, want %x", context.sharedSecret, expectedSharedSecret) - } - expectedKey := mustDecodeHex(t, setup["key"]) - if !bytes.Equal(context.key, expectedKey) { - t.Errorf("unexpected key, got: %x, want %x", context.key, expectedKey) + + privKeyBytes := mustDecodeHex(t, setup["skRm"]) + priv, err := ParseHPKEPrivateKey(uint16(kemID), privKeyBytes) + if err != nil { + t.Fatal(err) } - expectedBaseNonce := mustDecodeHex(t, setup["base_nonce"]) - if !bytes.Equal(context.baseNonce, expectedBaseNonce) { - t.Errorf("unexpected base nonce, got: %x, want %x", context.baseNonce, expectedBaseNonce) + + receipient, err := SetupReceipient( + uint16(kemID), + uint16(kdfID), + uint16(aeadID), + priv, + info, + encap, + ) + if err != nil { + t.Fatal(err) } - expectedExporterSecret := mustDecodeHex(t, setup["exporter_secret"]) - if !bytes.Equal(context.exporterSecret, expectedExporterSecret) { - t.Errorf("unexpected exporter secret, got: %x, want %x", context.exporterSecret, expectedExporterSecret) + + for _, ctx := range []*context{sender.context, receipient.context} { + expectedSharedSecret := mustDecodeHex(t, setup["shared_secret"]) + if !bytes.Equal(ctx.sharedSecret, expectedSharedSecret) { + t.Errorf("unexpected shared secret, got: %x, want %x", ctx.sharedSecret, expectedSharedSecret) + } + expectedKey := mustDecodeHex(t, setup["key"]) + if !bytes.Equal(ctx.key, expectedKey) { + t.Errorf("unexpected key, got: %x, want %x", ctx.key, expectedKey) + } + expectedBaseNonce := mustDecodeHex(t, setup["base_nonce"]) + if !bytes.Equal(ctx.baseNonce, expectedBaseNonce) { + t.Errorf("unexpected base nonce, got: %x, want %x", ctx.baseNonce, expectedBaseNonce) + } + expectedExporterSecret := mustDecodeHex(t, setup["exporter_secret"]) + if !bytes.Equal(ctx.exporterSecret, expectedExporterSecret) { + t.Errorf("unexpected exporter secret, got: %x, want %x", ctx.exporterSecret, expectedExporterSecret) + } } for _, enc := range parseVectorEncryptions(vector.Encryptions) { @@ -142,26 +163,31 @@ func TestRFC9180Vectors(t *testing.T) { if err != nil { t.Fatal(err) } - context.seqNum = uint128{lo: uint64(seqNum)} + sender.seqNum = uint128{lo: uint64(seqNum)} + receipient.seqNum = uint128{lo: uint64(seqNum)} expectedNonce := mustDecodeHex(t, enc["nonce"]) - // We can't call nextNonce, because it increments the sequence number, - // so just compute it directly. - computedNonce := context.seqNum.bytes()[16-context.aead.NonceSize():] - for i := range context.baseNonce { - computedNonce[i] ^= context.baseNonce[i] - } + computedNonce := sender.nextNonce() if !bytes.Equal(computedNonce, expectedNonce) { t.Errorf("unexpected nonce: got %x, want %x", computedNonce, expectedNonce) } expectedCiphertext := mustDecodeHex(t, enc["ct"]) - ciphertext, err := context.Seal(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["pt"])) + ciphertext, err := sender.Seal(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["pt"])) if err != nil { t.Fatal(err) } if !bytes.Equal(ciphertext, expectedCiphertext) { t.Errorf("unexpected ciphertext: got %x want %x", ciphertext, expectedCiphertext) } + + expectedPlaintext := mustDecodeHex(t, enc["pt"]) + plaintext, err := receipient.Open(mustDecodeHex(t, enc["aad"]), mustDecodeHex(t, enc["ct"])) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(plaintext, expectedPlaintext) { + t.Errorf("unexpected plaintext: got %x want %x", plaintext, expectedPlaintext) + } }) } }) diff --git a/crypto/internal/impl/impl.go b/crypto/internal/impl/impl.go new file mode 100644 index 00000000000..193839f1f19 --- /dev/null +++ b/crypto/internal/impl/impl.go @@ -0,0 +1,107 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package impl is a registry of alternative implementations of cryptographic +// primitives, to allow selecting them for testing. +package impl + +import "strings" + +type implementation struct { + Package string + Name string + Available bool + Toggle *bool +} + +var allImplementations []implementation + +// Register records an alternative implementation of a cryptographic primitive. +// The implementation might be available or not based on CPU support. If +// available is false, the implementation is unavailable and can't be tested on +// this machine. If available is true, it can be set to false to disable the +// implementation. If all alternative implementations but one are disabled, the +// remaining one must be used (i.e. disabling one implementation must not +// implicitly disable any other). Each package has an implicit base +// implementation that is selected when all alternatives are unavailable or +// disabled. pkg must be the package name, not path (e.g. "aes" not "crypto/aes"). +func Register(pkg, name string, available *bool) { + if strings.Contains(pkg, "/") { + panic("impl: package name must not contain slashes") + } + allImplementations = append(allImplementations, implementation{ + Package: pkg, + Name: name, + Available: *available, + Toggle: available, + }) +} + +// Packages returns the list of all packages for which alternative +// implementations are registered. +func Packages() []string { + var pkgs []string + seen := make(map[string]bool) + for _, i := range allImplementations { + if !seen[i.Package] { + pkgs = append(pkgs, i.Package) + seen[i.Package] = true + } + } + return pkgs +} + +// List returns the names of all alternative implementations registered for the +// given package, whether available or not. The implicit base implementation is +// not included. +func List(pkg string) []string { + var names []string + for _, i := range allImplementations { + if i.Package == pkg { + names = append(names, i.Name) + } + } + return names +} + +func available(pkg, name string) bool { + for _, i := range allImplementations { + if i.Package == pkg && i.Name == name { + return i.Available + } + } + panic("unknown implementation") +} + +// Select disables all implementations for the given package except the one +// with the given name. If name is empty, the base implementation is selected. +// It returns whether the selected implementation is available. +func Select(pkg, name string) bool { + if name == "" { + for _, i := range allImplementations { + if i.Package == pkg { + *i.Toggle = false + } + } + return true + } + if !available(pkg, name) { + return false + } + for _, i := range allImplementations { + if i.Package == pkg { + *i.Toggle = i.Name == name + } + } + return true +} + +func Reset(pkg string) { + for _, i := range allImplementations { + if i.Package == pkg { + *i.Toggle = i.Available + return + } + } +} diff --git a/crypto/internal/mlkem768/mlkem768.go b/crypto/internal/mlkem768/mlkem768.go deleted file mode 100644 index 08986fa3bed..00000000000 --- a/crypto/internal/mlkem768/mlkem768.go +++ /dev/null @@ -1,888 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package mlkem768 implements the quantum-resistant key encapsulation method -// ML-KEM (formerly known as Kyber). -// -// Only the recommended ML-KEM-768 parameter set is provided. -// -// The version currently implemented is the one specified by [NIST FIPS 203 ipd], -// with the unintentional transposition of the matrix A reverted to match the -// behavior of [Kyber version 3.0]. Future versions of this package might -// introduce backwards incompatible changes to implement changes to FIPS 203. -// -// [Kyber version 3.0]: https://pq-crystals.org/kyber/data/kyber-specification-round3-20210804.pdf -// [NIST FIPS 203 ipd]: https://doi.org/10.6028/NIST.FIPS.203.ipd -package mlkem768 - -// This package targets security, correctness, simplicity, readability, and -// reviewability as its primary goals. All critical operations are performed in -// constant time. -// -// Variable and function names, as well as code layout, are selected to -// facilitate reviewing the implementation against the NIST FIPS 203 ipd -// document. -// -// Reviewers unfamiliar with polynomials or linear algebra might find the -// background at https://words.filippo.io/kyber-math/ useful. - -import ( - "errors" - - "crypto/rand" - - "github.com/runZeroInc/excrypto/crypto/subtle" - "github.com/runZeroInc/excrypto/internal/byteorder" - - "github.com/runZeroInc/excrypto/x/crypto/sha3" -) - -const ( - // ML-KEM global constants. - n = 256 - q = 3329 - - log2q = 12 - - // ML-KEM-768 parameters. The code makes assumptions based on these values, - // they can't be changed blindly. - k = 3 - η = 2 - du = 10 - dv = 4 - - // encodingSizeX is the byte size of a ringElement or nttElement encoded - // by ByteEncode_X (FIPS 203 (DRAFT), Algorithm 4). - encodingSize12 = n * log2q / 8 - encodingSize10 = n * du / 8 - encodingSize4 = n * dv / 8 - encodingSize1 = n * 1 / 8 - - messageSize = encodingSize1 - decryptionKeySize = k * encodingSize12 - encryptionKeySize = k*encodingSize12 + 32 - - CiphertextSize = k*encodingSize10 + encodingSize4 - EncapsulationKeySize = encryptionKeySize - DecapsulationKeySize = decryptionKeySize + encryptionKeySize + 32 + 32 - SharedKeySize = 32 - SeedSize = 32 + 32 -) - -// A DecapsulationKey is the secret key used to decapsulate a shared key from a -// ciphertext. It includes various precomputed values. -type DecapsulationKey struct { - dk [DecapsulationKeySize]byte - encryptionKey - decryptionKey -} - -// Bytes returns the extended encoding of the decapsulation key, according to -// FIPS 203 (DRAFT). -func (dk *DecapsulationKey) Bytes() []byte { - var b [DecapsulationKeySize]byte - copy(b[:], dk.dk[:]) - return b[:] -} - -// EncapsulationKey returns the public encapsulation key necessary to produce -// ciphertexts. -func (dk *DecapsulationKey) EncapsulationKey() []byte { - var b [EncapsulationKeySize]byte - copy(b[:], dk.dk[decryptionKeySize:]) - return b[:] -} - -// encryptionKey is the parsed and expanded form of a PKE encryption key. -type encryptionKey struct { - t [k]nttElement // ByteDecode₁₂(ek[:384k]) - A [k * k]nttElement // A[i*k+j] = sampleNTT(ρ, j, i) -} - -// decryptionKey is the parsed and expanded form of a PKE decryption key. -type decryptionKey struct { - s [k]nttElement // ByteDecode₁₂(dk[:decryptionKeySize]) -} - -// GenerateKey generates a new decapsulation key, drawing random bytes from -// crypto/rand. The decapsulation key must be kept secret. -func GenerateKey() (*DecapsulationKey, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey{} - return generateKey(dk) -} - -func generateKey(dk *DecapsulationKey) (*DecapsulationKey, error) { - var d [32]byte - if _, err := rand.Read(d[:]); err != nil { - return nil, errors.New("mlkem768: crypto/rand Read failed: " + err.Error()) - } - var z [32]byte - if _, err := rand.Read(z[:]); err != nil { - return nil, errors.New("mlkem768: crypto/rand Read failed: " + err.Error()) - } - return kemKeyGen(dk, &d, &z), nil -} - -// NewKeyFromSeed deterministically generates a decapsulation key from a 64-byte -// seed in the "d || z" form. The seed must be uniformly random. -func NewKeyFromSeed(seed []byte) (*DecapsulationKey, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey{} - return newKeyFromSeed(dk, seed) -} - -func newKeyFromSeed(dk *DecapsulationKey, seed []byte) (*DecapsulationKey, error) { - if len(seed) != SeedSize { - return nil, errors.New("mlkem768: invalid seed length") - } - d := (*[32]byte)(seed[:32]) - z := (*[32]byte)(seed[32:]) - return kemKeyGen(dk, d, z), nil -} - -// NewKeyFromExtendedEncoding parses a decapsulation key from its FIPS 203 -// (DRAFT) extended encoding. -func NewKeyFromExtendedEncoding(decapsulationKey []byte) (*DecapsulationKey, error) { - // The actual logic is in a separate function to outline this allocation. - dk := &DecapsulationKey{} - return newKeyFromExtendedEncoding(dk, decapsulationKey) -} - -func newKeyFromExtendedEncoding(dk *DecapsulationKey, dkBytes []byte) (*DecapsulationKey, error) { - if len(dkBytes) != DecapsulationKeySize { - return nil, errors.New("mlkem768: invalid decapsulation key length") - } - - // Note that we don't check that H(ek) matches ekPKE, as that's not - // specified in FIPS 203 (DRAFT). This is one reason to prefer the seed - // private key format. - dk.dk = [DecapsulationKeySize]byte(dkBytes) - - dkPKE := dkBytes[:decryptionKeySize] - if err := parseDK(&dk.decryptionKey, dkPKE); err != nil { - return nil, err - } - - ekPKE := dkBytes[decryptionKeySize : decryptionKeySize+encryptionKeySize] - if err := parseEK(&dk.encryptionKey, ekPKE); err != nil { - return nil, err - } - - return dk, nil -} - -// kemKeyGen generates a decapsulation key. -// -// It implements ML-KEM.KeyGen according to FIPS 203 (DRAFT), Algorithm 15, and -// K-PKE.KeyGen according to FIPS 203 (DRAFT), Algorithm 12. The two are merged -// to save copies and allocations. -func kemKeyGen(dk *DecapsulationKey, d, z *[32]byte) *DecapsulationKey { - if dk == nil { - dk = &DecapsulationKey{} - } - - G := sha3.Sum512(d[:]) - ρ, σ := G[:32], G[32:] - - A := &dk.A - for i := byte(0); i < k; i++ { - for j := byte(0); j < k; j++ { - // Note that this is consistent with Kyber round 3, rather than with - // the initial draft of FIPS 203, because NIST signaled that the - // change was involuntary and will be reverted. - A[i*k+j] = sampleNTT(ρ, j, i) - } - } - - var N byte - s := &dk.s - for i := range s { - s[i] = ntt(samplePolyCBD(σ, N)) - N++ - } - e := make([]nttElement, k) - for i := range e { - e[i] = ntt(samplePolyCBD(σ, N)) - N++ - } - - t := &dk.t - for i := range t { // t = A ◦ s + e - t[i] = e[i] - for j := range s { - t[i] = polyAdd(t[i], nttMul(A[i*k+j], s[j])) - } - } - - // dkPKE ← ByteEncode₁₂(s) - // ekPKE ← ByteEncode₁₂(t) || ρ - // ek ← ekPKE - // dk ← dkPKE || ek || H(ek) || z - dkB := dk.dk[:0] - - for i := range s { - dkB = polyByteEncode(dkB, s[i]) - } - - for i := range t { - dkB = polyByteEncode(dkB, t[i]) - } - dkB = append(dkB, ρ...) - - H := sha3.New256() - H.Write(dkB[decryptionKeySize:]) - dkB = H.Sum(dkB) - - dkB = append(dkB, z[:]...) - - if len(dkB) != len(dk.dk) { - panic("mlkem768: internal error: invalid decapsulation key size") - } - - return dk -} - -// Encapsulate generates a shared key and an associated ciphertext from an -// encapsulation key, drawing random bytes from crypto/rand. -// If the encapsulation key is not valid, Encapsulate returns an error. -// -// The shared key must be kept secret. -func Encapsulate(encapsulationKey []byte) (ciphertext, sharedKey []byte, err error) { - // The actual logic is in a separate function to outline this allocation. - var cc [CiphertextSize]byte - return encapsulate(&cc, encapsulationKey) -} - -func encapsulate(cc *[CiphertextSize]byte, encapsulationKey []byte) (ciphertext, sharedKey []byte, err error) { - if len(encapsulationKey) != EncapsulationKeySize { - return nil, nil, errors.New("mlkem768: invalid encapsulation key length") - } - var m [messageSize]byte - if _, err := rand.Read(m[:]); err != nil { - return nil, nil, errors.New("mlkem768: crypto/rand Read failed: " + err.Error()) - } - return kemEncaps(cc, encapsulationKey, &m) -} - -// kemEncaps generates a shared key and an associated ciphertext. -// -// It implements ML-KEM.Encaps according to FIPS 203 (DRAFT), Algorithm 16. -func kemEncaps(cc *[CiphertextSize]byte, ek []byte, m *[messageSize]byte) (c, K []byte, err error) { - if cc == nil { - cc = &[CiphertextSize]byte{} - } - - H := sha3.Sum256(ek[:]) - g := sha3.New512() - g.Write(m[:]) - g.Write(H[:]) - G := g.Sum(nil) - K, r := G[:SharedKeySize], G[SharedKeySize:] - var ex encryptionKey - if err := parseEK(&ex, ek[:]); err != nil { - return nil, nil, err - } - c = pkeEncrypt(cc, &ex, m, r) - return c, K, nil -} - -// parseEK parses an encryption key from its encoded form. -// -// It implements the initial stages of K-PKE.Encrypt according to FIPS 203 -// (DRAFT), Algorithm 13. -func parseEK(ex *encryptionKey, ekPKE []byte) error { - if len(ekPKE) != encryptionKeySize { - return errors.New("mlkem768: invalid encryption key length") - } - - for i := range ex.t { - var err error - ex.t[i], err = polyByteDecode[nttElement](ekPKE[:encodingSize12]) - if err != nil { - return err - } - ekPKE = ekPKE[encodingSize12:] - } - ρ := ekPKE - - for i := byte(0); i < k; i++ { - for j := byte(0); j < k; j++ { - // See the note in pkeKeyGen about the order of the indices being - // consistent with Kyber round 3. - ex.A[i*k+j] = sampleNTT(ρ, j, i) - } - } - - return nil -} - -// pkeEncrypt encrypt a plaintext message. -// -// It implements K-PKE.Encrypt according to FIPS 203 (DRAFT), Algorithm 13, -// although the computation of t and AT is done in parseEK. -func pkeEncrypt(cc *[CiphertextSize]byte, ex *encryptionKey, m *[messageSize]byte, rnd []byte) []byte { - var N byte - r, e1 := make([]nttElement, k), make([]ringElement, k) - for i := range r { - r[i] = ntt(samplePolyCBD(rnd, N)) - N++ - } - for i := range e1 { - e1[i] = samplePolyCBD(rnd, N) - N++ - } - e2 := samplePolyCBD(rnd, N) - - u := make([]ringElement, k) // NTT⁻¹(AT ◦ r) + e1 - for i := range u { - u[i] = e1[i] - for j := range r { - // Note that i and j are inverted, as we need the transposed of A. - u[i] = polyAdd(u[i], inverseNTT(nttMul(ex.A[j*k+i], r[j]))) - } - } - - μ := ringDecodeAndDecompress1(m) - - var vNTT nttElement // t⊺ ◦ r - for i := range ex.t { - vNTT = polyAdd(vNTT, nttMul(ex.t[i], r[i])) - } - v := polyAdd(polyAdd(inverseNTT(vNTT), e2), μ) - - c := cc[:0] - for _, f := range u { - c = ringCompressAndEncode10(c, f) - } - c = ringCompressAndEncode4(c, v) - - return c -} - -// Decapsulate generates a shared key from a ciphertext and a decapsulation key. -// If the ciphertext is not valid, Decapsulate returns an error. -// -// The shared key must be kept secret. -func Decapsulate(dk *DecapsulationKey, ciphertext []byte) (sharedKey []byte, err error) { - if len(ciphertext) != CiphertextSize { - return nil, errors.New("mlkem768: invalid ciphertext length") - } - c := (*[CiphertextSize]byte)(ciphertext) - return kemDecaps(dk, c), nil -} - -// kemDecaps produces a shared key from a ciphertext. -// -// It implements ML-KEM.Decaps according to FIPS 203 (DRAFT), Algorithm 17. -func kemDecaps(dk *DecapsulationKey, c *[CiphertextSize]byte) (K []byte) { - h := dk.dk[decryptionKeySize+encryptionKeySize : decryptionKeySize+encryptionKeySize+32] - z := dk.dk[decryptionKeySize+encryptionKeySize+32:] - - m := pkeDecrypt(&dk.decryptionKey, c) - g := sha3.New512() - g.Write(m[:]) - g.Write(h) - G := g.Sum(nil) - Kprime, r := G[:SharedKeySize], G[SharedKeySize:] - J := sha3.NewShake256() - J.Write(z) - J.Write(c[:]) - Kout := make([]byte, SharedKeySize) - J.Read(Kout) - var cc [CiphertextSize]byte - c1 := pkeEncrypt(&cc, &dk.encryptionKey, (*[32]byte)(m), r) - - subtle.ConstantTimeCopy(subtle.ConstantTimeCompare(c[:], c1), Kout, Kprime) - return Kout -} - -// parseDK parses a decryption key from its encoded form. -// -// It implements the computation of s from K-PKE.Decrypt according to FIPS 203 -// (DRAFT), Algorithm 14. -func parseDK(dx *decryptionKey, dkPKE []byte) error { - if len(dkPKE) != decryptionKeySize { - return errors.New("mlkem768: invalid decryption key length") - } - - for i := range dx.s { - f, err := polyByteDecode[nttElement](dkPKE[:encodingSize12]) - if err != nil { - return err - } - dx.s[i] = f - dkPKE = dkPKE[encodingSize12:] - } - - return nil -} - -// pkeDecrypt decrypts a ciphertext. -// -// It implements K-PKE.Decrypt according to FIPS 203 (DRAFT), Algorithm 14, -// although the computation of s is done in parseDK. -func pkeDecrypt(dx *decryptionKey, c *[CiphertextSize]byte) []byte { - u := make([]ringElement, k) - for i := range u { - b := (*[encodingSize10]byte)(c[encodingSize10*i : encodingSize10*(i+1)]) - u[i] = ringDecodeAndDecompress10(b) - } - - b := (*[encodingSize4]byte)(c[encodingSize10*k:]) - v := ringDecodeAndDecompress4(b) - - var mask nttElement // s⊺ ◦ NTT(u) - for i := range dx.s { - mask = polyAdd(mask, nttMul(dx.s[i], ntt(u[i]))) - } - w := polySub(v, inverseNTT(mask)) - - return ringCompressAndEncode1(nil, w) -} - -// fieldElement is an integer modulo q, an element of ℤ_q. It is always reduced. -type fieldElement uint16 - -// fieldCheckReduced checks that a value a is < q. -func fieldCheckReduced(a uint16) (fieldElement, error) { - if a >= q { - return 0, errors.New("unreduced field element") - } - return fieldElement(a), nil -} - -// fieldReduceOnce reduces a value a < 2q. -func fieldReduceOnce(a uint16) fieldElement { - x := a - q - // If x underflowed, then x >= 2¹⁶ - q > 2¹⁵, so the top bit is set. - x += (x >> 15) * q - return fieldElement(x) -} - -func fieldAdd(a, b fieldElement) fieldElement { - x := uint16(a + b) - return fieldReduceOnce(x) -} - -func fieldSub(a, b fieldElement) fieldElement { - x := uint16(a - b + q) - return fieldReduceOnce(x) -} - -const ( - barrettMultiplier = 5039 // 2¹² * 2¹² / q - barrettShift = 24 // log₂(2¹² * 2¹²) -) - -// fieldReduce reduces a value a < 2q² using Barrett reduction, to avoid -// potentially variable-time division. -func fieldReduce(a uint32) fieldElement { - quotient := uint32((uint64(a) * barrettMultiplier) >> barrettShift) - return fieldReduceOnce(uint16(a - quotient*q)) -} - -func fieldMul(a, b fieldElement) fieldElement { - x := uint32(a) * uint32(b) - return fieldReduce(x) -} - -// fieldMulSub returns a * (b - c). This operation is fused to save a -// fieldReduceOnce after the subtraction. -func fieldMulSub(a, b, c fieldElement) fieldElement { - x := uint32(a) * uint32(b-c+q) - return fieldReduce(x) -} - -// fieldAddMul returns a * b + c * d. This operation is fused to save a -// fieldReduceOnce and a fieldReduce. -func fieldAddMul(a, b, c, d fieldElement) fieldElement { - x := uint32(a) * uint32(b) - x += uint32(c) * uint32(d) - return fieldReduce(x) -} - -// compress maps a field element uniformly to the range 0 to 2ᵈ-1, according to -// FIPS 203 (DRAFT), Definition 4.5. -func compress(x fieldElement, d uint8) uint16 { - // We want to compute (x * 2ᵈ) / q, rounded to nearest integer, with 1/2 - // rounding up (see FIPS 203 (DRAFT), Section 2.3). - - // Barrett reduction produces a quotient and a remainder in the range [0, 2q), - // such that dividend = quotient * q + remainder. - dividend := uint32(x) << d // x * 2ᵈ - quotient := uint32(uint64(dividend) * barrettMultiplier >> barrettShift) - remainder := dividend - quotient*q - - // Since the remainder is in the range [0, 2q), not [0, q), we need to - // portion it into three spans for rounding. - // - // [ 0, q/2 ) -> round to 0 - // [ q/2, q + q/2 ) -> round to 1 - // [ q + q/2, 2q ) -> round to 2 - // - // We can convert that to the following logic: add 1 if remainder > q/2, - // then add 1 again if remainder > q + q/2. - // - // Note that if remainder > x, then ⌊x⌋ - remainder underflows, and the top - // bit of the difference will be set. - quotient += (q/2 - remainder) >> 31 & 1 - quotient += (q + q/2 - remainder) >> 31 & 1 - - // quotient might have overflowed at this point, so reduce it by masking. - var mask uint32 = (1 << d) - 1 - return uint16(quotient & mask) -} - -// decompress maps a number x between 0 and 2ᵈ-1 uniformly to the full range of -// field elements, according to FIPS 203 (DRAFT), Definition 4.6. -func decompress(y uint16, d uint8) fieldElement { - // We want to compute (y * q) / 2ᵈ, rounded to nearest integer, with 1/2 - // rounding up (see FIPS 203 (DRAFT), Section 2.3). - - dividend := uint32(y) * q - quotient := dividend >> d // (y * q) / 2ᵈ - - // The d'th least-significant bit of the dividend (the most significant bit - // of the remainder) is 1 for the top half of the values that divide to the - // same quotient, which are the ones that round up. - quotient += dividend >> (d - 1) & 1 - - // quotient is at most (2¹¹-1) * q / 2¹¹ + 1 = 3328, so it didn't overflow. - return fieldElement(quotient) -} - -// ringElement is a polynomial, an element of R_q, represented as an array -// according to FIPS 203 (DRAFT), Section 2.4. -type ringElement [n]fieldElement - -// polyAdd adds two ringElements or nttElements. -func polyAdd[T ~[n]fieldElement](a, b T) (s T) { - for i := range s { - s[i] = fieldAdd(a[i], b[i]) - } - return s -} - -// polySub subtracts two ringElements or nttElements. -func polySub[T ~[n]fieldElement](a, b T) (s T) { - for i := range s { - s[i] = fieldSub(a[i], b[i]) - } - return s -} - -// polyByteEncode appends the 384-byte encoding of f to b. -// -// It implements ByteEncode₁₂, according to FIPS 203 (DRAFT), Algorithm 4. -func polyByteEncode[T ~[n]fieldElement](b []byte, f T) []byte { - out, B := sliceForAppend(b, encodingSize12) - for i := 0; i < n; i += 2 { - x := uint32(f[i]) | uint32(f[i+1])<<12 - B[0] = uint8(x) - B[1] = uint8(x >> 8) - B[2] = uint8(x >> 16) - B = B[3:] - } - return out -} - -// polyByteDecode decodes the 384-byte encoding of a polynomial, checking that -// all the coefficients are properly reduced. This achieves the "Modulus check" -// step of ML-KEM Encapsulation Input Validation. -// -// polyByteDecode is also used in ML-KEM Decapsulation, where the input -// validation is not required, but implicitly allowed by the specification. -// -// It implements ByteDecode₁₂, according to FIPS 203 (DRAFT), Algorithm 5. -func polyByteDecode[T ~[n]fieldElement](b []byte) (T, error) { - if len(b) != encodingSize12 { - return T{}, errors.New("mlkem768: invalid encoding length") - } - var f T - for i := 0; i < n; i += 2 { - d := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 - const mask12 = 0b1111_1111_1111 - var err error - if f[i], err = fieldCheckReduced(uint16(d & mask12)); err != nil { - return T{}, errors.New("mlkem768: invalid polynomial encoding") - } - if f[i+1], err = fieldCheckReduced(uint16(d >> 12)); err != nil { - return T{}, errors.New("mlkem768: invalid polynomial encoding") - } - b = b[3:] - } - return f, nil -} - -// sliceForAppend takes a slice and a requested number of bytes. It returns a -// slice with the contents of the given slice followed by that many bytes and a -// second slice that aliases into it and contains only the extra bytes. If the -// original slice has sufficient capacity then no allocation is performed. -func sliceForAppend(in []byte, n int) (head, tail []byte) { - if total := len(in) + n; cap(in) >= total { - head = in[:total] - } else { - head = make([]byte, total) - copy(head, in) - } - tail = head[len(in):] - return -} - -// ringCompressAndEncode1 appends a 32-byte encoding of a ring element to s, -// compressing one coefficients per bit. -// -// It implements Compress₁, according to FIPS 203 (DRAFT), Definition 4.5, -// followed by ByteEncode₁, according to FIPS 203 (DRAFT), Algorithm 4. -func ringCompressAndEncode1(s []byte, f ringElement) []byte { - s, b := sliceForAppend(s, encodingSize1) - for i := range b { - b[i] = 0 - } - for i := range f { - b[i/8] |= uint8(compress(f[i], 1) << (i % 8)) - } - return s -} - -// ringDecodeAndDecompress1 decodes a 32-byte slice to a ring element where each -// bit is mapped to 0 or ⌈q/2⌋. -// -// It implements ByteDecode₁, according to FIPS 203 (DRAFT), Algorithm 5, -// followed by Decompress₁, according to FIPS 203 (DRAFT), Definition 4.6. -func ringDecodeAndDecompress1(b *[encodingSize1]byte) ringElement { - var f ringElement - for i := range f { - b_i := b[i/8] >> (i % 8) & 1 - const halfQ = (q + 1) / 2 // ⌈q/2⌋, rounded up per FIPS 203 (DRAFT), Section 2.3 - f[i] = fieldElement(b_i) * halfQ // 0 decompresses to 0, and 1 to ⌈q/2⌋ - } - return f -} - -// ringCompressAndEncode4 appends a 128-byte encoding of a ring element to s, -// compressing two coefficients per byte. -// -// It implements Compress₄, according to FIPS 203 (DRAFT), Definition 4.5, -// followed by ByteEncode₄, according to FIPS 203 (DRAFT), Algorithm 4. -func ringCompressAndEncode4(s []byte, f ringElement) []byte { - s, b := sliceForAppend(s, encodingSize4) - for i := 0; i < n; i += 2 { - b[i/2] = uint8(compress(f[i], 4) | compress(f[i+1], 4)<<4) - } - return s -} - -// ringDecodeAndDecompress4 decodes a 128-byte encoding of a ring element where -// each four bits are mapped to an equidistant distribution. -// -// It implements ByteDecode₄, according to FIPS 203 (DRAFT), Algorithm 5, -// followed by Decompress₄, according to FIPS 203 (DRAFT), Definition 4.6. -func ringDecodeAndDecompress4(b *[encodingSize4]byte) ringElement { - var f ringElement - for i := 0; i < n; i += 2 { - f[i] = fieldElement(decompress(uint16(b[i/2]&0b1111), 4)) - f[i+1] = fieldElement(decompress(uint16(b[i/2]>>4), 4)) - } - return f -} - -// ringCompressAndEncode10 appends a 320-byte encoding of a ring element to s, -// compressing four coefficients per five bytes. -// -// It implements Compress₁₀, according to FIPS 203 (DRAFT), Definition 4.5, -// followed by ByteEncode₁₀, according to FIPS 203 (DRAFT), Algorithm 4. -func ringCompressAndEncode10(s []byte, f ringElement) []byte { - s, b := sliceForAppend(s, encodingSize10) - for i := 0; i < n; i += 4 { - var x uint64 - x |= uint64(compress(f[i+0], 10)) - x |= uint64(compress(f[i+1], 10)) << 10 - x |= uint64(compress(f[i+2], 10)) << 20 - x |= uint64(compress(f[i+3], 10)) << 30 - b[0] = uint8(x) - b[1] = uint8(x >> 8) - b[2] = uint8(x >> 16) - b[3] = uint8(x >> 24) - b[4] = uint8(x >> 32) - b = b[5:] - } - return s -} - -// ringDecodeAndDecompress10 decodes a 320-byte encoding of a ring element where -// each ten bits are mapped to an equidistant distribution. -// -// It implements ByteDecode₁₀, according to FIPS 203 (DRAFT), Algorithm 5, -// followed by Decompress₁₀, according to FIPS 203 (DRAFT), Definition 4.6. -func ringDecodeAndDecompress10(bb *[encodingSize10]byte) ringElement { - b := bb[:] - var f ringElement - for i := 0; i < n; i += 4 { - x := uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 - b = b[5:] - f[i] = fieldElement(decompress(uint16(x>>0&0b11_1111_1111), 10)) - f[i+1] = fieldElement(decompress(uint16(x>>10&0b11_1111_1111), 10)) - f[i+2] = fieldElement(decompress(uint16(x>>20&0b11_1111_1111), 10)) - f[i+3] = fieldElement(decompress(uint16(x>>30&0b11_1111_1111), 10)) - } - return f -} - -// samplePolyCBD draws a ringElement from the special Dη distribution given a -// stream of random bytes generated by the PRF function, according to FIPS 203 -// (DRAFT), Algorithm 7 and Definition 4.1. -func samplePolyCBD(s []byte, b byte) ringElement { - prf := sha3.NewShake256() - prf.Write(s) - prf.Write([]byte{b}) - B := make([]byte, 128) - prf.Read(B) - - // SamplePolyCBD simply draws four (2η) bits for each coefficient, and adds - // the first two and subtracts the last two. - - var f ringElement - for i := 0; i < n; i += 2 { - b := B[i/2] - b_7, b_6, b_5, b_4 := b>>7, b>>6&1, b>>5&1, b>>4&1 - b_3, b_2, b_1, b_0 := b>>3&1, b>>2&1, b>>1&1, b&1 - f[i] = fieldSub(fieldElement(b_0+b_1), fieldElement(b_2+b_3)) - f[i+1] = fieldSub(fieldElement(b_4+b_5), fieldElement(b_6+b_7)) - } - return f -} - -// nttElement is an NTT representation, an element of T_q, represented as an -// array according to FIPS 203 (DRAFT), Section 2.4. -type nttElement [n]fieldElement - -// gammas are the values ζ^2BitRev7(i)+1 mod q for each index i. -var gammas = [128]fieldElement{17, 3312, 2761, 568, 583, 2746, 2649, 680, 1637, 1692, 723, 2606, 2288, 1041, 1100, 2229, 1409, 1920, 2662, 667, 3281, 48, 233, 3096, 756, 2573, 2156, 1173, 3015, 314, 3050, 279, 1703, 1626, 1651, 1678, 2789, 540, 1789, 1540, 1847, 1482, 952, 2377, 1461, 1868, 2687, 642, 939, 2390, 2308, 1021, 2437, 892, 2388, 941, 733, 2596, 2337, 992, 268, 3061, 641, 2688, 1584, 1745, 2298, 1031, 2037, 1292, 3220, 109, 375, 2954, 2549, 780, 2090, 1239, 1645, 1684, 1063, 2266, 319, 3010, 2773, 556, 757, 2572, 2099, 1230, 561, 2768, 2466, 863, 2594, 735, 2804, 525, 1092, 2237, 403, 2926, 1026, 2303, 1143, 2186, 2150, 1179, 2775, 554, 886, 2443, 1722, 1607, 1212, 2117, 1874, 1455, 1029, 2300, 2110, 1219, 2935, 394, 885, 2444, 2154, 1175} - -// nttMul multiplies two nttElements. -// -// It implements MultiplyNTTs, according to FIPS 203 (DRAFT), Algorithm 10. -func nttMul(f, g nttElement) nttElement { - var h nttElement - // We use i += 2 for bounds check elimination. See https://go.dev/issue/66826. - for i := 0; i < 256; i += 2 { - a0, a1 := f[i], f[i+1] - b0, b1 := g[i], g[i+1] - h[i] = fieldAddMul(a0, b0, fieldMul(a1, b1), gammas[i/2]) - h[i+1] = fieldAddMul(a0, b1, a1, b0) - } - return h -} - -// zetas are the values ζ^BitRev7(k) mod q for each index k. -var zetas = [128]fieldElement{1, 1729, 2580, 3289, 2642, 630, 1897, 848, 1062, 1919, 193, 797, 2786, 3260, 569, 1746, 296, 2447, 1339, 1476, 3046, 56, 2240, 1333, 1426, 2094, 535, 2882, 2393, 2879, 1974, 821, 289, 331, 3253, 1756, 1197, 2304, 2277, 2055, 650, 1977, 2513, 632, 2865, 33, 1320, 1915, 2319, 1435, 807, 452, 1438, 2868, 1534, 2402, 2647, 2617, 1481, 648, 2474, 3110, 1227, 910, 17, 2761, 583, 2649, 1637, 723, 2288, 1100, 1409, 2662, 3281, 233, 756, 2156, 3015, 3050, 1703, 1651, 2789, 1789, 1847, 952, 1461, 2687, 939, 2308, 2437, 2388, 733, 2337, 268, 641, 1584, 2298, 2037, 3220, 375, 2549, 2090, 1645, 1063, 319, 2773, 757, 2099, 561, 2466, 2594, 2804, 1092, 403, 1026, 1143, 2150, 2775, 886, 1722, 1212, 1874, 1029, 2110, 2935, 885, 2154} - -// ntt maps a ringElement to its nttElement representation. -// -// It implements NTT, according to FIPS 203 (DRAFT), Algorithm 8. -func ntt(f ringElement) nttElement { - k := 1 - for len := 128; len >= 2; len /= 2 { - for start := 0; start < 256; start += 2 * len { - zeta := zetas[k] - k++ - // Bounds check elimination hint. - f, flen := f[start:start+len], f[start+len:start+len+len] - for j := 0; j < len; j++ { - t := fieldMul(zeta, flen[j]) - flen[j] = fieldSub(f[j], t) - f[j] = fieldAdd(f[j], t) - } - } - } - return nttElement(f) -} - -// inverseNTT maps a nttElement back to the ringElement it represents. -// -// It implements NTT⁻¹, according to FIPS 203 (DRAFT), Algorithm 9. -func inverseNTT(f nttElement) ringElement { - k := 127 - for len := 2; len <= 128; len *= 2 { - for start := 0; start < 256; start += 2 * len { - zeta := zetas[k] - k-- - // Bounds check elimination hint. - f, flen := f[start:start+len], f[start+len:start+len+len] - for j := 0; j < len; j++ { - t := f[j] - f[j] = fieldAdd(t, flen[j]) - flen[j] = fieldMulSub(zeta, flen[j], t) - } - } - } - for i := range f { - f[i] = fieldMul(f[i], 3303) // 3303 = 128⁻¹ mod q - } - return ringElement(f) -} - -// sampleNTT draws a uniformly random nttElement from a stream of uniformly -// random bytes generated by the XOF function, according to FIPS 203 (DRAFT), -// Algorithm 6 and Definition 4.2. -func sampleNTT(rho []byte, ii, jj byte) nttElement { - B := sha3.NewShake128() - B.Write(rho) - B.Write([]byte{ii, jj}) - - // SampleNTT essentially draws 12 bits at a time from r, interprets them in - // little-endian, and rejects values higher than q, until it drew 256 - // values. (The rejection rate is approximately 19%.) - // - // To do this from a bytes stream, it draws three bytes at a time, and - // splits them into two uint16 appropriately masked. - // - // r₀ r₁ r₂ - // |- - - - - - - -|- - - - - - - -|- - - - - - - -| - // - // Uint16(r₀ || r₁) - // |- - - - - - - - - - - - - - - -| - // |- - - - - - - - - - - -| - // d₁ - // - // Uint16(r₁ || r₂) - // |- - - - - - - - - - - - - - - -| - // |- - - - - - - - - - - -| - // d₂ - // - // Note that in little-endian, the rightmost bits are the most significant - // bits (dropped with a mask) and the leftmost bits are the least - // significant bits (dropped with a right shift). - - var a nttElement - var j int // index into a - var buf [24]byte // buffered reads from B - off := len(buf) // index into buf, starts in a "buffer fully consumed" state - for { - if off >= len(buf) { - B.Read(buf[:]) - off = 0 - } - d1 := byteorder.LeUint16(buf[off:]) & 0b1111_1111_1111 - d2 := byteorder.LeUint16(buf[off+1:]) >> 4 - off += 3 - if d1 < q { - a[j] = fieldElement(d1) - j++ - } - if j >= len(a) { - break - } - if d2 < q { - a[j] = fieldElement(d2) - j++ - } - if j >= len(a) { - break - } - } - return a -} diff --git a/crypto/internal/mlkem768/mlkem768_test.go b/crypto/internal/mlkem768/mlkem768_test.go deleted file mode 100644 index 52b31b9ff8c..00000000000 --- a/crypto/internal/mlkem768/mlkem768_test.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2023 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package mlkem768 - -import ( - "bytes" - _ "embed" - "encoding/hex" - "errors" - "flag" - "math/big" - "strconv" - "testing" - - "crypto/rand" - - "github.com/runZeroInc/excrypto/x/crypto/sha3" -) - -func TestFieldReduce(t *testing.T) { - for a := uint32(0); a < 2*q*q; a++ { - got := fieldReduce(a) - exp := fieldElement(a % q) - if got != exp { - t.Fatalf("reduce(%d) = %d, expected %d", a, got, exp) - } - } -} - -func TestFieldAdd(t *testing.T) { - for a := fieldElement(0); a < q; a++ { - for b := fieldElement(0); b < q; b++ { - got := fieldAdd(a, b) - exp := (a + b) % q - if got != exp { - t.Fatalf("%d + %d = %d, expected %d", a, b, got, exp) - } - } - } -} - -func TestFieldSub(t *testing.T) { - for a := fieldElement(0); a < q; a++ { - for b := fieldElement(0); b < q; b++ { - got := fieldSub(a, b) - exp := (a - b + q) % q - if got != exp { - t.Fatalf("%d - %d = %d, expected %d", a, b, got, exp) - } - } - } -} - -func TestFieldMul(t *testing.T) { - for a := fieldElement(0); a < q; a++ { - for b := fieldElement(0); b < q; b++ { - got := fieldMul(a, b) - exp := fieldElement((uint32(a) * uint32(b)) % q) - if got != exp { - t.Fatalf("%d * %d = %d, expected %d", a, b, got, exp) - } - } - } -} - -func TestDecompressCompress(t *testing.T) { - for _, bits := range []uint8{1, 4, 10} { - for a := uint16(0); a < 1<= q { - t.Fatalf("decompress(%d, %d) = %d >= q", a, bits, f) - } - got := compress(f, bits) - if got != a { - t.Fatalf("compress(decompress(%d, %d), %d) = %d", a, bits, bits, got) - } - } - - for a := fieldElement(0); a < q; a++ { - c := compress(a, bits) - if c >= 1<= 2^bits", a, bits, c) - } - got := decompress(c, bits) - diff := min(a-got, got-a, a-got+q, got-a+q) - ceil := q / (1 << bits) - if diff > fieldElement(ceil) { - t.Fatalf("decompress(compress(%d, %d), %d) = %d (diff %d, max diff %d)", - a, bits, bits, got, diff, ceil) - } - } - } -} - -func CompressRat(x fieldElement, d uint8) uint16 { - if x >= q { - panic("x out of range") - } - if d <= 0 || d >= 12 { - panic("d out of range") - } - - precise := big.NewRat((1<= 1<= 12 { - panic("d out of range") - } - - precise := big.NewRat(q*int64(y), 1<>7 != 0 { - panic("not 7 bits") - } - var r uint8 - r |= n >> 6 & 0b0000_0001 - r |= n >> 4 & 0b0000_0010 - r |= n >> 2 & 0b0000_0100 - r |= n /**/ & 0b0000_1000 - r |= n << 2 & 0b0001_0000 - r |= n << 4 & 0b0010_0000 - r |= n << 6 & 0b0100_0000 - return r -} - -func TestZetas(t *testing.T) { - ζ := big.NewInt(17) - q := big.NewInt(q) - for k, zeta := range zetas { - // ζ^BitRev7(k) mod q - exp := new(big.Int).Exp(ζ, big.NewInt(int64(BitRev7(uint8(k)))), q) - if big.NewInt(int64(zeta)).Cmp(exp) != 0 { - t.Errorf("zetas[%d] = %v, expected %v", k, zeta, exp) - } - } -} - -func TestGammas(t *testing.T) { - ζ := big.NewInt(17) - q := big.NewInt(q) - for k, gamma := range gammas { - // ζ^2BitRev7(i)+1 - exp := new(big.Int).Exp(ζ, big.NewInt(int64(BitRev7(uint8(k)))*2+1), q) - if big.NewInt(int64(gamma)).Cmp(exp) != 0 { - t.Errorf("gammas[%d] = %v, expected %v", k, gamma, exp) - } - } -} - -func TestRoundTrip(t *testing.T) { - dk, err := GenerateKey() - if err != nil { - t.Fatal(err) - } - c, Ke, err := Encapsulate(dk.EncapsulationKey()) - if err != nil { - t.Fatal(err) - } - Kd, err := Decapsulate(dk, c) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(Ke, Kd) { - t.Fail() - } - - dk1, err := GenerateKey() - if err != nil { - t.Fatal(err) - } - if bytes.Equal(dk.EncapsulationKey(), dk1.EncapsulationKey()) { - t.Fail() - } - if bytes.Equal(dk.Bytes(), dk1.Bytes()) { - t.Fail() - } - if bytes.Equal(dk.Bytes()[EncapsulationKeySize-32:], dk1.Bytes()[EncapsulationKeySize-32:]) { - t.Fail() - } - - c1, Ke1, err := Encapsulate(dk.EncapsulationKey()) - if err != nil { - t.Fatal(err) - } - if bytes.Equal(c, c1) { - t.Fail() - } - if bytes.Equal(Ke, Ke1) { - t.Fail() - } -} - -func TestBadLengths(t *testing.T) { - dk, err := GenerateKey() - if err != nil { - t.Fatal(err) - } - ek := dk.EncapsulationKey() - - for i := 0; i < len(ek)-1; i++ { - if _, _, err := Encapsulate(ek[:i]); err == nil { - t.Errorf("expected error for ek length %d", i) - } - } - ekLong := ek - for i := 0; i < 100; i++ { - ekLong = append(ekLong, 0) - if _, _, err := Encapsulate(ekLong); err == nil { - t.Errorf("expected error for ek length %d", len(ekLong)) - } - } - - c, _, err := Encapsulate(ek) - if err != nil { - t.Fatal(err) - } - - for i := 0; i < len(dk.Bytes())-1; i++ { - if _, err := NewKeyFromExtendedEncoding(dk.Bytes()[:i]); err == nil { - t.Errorf("expected error for dk length %d", i) - } - } - dkLong := dk.Bytes() - for i := 0; i < 100; i++ { - dkLong = append(dkLong, 0) - if _, err := NewKeyFromExtendedEncoding(dkLong); err == nil { - t.Errorf("expected error for dk length %d", len(dkLong)) - } - } - - for i := 0; i < len(c)-1; i++ { - if _, err := Decapsulate(dk, c[:i]); err == nil { - t.Errorf("expected error for c length %d", i) - } - } - cLong := c - for i := 0; i < 100; i++ { - cLong = append(cLong, 0) - if _, err := Decapsulate(dk, cLong); err == nil { - t.Errorf("expected error for c length %d", len(cLong)) - } - } -} - -func EncapsulateDerand(ek, m []byte) (c, K []byte, err error) { - if len(m) != messageSize { - return nil, nil, errors.New("bad message length") - } - return kemEncaps(nil, ek, (*[messageSize]byte)(m)) -} - -func DecapsulateFromBytes(dkBytes []byte, c []byte) ([]byte, error) { - dk, err := NewKeyFromExtendedEncoding(dkBytes) - if err != nil { - return nil, err - } - return Decapsulate(dk, c) -} - -func GenerateKeyDerand(t testing.TB, d, z []byte) ([]byte, *DecapsulationKey) { - if len(d) != 32 || len(z) != 32 { - t.Fatal("bad length") - } - dk := kemKeyGen(nil, (*[32]byte)(d), (*[32]byte)(z)) - return dk.EncapsulationKey(), dk -} - -var millionFlag = flag.Bool("million", false, "run the million vector test") - -// TestPQCrystalsAccumulated accumulates the 10k vectors generated by the -// reference implementation and checks the hash of the result, to avoid checking -// in 150MB of test vectors. -func TestPQCrystalsAccumulated(t *testing.T) { - n := 10000 - expected := "f7db260e1137a742e05fe0db9525012812b004d29040a5b606aad3d134b548d3" - if testing.Short() { - n = 100 - expected = "8d0c478ead6037897a0da6be21e5399545babf5fc6dd10c061c99b7dee2bf0dc" - } - if *millionFlag { - n = 1000000 - expected = "70090cc5842aad0ec43d5042c783fae9bc320c047b5dafcb6e134821db02384d" - } - - s := sha3.NewShake128() - o := sha3.NewShake128() - d := make([]byte, 32) - z := make([]byte, 32) - msg := make([]byte, 32) - ct1 := make([]byte, CiphertextSize) - - for i := 0; i < n; i++ { - s.Read(d) - s.Read(z) - ek, dk := GenerateKeyDerand(t, d, z) - o.Write(ek) - o.Write(dk.Bytes()) - - s.Read(msg) - ct, k, err := EncapsulateDerand(ek, msg) - if err != nil { - t.Fatal(err) - } - o.Write(ct) - o.Write(k) - - kk, err := Decapsulate(dk, ct) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(kk, k) { - t.Errorf("k: got %x, expected %x", kk, k) - } - - s.Read(ct1) - k1, err := Decapsulate(dk, ct1) - if err != nil { - t.Fatal(err) - } - o.Write(k1) - } - - got := hex.EncodeToString(o.Sum(nil)) - if got != expected { - t.Errorf("got %s, expected %s", got, expected) - } -} - -var sink byte - -func BenchmarkKeyGen(b *testing.B) { - var dk DecapsulationKey - var d, z [32]byte - rand.Read(d[:]) - rand.Read(z[:]) - b.ResetTimer() - for i := 0; i < b.N; i++ { - dk := kemKeyGen(&dk, &d, &z) - sink ^= dk.EncapsulationKey()[0] - } -} - -func BenchmarkEncaps(b *testing.B) { - d := make([]byte, 32) - rand.Read(d) - z := make([]byte, 32) - rand.Read(z) - var m [messageSize]byte - rand.Read(m[:]) - ek, _ := GenerateKeyDerand(b, d, z) - var c [CiphertextSize]byte - b.ResetTimer() - for i := 0; i < b.N; i++ { - c, K, err := kemEncaps(&c, ek, &m) - if err != nil { - b.Fatal(err) - } - sink ^= c[0] ^ K[0] - } -} - -func BenchmarkDecaps(b *testing.B) { - d := make([]byte, 32) - rand.Read(d) - z := make([]byte, 32) - rand.Read(z) - m := make([]byte, 32) - rand.Read(m) - ek, dk := GenerateKeyDerand(b, d, z) - c, _, err := EncapsulateDerand(ek, m) - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - K := kemDecaps(dk, (*[CiphertextSize]byte)(c)) - sink ^= K[0] - } -} - -func BenchmarkRoundTrip(b *testing.B) { - dk, err := GenerateKey() - if err != nil { - b.Fatal(err) - } - ek := dk.EncapsulationKey() - c, _, err := Encapsulate(ek) - if err != nil { - b.Fatal(err) - } - b.Run("Alice", func(b *testing.B) { - for i := 0; i < b.N; i++ { - dkS, err := GenerateKey() - if err != nil { - b.Fatal(err) - } - ekS := dkS.EncapsulationKey() - sink ^= ekS[0] - - Ks, err := Decapsulate(dk, c) - if err != nil { - b.Fatal(err) - } - sink ^= Ks[0] - } - }) - b.Run("Bob", func(b *testing.B) { - for i := 0; i < b.N; i++ { - cS, Ks, err := Encapsulate(ek) - if err != nil { - b.Fatal(err) - } - sink ^= cS[0] ^ Ks[0] - } - }) -} diff --git a/crypto/internal/nistec/_asm/go.mod b/crypto/internal/nistec/_asm/go.mod deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/internal/nistec/_asm/go.sum b/crypto/internal/nistec/_asm/go.sum deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/internal/nistec/p256.go b/crypto/internal/nistec/p256.go deleted file mode 100644 index 392b494346b..00000000000 --- a/crypto/internal/nistec/p256.go +++ /dev/null @@ -1,509 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Code generated by generate.go. DO NOT EDIT. - -//go:build (!amd64 && !arm64 && !ppc64le && !s390x) || purego - -package nistec - -import ( - "github.com/runZeroInc/excrypto/crypto/internal/nistec/fiat" - "github.com/runZeroInc/excrypto/crypto/subtle" - "errors" - "sync" -) - -// p256ElementLength is the length of an element of the base or scalar field, -// which have the same bytes length for all NIST P curves. -const p256ElementLength = 32 - -// P256Point is a P256 point. The zero value is NOT valid. -type P256Point struct { - // The point is represented in projective coordinates (X:Y:Z), - // where x = X/Z and y = Y/Z. - x, y, z *fiat.P256Element -} - -// NewP256Point returns a new P256Point representing the point at infinity point. -func NewP256Point() *P256Point { - return &P256Point{ - x: new(fiat.P256Element), - y: new(fiat.P256Element).One(), - z: new(fiat.P256Element), - } -} - -// SetGenerator sets p to the canonical generator and returns p. -func (p *P256Point) SetGenerator() *P256Point { - p.x.SetBytes([]byte{0x6b, 0x17, 0xd1, 0xf2, 0xe1, 0x2c, 0x42, 0x47, 0xf8, 0xbc, 0xe6, 0xe5, 0x63, 0xa4, 0x40, 0xf2, 0x77, 0x3, 0x7d, 0x81, 0x2d, 0xeb, 0x33, 0xa0, 0xf4, 0xa1, 0x39, 0x45, 0xd8, 0x98, 0xc2, 0x96}) - p.y.SetBytes([]byte{0x4f, 0xe3, 0x42, 0xe2, 0xfe, 0x1a, 0x7f, 0x9b, 0x8e, 0xe7, 0xeb, 0x4a, 0x7c, 0xf, 0x9e, 0x16, 0x2b, 0xce, 0x33, 0x57, 0x6b, 0x31, 0x5e, 0xce, 0xcb, 0xb6, 0x40, 0x68, 0x37, 0xbf, 0x51, 0xf5}) - p.z.One() - return p -} - -// Set sets p = q and returns p. -func (p *P256Point) Set(q *P256Point) *P256Point { - p.x.Set(q.x) - p.y.Set(q.y) - p.z.Set(q.z) - return p -} - -// SetBytes sets p to the compressed, uncompressed, or infinity value encoded in -// b, as specified in SEC 1, Version 2.0, Section 2.3.4. If the point is not on -// the curve, it returns nil and an error, and the receiver is unchanged. -// Otherwise, it returns p. -func (p *P256Point) SetBytes(b []byte) (*P256Point, error) { - switch { - // Point at infinity. - case len(b) == 1 && b[0] == 0: - return p.Set(NewP256Point()), nil - - // Uncompressed form. - case len(b) == 1+2*p256ElementLength && b[0] == 4: - x, err := new(fiat.P256Element).SetBytes(b[1 : 1+p256ElementLength]) - if err != nil { - return nil, err - } - y, err := new(fiat.P256Element).SetBytes(b[1+p256ElementLength:]) - if err != nil { - return nil, err - } - if err := p256CheckOnCurve(x, y); err != nil { - return nil, err - } - p.x.Set(x) - p.y.Set(y) - p.z.One() - return p, nil - - // Compressed form. - case len(b) == 1+p256ElementLength && (b[0] == 2 || b[0] == 3): - x, err := new(fiat.P256Element).SetBytes(b[1:]) - if err != nil { - return nil, err - } - - // y² = x³ - 3x + b - y := p256Polynomial(new(fiat.P256Element), x) - if !p256Sqrt(y, y) { - return nil, errors.New("invalid P256 compressed point encoding") - } - - // Select the positive or negative root, as indicated by the least - // significant bit, based on the encoding type byte. - otherRoot := new(fiat.P256Element) - otherRoot.Sub(otherRoot, y) - cond := y.Bytes()[p256ElementLength-1]&1 ^ b[0]&1 - y.Select(otherRoot, y, int(cond)) - - p.x.Set(x) - p.y.Set(y) - p.z.One() - return p, nil - - default: - return nil, errors.New("invalid P256 point encoding") - } -} - -var _p256B *fiat.P256Element -var _p256BOnce sync.Once - -func p256B() *fiat.P256Element { - _p256BOnce.Do(func() { - _p256B, _ = new(fiat.P256Element).SetBytes([]byte{0x5a, 0xc6, 0x35, 0xd8, 0xaa, 0x3a, 0x93, 0xe7, 0xb3, 0xeb, 0xbd, 0x55, 0x76, 0x98, 0x86, 0xbc, 0x65, 0x1d, 0x6, 0xb0, 0xcc, 0x53, 0xb0, 0xf6, 0x3b, 0xce, 0x3c, 0x3e, 0x27, 0xd2, 0x60, 0x4b}) - }) - return _p256B -} - -// p256Polynomial sets y2 to x³ - 3x + b, and returns y2. -func p256Polynomial(y2, x *fiat.P256Element) *fiat.P256Element { - y2.Square(x) - y2.Mul(y2, x) - - threeX := new(fiat.P256Element).Add(x, x) - threeX.Add(threeX, x) - y2.Sub(y2, threeX) - - return y2.Add(y2, p256B()) -} - -func p256CheckOnCurve(x, y *fiat.P256Element) error { - // y² = x³ - 3x + b - rhs := p256Polynomial(new(fiat.P256Element), x) - lhs := new(fiat.P256Element).Square(y) - if rhs.Equal(lhs) != 1 { - return errors.New("P256 point not on curve") - } - return nil -} - -// Bytes returns the uncompressed or infinity encoding of p, as specified in -// SEC 1, Version 2.0, Section 2.3.3. Note that the encoding of the point at -// infinity is shorter than all other encodings. -func (p *P256Point) Bytes() []byte { - // This function is outlined to make the allocations inline in the caller - // rather than happen on the heap. - var out [1 + 2*p256ElementLength]byte - return p.bytes(&out) -} - -func (p *P256Point) bytes(out *[1 + 2*p256ElementLength]byte) []byte { - if p.z.IsZero() == 1 { - return append(out[:0], 0) - } - - zinv := new(fiat.P256Element).Invert(p.z) - x := new(fiat.P256Element).Mul(p.x, zinv) - y := new(fiat.P256Element).Mul(p.y, zinv) - - buf := append(out[:0], 4) - buf = append(buf, x.Bytes()...) - buf = append(buf, y.Bytes()...) - return buf -} - -// BytesX returns the encoding of the x-coordinate of p, as specified in SEC 1, -// Version 2.0, Section 2.3.5, or an error if p is the point at infinity. -func (p *P256Point) BytesX() ([]byte, error) { - // This function is outlined to make the allocations inline in the caller - // rather than happen on the heap. - var out [p256ElementLength]byte - return p.bytesX(&out) -} - -func (p *P256Point) bytesX(out *[p256ElementLength]byte) ([]byte, error) { - if p.z.IsZero() == 1 { - return nil, errors.New("P256 point is the point at infinity") - } - - zinv := new(fiat.P256Element).Invert(p.z) - x := new(fiat.P256Element).Mul(p.x, zinv) - - return append(out[:0], x.Bytes()...), nil -} - -// BytesCompressed returns the compressed or infinity encoding of p, as -// specified in SEC 1, Version 2.0, Section 2.3.3. Note that the encoding of the -// point at infinity is shorter than all other encodings. -func (p *P256Point) BytesCompressed() []byte { - // This function is outlined to make the allocations inline in the caller - // rather than happen on the heap. - var out [1 + p256ElementLength]byte - return p.bytesCompressed(&out) -} - -func (p *P256Point) bytesCompressed(out *[1 + p256ElementLength]byte) []byte { - if p.z.IsZero() == 1 { - return append(out[:0], 0) - } - - zinv := new(fiat.P256Element).Invert(p.z) - x := new(fiat.P256Element).Mul(p.x, zinv) - y := new(fiat.P256Element).Mul(p.y, zinv) - - // Encode the sign of the y coordinate (indicated by the least significant - // bit) as the encoding type (2 or 3). - buf := append(out[:0], 2) - buf[0] |= y.Bytes()[p256ElementLength-1] & 1 - buf = append(buf, x.Bytes()...) - return buf -} - -// Add sets q = p1 + p2, and returns q. The points may overlap. -func (q *P256Point) Add(p1, p2 *P256Point) *P256Point { - // Complete addition formula for a = -3 from "Complete addition formulas for - // prime order elliptic curves" (https://eprint.iacr.org/2015/1060), §A.2. - - t0 := new(fiat.P256Element).Mul(p1.x, p2.x) // t0 := X1 * X2 - t1 := new(fiat.P256Element).Mul(p1.y, p2.y) // t1 := Y1 * Y2 - t2 := new(fiat.P256Element).Mul(p1.z, p2.z) // t2 := Z1 * Z2 - t3 := new(fiat.P256Element).Add(p1.x, p1.y) // t3 := X1 + Y1 - t4 := new(fiat.P256Element).Add(p2.x, p2.y) // t4 := X2 + Y2 - t3.Mul(t3, t4) // t3 := t3 * t4 - t4.Add(t0, t1) // t4 := t0 + t1 - t3.Sub(t3, t4) // t3 := t3 - t4 - t4.Add(p1.y, p1.z) // t4 := Y1 + Z1 - x3 := new(fiat.P256Element).Add(p2.y, p2.z) // X3 := Y2 + Z2 - t4.Mul(t4, x3) // t4 := t4 * X3 - x3.Add(t1, t2) // X3 := t1 + t2 - t4.Sub(t4, x3) // t4 := t4 - X3 - x3.Add(p1.x, p1.z) // X3 := X1 + Z1 - y3 := new(fiat.P256Element).Add(p2.x, p2.z) // Y3 := X2 + Z2 - x3.Mul(x3, y3) // X3 := X3 * Y3 - y3.Add(t0, t2) // Y3 := t0 + t2 - y3.Sub(x3, y3) // Y3 := X3 - Y3 - z3 := new(fiat.P256Element).Mul(p256B(), t2) // Z3 := b * t2 - x3.Sub(y3, z3) // X3 := Y3 - Z3 - z3.Add(x3, x3) // Z3 := X3 + X3 - x3.Add(x3, z3) // X3 := X3 + Z3 - z3.Sub(t1, x3) // Z3 := t1 - X3 - x3.Add(t1, x3) // X3 := t1 + X3 - y3.Mul(p256B(), y3) // Y3 := b * Y3 - t1.Add(t2, t2) // t1 := t2 + t2 - t2.Add(t1, t2) // t2 := t1 + t2 - y3.Sub(y3, t2) // Y3 := Y3 - t2 - y3.Sub(y3, t0) // Y3 := Y3 - t0 - t1.Add(y3, y3) // t1 := Y3 + Y3 - y3.Add(t1, y3) // Y3 := t1 + Y3 - t1.Add(t0, t0) // t1 := t0 + t0 - t0.Add(t1, t0) // t0 := t1 + t0 - t0.Sub(t0, t2) // t0 := t0 - t2 - t1.Mul(t4, y3) // t1 := t4 * Y3 - t2.Mul(t0, y3) // t2 := t0 * Y3 - y3.Mul(x3, z3) // Y3 := X3 * Z3 - y3.Add(y3, t2) // Y3 := Y3 + t2 - x3.Mul(t3, x3) // X3 := t3 * X3 - x3.Sub(x3, t1) // X3 := X3 - t1 - z3.Mul(t4, z3) // Z3 := t4 * Z3 - t1.Mul(t3, t0) // t1 := t3 * t0 - z3.Add(z3, t1) // Z3 := Z3 + t1 - - q.x.Set(x3) - q.y.Set(y3) - q.z.Set(z3) - return q -} - -// Double sets q = p + p, and returns q. The points may overlap. -func (q *P256Point) Double(p *P256Point) *P256Point { - // Complete addition formula for a = -3 from "Complete addition formulas for - // prime order elliptic curves" (https://eprint.iacr.org/2015/1060), §A.2. - - t0 := new(fiat.P256Element).Square(p.x) // t0 := X ^ 2 - t1 := new(fiat.P256Element).Square(p.y) // t1 := Y ^ 2 - t2 := new(fiat.P256Element).Square(p.z) // t2 := Z ^ 2 - t3 := new(fiat.P256Element).Mul(p.x, p.y) // t3 := X * Y - t3.Add(t3, t3) // t3 := t3 + t3 - z3 := new(fiat.P256Element).Mul(p.x, p.z) // Z3 := X * Z - z3.Add(z3, z3) // Z3 := Z3 + Z3 - y3 := new(fiat.P256Element).Mul(p256B(), t2) // Y3 := b * t2 - y3.Sub(y3, z3) // Y3 := Y3 - Z3 - x3 := new(fiat.P256Element).Add(y3, y3) // X3 := Y3 + Y3 - y3.Add(x3, y3) // Y3 := X3 + Y3 - x3.Sub(t1, y3) // X3 := t1 - Y3 - y3.Add(t1, y3) // Y3 := t1 + Y3 - y3.Mul(x3, y3) // Y3 := X3 * Y3 - x3.Mul(x3, t3) // X3 := X3 * t3 - t3.Add(t2, t2) // t3 := t2 + t2 - t2.Add(t2, t3) // t2 := t2 + t3 - z3.Mul(p256B(), z3) // Z3 := b * Z3 - z3.Sub(z3, t2) // Z3 := Z3 - t2 - z3.Sub(z3, t0) // Z3 := Z3 - t0 - t3.Add(z3, z3) // t3 := Z3 + Z3 - z3.Add(z3, t3) // Z3 := Z3 + t3 - t3.Add(t0, t0) // t3 := t0 + t0 - t0.Add(t3, t0) // t0 := t3 + t0 - t0.Sub(t0, t2) // t0 := t0 - t2 - t0.Mul(t0, z3) // t0 := t0 * Z3 - y3.Add(y3, t0) // Y3 := Y3 + t0 - t0.Mul(p.y, p.z) // t0 := Y * Z - t0.Add(t0, t0) // t0 := t0 + t0 - z3.Mul(t0, z3) // Z3 := t0 * Z3 - x3.Sub(x3, z3) // X3 := X3 - Z3 - z3.Mul(t0, t1) // Z3 := t0 * t1 - z3.Add(z3, z3) // Z3 := Z3 + Z3 - z3.Add(z3, z3) // Z3 := Z3 + Z3 - - q.x.Set(x3) - q.y.Set(y3) - q.z.Set(z3) - return q -} - -// Select sets q to p1 if cond == 1, and to p2 if cond == 0. -func (q *P256Point) Select(p1, p2 *P256Point, cond int) *P256Point { - q.x.Select(p1.x, p2.x, cond) - q.y.Select(p1.y, p2.y, cond) - q.z.Select(p1.z, p2.z, cond) - return q -} - -// A p256Table holds the first 15 multiples of a point at offset -1, so [1]P -// is at table[0], [15]P is at table[14], and [0]P is implicitly the identity -// point. -type p256Table [15]*P256Point - -// Select selects the n-th multiple of the table base point into p. It works in -// constant time by iterating over every entry of the table. n must be in [0, 15]. -func (table *p256Table) Select(p *P256Point, n uint8) { - if n >= 16 { - panic("nistec: internal error: p256Table called with out-of-bounds value") - } - p.Set(NewP256Point()) - for i := uint8(1); i < 16; i++ { - cond := subtle.ConstantTimeByteEq(i, n) - p.Select(table[i-1], p, cond) - } -} - -// ScalarMult sets p = scalar * q, and returns p. -func (p *P256Point) ScalarMult(q *P256Point, scalar []byte) (*P256Point, error) { - // Compute a p256Table for the base point q. The explicit NewP256Point - // calls get inlined, letting the allocations live on the stack. - var table = p256Table{NewP256Point(), NewP256Point(), NewP256Point(), - NewP256Point(), NewP256Point(), NewP256Point(), NewP256Point(), - NewP256Point(), NewP256Point(), NewP256Point(), NewP256Point(), - NewP256Point(), NewP256Point(), NewP256Point(), NewP256Point()} - table[0].Set(q) - for i := 1; i < 15; i += 2 { - table[i].Double(table[i/2]) - table[i+1].Add(table[i], q) - } - - // Instead of doing the classic double-and-add chain, we do it with a - // four-bit window: we double four times, and then add [0-15]P. - t := NewP256Point() - p.Set(NewP256Point()) - for i, byte := range scalar { - // No need to double on the first iteration, as p is the identity at - // this point, and [N]∞ = ∞. - if i != 0 { - p.Double(p) - p.Double(p) - p.Double(p) - p.Double(p) - } - - windowValue := byte >> 4 - table.Select(t, windowValue) - p.Add(p, t) - - p.Double(p) - p.Double(p) - p.Double(p) - p.Double(p) - - windowValue = byte & 0b1111 - table.Select(t, windowValue) - p.Add(p, t) - } - - return p, nil -} - -var p256GeneratorTable *[p256ElementLength * 2]p256Table -var p256GeneratorTableOnce sync.Once - -// generatorTable returns a sequence of p256Tables. The first table contains -// multiples of G. Each successive table is the previous table doubled four -// times. -func (p *P256Point) generatorTable() *[p256ElementLength * 2]p256Table { - p256GeneratorTableOnce.Do(func() { - p256GeneratorTable = new([p256ElementLength * 2]p256Table) - base := NewP256Point().SetGenerator() - for i := 0; i < p256ElementLength*2; i++ { - p256GeneratorTable[i][0] = NewP256Point().Set(base) - for j := 1; j < 15; j++ { - p256GeneratorTable[i][j] = NewP256Point().Add(p256GeneratorTable[i][j-1], base) - } - base.Double(base) - base.Double(base) - base.Double(base) - base.Double(base) - } - }) - return p256GeneratorTable -} - -// ScalarBaseMult sets p = scalar * B, where B is the canonical generator, and -// returns p. -func (p *P256Point) ScalarBaseMult(scalar []byte) (*P256Point, error) { - if len(scalar) != p256ElementLength { - return nil, errors.New("invalid scalar length") - } - tables := p.generatorTable() - - // This is also a scalar multiplication with a four-bit window like in - // ScalarMult, but in this case the doublings are precomputed. The value - // [windowValue]G added at iteration k would normally get doubled - // (totIterations-k)×4 times, but with a larger precomputation we can - // instead add [2^((totIterations-k)×4)][windowValue]G and avoid the - // doublings between iterations. - t := NewP256Point() - p.Set(NewP256Point()) - tableIndex := len(tables) - 1 - for _, byte := range scalar { - windowValue := byte >> 4 - tables[tableIndex].Select(t, windowValue) - p.Add(p, t) - tableIndex-- - - windowValue = byte & 0b1111 - tables[tableIndex].Select(t, windowValue) - p.Add(p, t) - tableIndex-- - } - - return p, nil -} - -// p256Sqrt sets e to a square root of x. If x is not a square, p256Sqrt returns -// false and e is unchanged. e and x can overlap. -func p256Sqrt(e, x *fiat.P256Element) (isSquare bool) { - candidate := new(fiat.P256Element) - p256SqrtCandidate(candidate, x) - square := new(fiat.P256Element).Square(candidate) - if square.Equal(x) != 1 { - return false - } - e.Set(candidate) - return true -} - -// p256SqrtCandidate sets z to a square root candidate for x. z and x must not overlap. -func p256SqrtCandidate(z, x *fiat.P256Element) { - // Since p = 3 mod 4, exponentiation by (p + 1) / 4 yields a square root candidate. - // - // The sequence of 7 multiplications and 253 squarings is derived from the - // following addition chain generated with github.com/mmcloughlin/addchain v0.4.0. - // - // _10 = 2*1 - // _11 = 1 + _10 - // _1100 = _11 << 2 - // _1111 = _11 + _1100 - // _11110000 = _1111 << 4 - // _11111111 = _1111 + _11110000 - // x16 = _11111111 << 8 + _11111111 - // x32 = x16 << 16 + x16 - // return ((x32 << 32 + 1) << 96 + 1) << 94 - // - var t0 = new(fiat.P256Element) - - z.Square(x) - z.Mul(x, z) - t0.Square(z) - for s := 1; s < 2; s++ { - t0.Square(t0) - } - z.Mul(z, t0) - t0.Square(z) - for s := 1; s < 4; s++ { - t0.Square(t0) - } - z.Mul(z, t0) - t0.Square(z) - for s := 1; s < 8; s++ { - t0.Square(t0) - } - z.Mul(z, t0) - t0.Square(z) - for s := 1; s < 16; s++ { - t0.Square(t0) - } - z.Mul(z, t0) - for s := 0; s < 32; s++ { - z.Square(z) - } - z.Mul(x, z) - for s := 0; s < 96; s++ { - z.Square(z) - } - z.Mul(x, z) - for s := 0; s < 94; s++ { - z.Square(z) - } -} diff --git a/crypto/internal/nistec/p256_asm_table.bin b/crypto/internal/nistec/p256_asm_table.bin deleted file mode 100644 index 20c527e4e0eebf1056cbc81b4f98810f8427bc34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88064 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�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 LuMtEv?ZM{}AK^ym diff --git a/crypto/internal/sysrand/internal/seccomp/seccomp_linux.go b/crypto/internal/sysrand/internal/seccomp/seccomp_linux.go new file mode 100644 index 00000000000..32ef52ad9e4 --- /dev/null +++ b/crypto/internal/sysrand/internal/seccomp/seccomp_linux.go @@ -0,0 +1,83 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package seccomp + +/* +#include +#include +#include +#include +#include +#include + +// A few definitions copied from linux/filter.h and linux/seccomp.h, +// which might not be available on all systems. + +struct sock_filter { + uint16_t code; + uint8_t jt; + uint8_t jf; + uint32_t k; +}; + +struct sock_fprog { + unsigned short len; + struct sock_filter *filter; +}; + +#define BPF_LD 0x00 +#define BPF_W 0x00 +#define BPF_ABS 0x20 +#define BPF_JMP 0x05 +#define BPF_JEQ 0x10 +#define BPF_K 0x00 +#define BPF_RET 0x06 + +#define BPF_STMT(code, k) { (unsigned short)(code), 0, 0, k } +#define BPF_JUMP(code, k, jt, jf) { (unsigned short)(code), jt, jf, k } + +struct seccomp_data { + int nr; + uint32_t arch; + uint64_t instruction_pointer; + uint64_t args[6]; +}; + +#define SECCOMP_RET_ERRNO 0x00050000U +#define SECCOMP_RET_ALLOW 0x7fff0000U +#define SECCOMP_SET_MODE_FILTER 1 + +int disable_getrandom() { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + return 1; + } + struct sock_filter filter[] = { + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_getrandom, 0, 1), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | ENOSYS), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + }; + struct sock_fprog prog = { + .len = sizeof(filter) / sizeof((filter)[0]), + .filter = filter, + }; + if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) { + return 2; + } + return 0; +} +*/ +import "C" +import "fmt" + +// DisableGetrandom makes future calls to getrandom(2) fail with ENOSYS. It +// applies only to the current thread and to any programs executed from it. +// Callers should use [runtime.LockOSThread] in a dedicated goroutine. +func DisableGetrandom() error { + if errno := C.disable_getrandom(); errno != 0 { + return fmt.Errorf("failed to disable getrandom: %v", errno) + } + return nil +} diff --git a/crypto/internal/sysrand/internal/seccomp/seccomp_unsupported.go b/crypto/internal/sysrand/internal/seccomp/seccomp_unsupported.go new file mode 100644 index 00000000000..f08cd1f4ec1 --- /dev/null +++ b/crypto/internal/sysrand/internal/seccomp/seccomp_unsupported.go @@ -0,0 +1,13 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !linux || !cgo + +package seccomp + +import "errors" + +func DisableGetrandom() error { + return errors.New("disabling getrandom is not supported on this system") +} diff --git a/crypto/internal/sysrand/rand.go b/crypto/internal/sysrand/rand.go new file mode 100644 index 00000000000..034bf617155 --- /dev/null +++ b/crypto/internal/sysrand/rand.go @@ -0,0 +1,77 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rand provides cryptographically secure random bytes from the +// operating system. +package sysrand + +import ( + "os" + "sync" + "sync/atomic" + "time" + _ "unsafe" +) + +var firstUse atomic.Bool + +func warnBlocked() { + println("crypto/rand: blocked for 60 seconds waiting to read random data from the kernel") +} + +// fatal is [runtime.fatal], pushed via linkname. +// +//go:linkname fatal +func fatal(string) + +var testingOnlyFailRead bool + +// Read fills b with cryptographically secure random bytes from the operating +// system. It always fills b entirely and crashes the program irrecoverably if +// an error is encountered. The operating system APIs are documented to never +// return an error on all but legacy Linux systems. +func Read(b []byte) { + if firstUse.CompareAndSwap(false, true) { + // First use of randomness. Start timer to warn about + // being blocked on entropy not being available. + t := time.AfterFunc(time.Minute, warnBlocked) + defer t.Stop() + } + if err := read(b); err != nil || testingOnlyFailRead { + var errStr string + if !testingOnlyFailRead { + errStr = err.Error() + } else { + errStr = "testing simulated failure" + } + fatal("crypto/rand: failed to read random data (see https://go.dev/issue/66821): " + errStr) + panic("unreachable") // To be sure. + } +} + +// The urandom fallback is only used on Linux kernels before 3.17 and on AIX. + +var urandomOnce sync.Once +var urandomFile *os.File +var urandomErr error + +func urandomRead(b []byte) error { + urandomOnce.Do(func() { + urandomFile, urandomErr = os.Open("/dev/urandom") + }) + if urandomErr != nil { + return urandomErr + } + for len(b) > 0 { + n, err := urandomFile.Read(b) + // Note that we don't ignore EAGAIN because it should not be possible to + // hit for a blocking read from urandom, although there were + // unreproducible reports of it at https://go.dev/issue/9205. + if err != nil { + return err + } + b = b[n:] + } + return nil +} diff --git a/crypto/internal/sysrand/rand_aix.go b/crypto/internal/sysrand/rand_aix.go new file mode 100644 index 00000000000..52928b6d74a --- /dev/null +++ b/crypto/internal/sysrand/rand_aix.go @@ -0,0 +1,9 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +func read(b []byte) error { + return urandomRead(b) +} diff --git a/crypto/internal/sysrand/rand_arc4random.go b/crypto/internal/sysrand/rand_arc4random.go new file mode 100644 index 00000000000..aee97c4a842 --- /dev/null +++ b/crypto/internal/sysrand/rand_arc4random.go @@ -0,0 +1,22 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build darwin || openbsd + +package sysrand + +import "internal/syscall/unix" + +// arc4random_buf is the recommended application CSPRNG, accepts buffers of +// any size, and never returns an error. +// +// "The subsystem is re-seeded from the kernel random number subsystem on a +// regular basis, and also upon fork(2)." - arc4random(3) +// +// Note that despite its legacy name, it uses a secure CSPRNG (not RC4) in +// all supported macOS versions. +func read(b []byte) error { + unix.ARC4Random(b) + return nil +} diff --git a/crypto/internal/sysrand/rand_getrandom.go b/crypto/internal/sysrand/rand_getrandom.go new file mode 100644 index 00000000000..11e9683a4fa --- /dev/null +++ b/crypto/internal/sysrand/rand_getrandom.go @@ -0,0 +1,64 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build dragonfly || freebsd || linux || solaris + +package sysrand + +import ( + "errors" + "internal/syscall/unix" + "math" + "runtime" + "syscall" +) + +func read(b []byte) error { + // Linux, DragonFly, and illumos don't have a limit on the buffer size. + // FreeBSD has a limit of IOSIZE_MAX, which seems to be either INT_MAX or + // SSIZE_MAX. 2^31-1 is a safe and high enough value to use for all of them. + // + // Note that Linux returns "a maximum of 32Mi-1 bytes", but that will only + // result in a short read, not an error. Short reads can also happen above + // 256 bytes due to signals. Reads up to 256 bytes are guaranteed not to + // return short (and not to return an error IF THE POOL IS INITIALIZED) on + // at least Linux, FreeBSD, DragonFly, and Oracle Solaris, but we don't make + // use of that. + maxSize := math.MaxInt32 + + // Oracle Solaris has a limit of 133120 bytes. Very specific. + // + // The getrandom() and getentropy() functions fail if: [...] + // + // - bufsz is <= 0 or > 133120, when GRND_RANDOM is not set + // + // https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html + if runtime.GOOS == "solaris" { + maxSize = 133120 + } + + for len(b) > 0 { + size := len(b) + if size > maxSize { + size = maxSize + } + n, err := unix.GetRandom(b[:size], 0) + if errors.Is(err, syscall.ENOSYS) { + // If getrandom(2) is not available, presumably on Linux versions + // earlier than 3.17, fall back to reading from /dev/urandom. + return urandomRead(b) + } + if errors.Is(err, syscall.EINTR) { + // If getrandom(2) is blocking, either because it is waiting for the + // entropy pool to become initialized or because we requested more + // than 256 bytes, it might get interrupted by a signal. + continue + } + if err != nil { + return err + } + b = b[n:] + } + return nil +} diff --git a/crypto/internal/sysrand/rand_js.go b/crypto/internal/sysrand/rand_js.go new file mode 100644 index 00000000000..b9eb8e78ea2 --- /dev/null +++ b/crypto/internal/sysrand/rand_js.go @@ -0,0 +1,27 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +// The maximum buffer size for crypto.getRandomValues is 65536 bytes. +// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues#exceptions +const maxGetRandomRead = 64 << 10 + +//go:wasmimport gojs runtime.getRandomData +//go:noescape +func getRandomValues(r []byte) + +// read calls the JavaScript Crypto.getRandomValues() method. +// See https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues. +func read(b []byte) error { + for len(b) > 0 { + size := len(b) + if size > maxGetRandomRead { + size = maxGetRandomRead + } + getRandomValues(b[:size]) + b = b[size:] + } + return nil +} diff --git a/crypto/internal/sysrand/rand_linux_test.go b/crypto/internal/sysrand/rand_linux_test.go new file mode 100644 index 00000000000..8b006fdfd2f --- /dev/null +++ b/crypto/internal/sysrand/rand_linux_test.go @@ -0,0 +1,69 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build cgo + +package sysrand_test + +import ( + "bytes" + "internal/syscall/unix" + "internal/testenv" + "os" + "runtime" + "syscall" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/sysrand/internal/seccomp" +) + +func TestNoGetrandom(t *testing.T) { + if os.Getenv("GO_GETRANDOM_DISABLED") == "1" { + // We are running under seccomp, the rest of the test suite will take + // care of actually testing the implementation, we check that getrandom + // is actually disabled. + _, err := unix.GetRandom(make([]byte, 16), 0) + if err != syscall.ENOSYS { + t.Errorf("GetRandom returned %v, want ENOSYS", err) + } else { + t.Log("GetRandom returned ENOSYS as expected") + } + return + } + + if testing.Short() { + t.Skip("skipping test in short mode") + } + testenv.MustHaveExec(t) + + done := make(chan struct{}) + go func() { + defer close(done) + // Call LockOSThread in a new goroutine, where we will apply the seccomp + // filter. We exit without unlocking the thread, so the thread will die + // and won't be reused. + runtime.LockOSThread() + + if err := seccomp.DisableGetrandom(); err != nil { + t.Errorf("failed to disable getrandom: %v", err) + return + } + + cmd := testenv.Command(t, os.Args[0], "-test.v") + cmd.Env = append(os.Environ(), "GO_GETRANDOM_DISABLED=1") + out, err := cmd.CombinedOutput() + if err != nil { + t.Errorf("subprocess failed: %v\n%s", err, out) + return + } + + if !bytes.Contains(out, []byte("GetRandom returned ENOSYS")) { + t.Errorf("subprocess did not disable getrandom") + } + if !bytes.Contains(out, []byte("TestRead")) { + t.Errorf("subprocess did not run TestRead") + } + }() + <-done +} diff --git a/crypto/internal/sysrand/rand_netbsd.go b/crypto/internal/sysrand/rand_netbsd.go new file mode 100644 index 00000000000..d203f1b4a47 --- /dev/null +++ b/crypto/internal/sysrand/rand_netbsd.go @@ -0,0 +1,24 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +import "internal/syscall/unix" + +func read(b []byte) error { + for len(b) > 0 { + size := len(b) + // "Returns independent uniformly distributed bytes at random each time, + // as many as requested up to 256, derived from the system entropy pool; + // see rnd(4)." -- man sysctl(7) + if size > 256 { + size = 256 + } + if err := unix.Arandom(b[:size]); err != nil { + return err + } + b = b[size:] + } + return nil +} diff --git a/crypto/internal/sysrand/rand_plan9.go b/crypto/internal/sysrand/rand_plan9.go new file mode 100644 index 00000000000..b759383eed9 --- /dev/null +++ b/crypto/internal/sysrand/rand_plan9.go @@ -0,0 +1,72 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +import ( + "internal/byteorder" + "internal/chacha8rand" + "io" + "os" + "sync" +) + +const randomDevice = "/dev/random" + +// This is a pseudorandom generator that seeds itself by reading from +// /dev/random. The read function always returns the full amount asked for, or +// else it returns an error. + +var ( + mu sync.Mutex + seeded sync.Once + seedErr error + state chacha8rand.State +) + +func read(b []byte) error { + seeded.Do(func() { + entropy, err := os.Open(randomDevice) + if err != nil { + seedErr = err + return + } + defer entropy.Close() + var seed [32]byte + _, err = io.ReadFull(entropy, seed[:]) + if err != nil { + seedErr = err + return + } + state.Init(seed) + }) + if seedErr != nil { + return seedErr + } + + mu.Lock() + defer mu.Unlock() + + for len(b) >= 8 { + if x, ok := state.Next(); ok { + byteorder.BEPutUint64(b, x) + b = b[8:] + } else { + state.Refill() + } + } + for len(b) > 0 { + if x, ok := state.Next(); ok { + var buf [8]byte + byteorder.BEPutUint64(buf[:], x) + n := copy(b, buf[:]) + b = b[n:] + } else { + state.Refill() + } + } + state.Reseed() + + return nil +} diff --git a/crypto/internal/sysrand/rand_test.go b/crypto/internal/sysrand/rand_test.go new file mode 100644 index 00000000000..2b9620c2fba --- /dev/null +++ b/crypto/internal/sysrand/rand_test.go @@ -0,0 +1,118 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +import ( + "bytes" + "compress/flate" + "internal/testenv" + "os" + "runtime" + "sync" + "testing" +) + +func TestRead(t *testing.T) { + // 40MiB, more than the documented maximum of 32Mi-1 on Linux 32-bit. + b := make([]byte, 40<<20) + Read(b) + + if testing.Short() { + b = b[len(b)-100_000:] + } + + var z bytes.Buffer + f, _ := flate.NewWriter(&z, 5) + f.Write(b) + f.Close() + if z.Len() < len(b)*99/100 { + t.Fatalf("Compressed %d -> %d", len(b), z.Len()) + } +} + +func TestReadByteValues(t *testing.T) { + b := make([]byte, 1) + v := make(map[byte]bool) + for { + Read(b) + v[b[0]] = true + if len(v) == 256 { + break + } + } +} + +func TestReadEmpty(t *testing.T) { + Read(make([]byte, 0)) + Read(nil) +} + +func TestConcurrentRead(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + const N = 100 + const M = 1000 + var wg sync.WaitGroup + wg.Add(N) + for i := 0; i < N; i++ { + go func() { + defer wg.Done() + for i := 0; i < M; i++ { + b := make([]byte, 32) + Read(b) + } + }() + } + wg.Wait() +} + +// TestNoUrandomFallback ensures the urandom fallback is not reached in +// normal operations. +func TestNoUrandomFallback(t *testing.T) { + expectFallback := false + if runtime.GOOS == "aix" { + // AIX always uses the urandom fallback. + expectFallback = true + } + if os.Getenv("GO_GETRANDOM_DISABLED") == "1" { + // We are testing the urandom fallback intentionally. + expectFallback = true + } + Read(make([]byte, 1)) + if urandomFile != nil && !expectFallback { + t.Error("/dev/urandom fallback used unexpectedly") + t.Log("note: if this test fails, it may be because the system does not have getrandom(2)") + } + if urandomFile == nil && expectFallback { + t.Error("/dev/urandom fallback not used as expected") + } +} + +func TestReadError(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + testenv.MustHaveExec(t) + + // We run this test in a subprocess because it's expected to crash. + if os.Getenv("GO_TEST_READ_ERROR") == "1" { + testingOnlyFailRead = true + Read(make([]byte, 32)) + t.Error("Read did not crash") + return + } + + cmd := testenv.Command(t, os.Args[0], "-test.run=TestReadError") + cmd.Env = append(os.Environ(), "GO_TEST_READ_ERROR=1") + out, err := cmd.CombinedOutput() + if err == nil { + t.Error("subprocess succeeded unexpectedly") + } + exp := "fatal error: crypto/rand: failed to read random data" + if !bytes.Contains(out, []byte(exp)) { + t.Errorf("subprocess output does not contain %q: %s", exp, out) + } +} diff --git a/crypto/internal/sysrand/rand_wasip1.go b/crypto/internal/sysrand/rand_wasip1.go new file mode 100644 index 00000000000..524df6d2595 --- /dev/null +++ b/crypto/internal/sysrand/rand_wasip1.go @@ -0,0 +1,15 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +import "syscall" + +func read(b []byte) error { + // This uses the wasi_snapshot_preview1 random_get syscall defined in + // https://github.com/WebAssembly/WASI/blob/23a52736049f4327dd335434851d5dc40ab7cad1/legacy/preview1/docs.md#-random_getbuf-pointeru8-buf_len-size---result-errno. + // The definition does not explicitly guarantee that the entire buffer will + // be filled, but this appears to be the case in all runtimes tested. + return syscall.RandomGet(b) +} diff --git a/crypto/internal/sysrand/rand_windows.go b/crypto/internal/sysrand/rand_windows.go new file mode 100644 index 00000000000..91f1490c15c --- /dev/null +++ b/crypto/internal/sysrand/rand_windows.go @@ -0,0 +1,11 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sysrand + +import "internal/syscall/windows" + +func read(b []byte) error { + return windows.ProcessPrng(b) +} diff --git a/crypto/md5/gen.go b/crypto/md5/gen.go index 438b6e9bafd..f4f76cb3bc2 100644 --- a/crypto/md5/gen.go +++ b/crypto/md5/gen.go @@ -219,7 +219,7 @@ func blockGeneric(dig *digest, p []byte) { // load input block {{range $i := seq 16 -}} - {{printf "x%x := byteorder.LeUint32(q[4*%#x:])" $i $i}} + {{printf "x%x := byteorder.LEUint32(q[4*%#x:])" $i $i}} {{end}} // round 1 diff --git a/crypto/md5/md5.go b/crypto/md5/md5.go index dece44d68e1..fd47a86aa49 100644 --- a/crypto/md5/md5.go +++ b/crypto/md5/md5.go @@ -11,11 +11,12 @@ package md5 import ( + "crypto" "errors" "hash" + "internal/byteorder" - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" ) func init() { @@ -63,13 +64,13 @@ func (d *digest) MarshalBinary() ([]byte, error) { func (d *digest) AppendBinary(b []byte) ([]byte, error) { b = append(b, magic...) - b = byteorder.BeAppendUint32(b, d.s[0]) - b = byteorder.BeAppendUint32(b, d.s[1]) - b = byteorder.BeAppendUint32(b, d.s[2]) - b = byteorder.BeAppendUint32(b, d.s[3]) + b = byteorder.BEAppendUint32(b, d.s[0]) + b = byteorder.BEAppendUint32(b, d.s[1]) + b = byteorder.BEAppendUint32(b, d.s[2]) + b = byteorder.BEAppendUint32(b, d.s[3]) b = append(b, d.x[:d.nx]...) b = append(b, make([]byte, len(d.x)-d.nx)...) - b = byteorder.BeAppendUint64(b, d.len) + b = byteorder.BEAppendUint64(b, d.len) return b, nil } @@ -92,15 +93,15 @@ func (d *digest) UnmarshalBinary(b []byte) error { } func consumeUint64(b []byte) ([]byte, uint64) { - return b[8:], byteorder.BeUint64(b[0:8]) + return b[8:], byteorder.BEUint64(b[0:8]) } func consumeUint32(b []byte) ([]byte, uint32) { - return b[4:], byteorder.BeUint32(b[0:4]) + return b[4:], byteorder.BEUint32(b[0:4]) } // New returns a new [hash.Hash] computing the MD5 checksum. The Hash -// also implements [encoding.BinaryMarshaler], [encoding.AppendBinary] and +// also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal // state of the hash. func New() hash.Hash { @@ -114,6 +115,9 @@ func (d *digest) Size() int { return Size } func (d *digest) BlockSize() int { return BlockSize } func (d *digest) Write(p []byte) (nn int, err error) { + if fips140only.Enabled { + return 0, errors.New("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode") + } // Note that we currently call block or blockGeneric // directly (guarded using haveAsm) because this allows // escape analysis to see that p and d don't escape. @@ -155,6 +159,10 @@ func (d *digest) Sum(in []byte) []byte { } func (d *digest) checkSum() [Size]byte { + if fips140only.Enabled { + panic("crypto/md5: use of MD5 is not allowed in FIPS 140-only mode") + } + // Append 0x80 to the end of the message and then append zeros // until the length is a multiple of 56 bytes. Finally append // 8 bytes representing the message length in bits. @@ -162,7 +170,7 @@ func (d *digest) checkSum() [Size]byte { // 1 byte end marker :: 0-63 padding bytes :: 8 byte length tmp := [1 + 63 + 8]byte{0x80} pad := (55 - d.len) % 64 // calculate number of padding bytes - byteorder.LePutUint64(tmp[1+pad:], d.len<<3) // append length in bits + byteorder.LEPutUint64(tmp[1+pad:], d.len<<3) // append length in bits d.Write(tmp[:1+pad+8]) // The previous write ensures that a whole number of @@ -172,10 +180,10 @@ func (d *digest) checkSum() [Size]byte { } var digest [Size]byte - byteorder.LePutUint32(digest[0:], d.s[0]) - byteorder.LePutUint32(digest[4:], d.s[1]) - byteorder.LePutUint32(digest[8:], d.s[2]) - byteorder.LePutUint32(digest[12:], d.s[3]) + byteorder.LEPutUint32(digest[0:], d.s[0]) + byteorder.LEPutUint32(digest[4:], d.s[1]) + byteorder.LEPutUint32(digest[8:], d.s[2]) + byteorder.LEPutUint32(digest[12:], d.s[3]) return digest } diff --git a/crypto/md5/md5_test.go b/crypto/md5/md5_test.go index c2dcc3a0eab..fbd49b93653 100644 --- a/crypto/md5/md5_test.go +++ b/crypto/md5/md5_test.go @@ -227,6 +227,7 @@ func TestLargeHashes(t *testing.T) { } func TestAllocations(t *testing.T) { + cryptotest.SkipTestAllocations(t) in := []byte("hello, world!") out := make([]byte, 0, Size) h := New() diff --git a/crypto/md5/md5block.go b/crypto/md5/md5block.go index 274b430b2af..1b064be6af8 100644 --- a/crypto/md5/md5block.go +++ b/crypto/md5/md5block.go @@ -24,22 +24,22 @@ func blockGeneric(dig *digest, p []byte) { aa, bb, cc, dd := a, b, c, d // load input block - x0 := byteorder.LeUint32(q[4*0x0:]) - x1 := byteorder.LeUint32(q[4*0x1:]) - x2 := byteorder.LeUint32(q[4*0x2:]) - x3 := byteorder.LeUint32(q[4*0x3:]) - x4 := byteorder.LeUint32(q[4*0x4:]) - x5 := byteorder.LeUint32(q[4*0x5:]) - x6 := byteorder.LeUint32(q[4*0x6:]) - x7 := byteorder.LeUint32(q[4*0x7:]) - x8 := byteorder.LeUint32(q[4*0x8:]) - x9 := byteorder.LeUint32(q[4*0x9:]) - xa := byteorder.LeUint32(q[4*0xa:]) - xb := byteorder.LeUint32(q[4*0xb:]) - xc := byteorder.LeUint32(q[4*0xc:]) - xd := byteorder.LeUint32(q[4*0xd:]) - xe := byteorder.LeUint32(q[4*0xe:]) - xf := byteorder.LeUint32(q[4*0xf:]) + x0 := byteorder.LEUint32(q[4*0x0:]) + x1 := byteorder.LEUint32(q[4*0x1:]) + x2 := byteorder.LEUint32(q[4*0x2:]) + x3 := byteorder.LEUint32(q[4*0x3:]) + x4 := byteorder.LEUint32(q[4*0x4:]) + x5 := byteorder.LEUint32(q[4*0x5:]) + x6 := byteorder.LEUint32(q[4*0x6:]) + x7 := byteorder.LEUint32(q[4*0x7:]) + x8 := byteorder.LEUint32(q[4*0x8:]) + x9 := byteorder.LEUint32(q[4*0x9:]) + xa := byteorder.LEUint32(q[4*0xa:]) + xb := byteorder.LEUint32(q[4*0xb:]) + xc := byteorder.LEUint32(q[4*0xc:]) + xd := byteorder.LEUint32(q[4*0xd:]) + xe := byteorder.LEUint32(q[4*0xe:]) + xf := byteorder.LEUint32(q[4*0xf:]) // round 1 a = b + bits.RotateLeft32((((c^d)&b)^d)+a+x0+0xd76aa478, 7) diff --git a/crypto/mlkem/example_test.go b/crypto/mlkem/example_test.go new file mode 100644 index 00000000000..a8451696c13 --- /dev/null +++ b/crypto/mlkem/example_test.go @@ -0,0 +1,48 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mlkem_test + +import ( + "log" + + "github.com/runZeroInc/excrypto/crypto/mlkem" +) + +func Example() { + // Alice generates a new key pair and sends the encapsulation key to Bob. + dk, err := mlkem.GenerateKey768() + if err != nil { + log.Fatal(err) + } + encapsulationKey := dk.EncapsulationKey().Bytes() + + // Bob uses the encapsulation key to encapsulate a shared secret, and sends + // back the ciphertext to Alice. + ciphertext := Bob(encapsulationKey) + + // Alice decapsulates the shared secret from the ciphertext. + sharedSecret, err := dk.Decapsulate(ciphertext) + if err != nil { + log.Fatal(err) + } + + // Alice and Bob now share a secret. + _ = sharedSecret +} + +func Bob(encapsulationKey []byte) (ciphertext []byte) { + // Bob encapsulates a shared secret using the encapsulation key. + ek, err := mlkem.NewEncapsulationKey768(encapsulationKey) + if err != nil { + log.Fatal(err) + } + sharedSecret, ciphertext := ek.Encapsulate() + + // Alice and Bob now share a secret. + _ = sharedSecret + + // Bob sends the ciphertext to Alice. + return ciphertext +} diff --git a/crypto/mlkem/mlkem.go b/crypto/mlkem/mlkem.go new file mode 100644 index 00000000000..fc24801e238 --- /dev/null +++ b/crypto/mlkem/mlkem.go @@ -0,0 +1,192 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package mlkem implements the quantum-resistant key encapsulation method +// ML-KEM (formerly known as Kyber), as specified in [NIST FIPS 203]. +// +// Most applications should use the ML-KEM-768 parameter set, as implemented by +// [DecapsulationKey768] and [EncapsulationKey768]. +// +// [NIST FIPS 203]: https://doi.org/10.6028/NIST.FIPS.203 +package mlkem + +import "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + +const ( + // SharedKeySize is the size of a shared key produced by ML-KEM. + SharedKeySize = 32 + + // SeedSize is the size of a seed used to generate a decapsulation key. + SeedSize = 64 + + // CiphertextSize768 is the size of a ciphertext produced by ML-KEM-768. + CiphertextSize768 = 1088 + + // EncapsulationKeySize768 is the size of an ML-KEM-768 encapsulation key. + EncapsulationKeySize768 = 1184 + + // CiphertextSize1024 is the size of a ciphertext produced by ML-KEM-1024. + CiphertextSize1024 = 1568 + + // EncapsulationKeySize1024 is the size of an ML-KEM-1024 encapsulation key. + EncapsulationKeySize1024 = 1568 +) + +// DecapsulationKey768 is the secret key used to decapsulate a shared key +// from a ciphertext. It includes various precomputed values. +type DecapsulationKey768 struct { + key *mlkem.DecapsulationKey768 +} + +// GenerateKey768 generates a new decapsulation key, drawing random bytes from +// the default crypto/rand source. The decapsulation key must be kept secret. +func GenerateKey768() (*DecapsulationKey768, error) { + key, err := mlkem.GenerateKey768() + if err != nil { + return nil, err + } + + return &DecapsulationKey768{key}, nil +} + +// NewDecapsulationKey768 expands a decapsulation key from a 64-byte seed in the +// "d || z" form. The seed must be uniformly random. +func NewDecapsulationKey768(seed []byte) (*DecapsulationKey768, error) { + key, err := mlkem.NewDecapsulationKey768(seed) + if err != nil { + return nil, err + } + + return &DecapsulationKey768{key}, nil +} + +// Bytes returns the decapsulation key as a 64-byte seed in the "d || z" form. +// +// The decapsulation key must be kept secret. +func (dk *DecapsulationKey768) Bytes() []byte { + return dk.key.Bytes() +} + +// Decapsulate generates a shared key from a ciphertext and a decapsulation +// key. If the ciphertext is not valid, Decapsulate returns an error. +// +// The shared key must be kept secret. +func (dk *DecapsulationKey768) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) { + return dk.key.Decapsulate(ciphertext) +} + +// EncapsulationKey returns the public encapsulation key necessary to produce +// ciphertexts. +func (dk *DecapsulationKey768) EncapsulationKey() *EncapsulationKey768 { + return &EncapsulationKey768{dk.key.EncapsulationKey()} +} + +// An EncapsulationKey768 is the public key used to produce ciphertexts to be +// decapsulated by the corresponding DecapsulationKey768. +type EncapsulationKey768 struct { + key *mlkem.EncapsulationKey768 +} + +// NewEncapsulationKey768 parses an encapsulation key from its encoded form. If +// the encapsulation key is not valid, NewEncapsulationKey768 returns an error. +func NewEncapsulationKey768(encapsulationKey []byte) (*EncapsulationKey768, error) { + key, err := mlkem.NewEncapsulationKey768(encapsulationKey) + if err != nil { + return nil, err + } + + return &EncapsulationKey768{key}, nil +} + +// Bytes returns the encapsulation key as a byte slice. +func (ek *EncapsulationKey768) Bytes() []byte { + return ek.key.Bytes() +} + +// Encapsulate generates a shared key and an associated ciphertext from an +// encapsulation key, drawing random bytes from the default crypto/rand source. +// +// The shared key must be kept secret. +func (ek *EncapsulationKey768) Encapsulate() (sharedKey, ciphertext []byte) { + return ek.key.Encapsulate() +} + +// DecapsulationKey1024 is the secret key used to decapsulate a shared key +// from a ciphertext. It includes various precomputed values. +type DecapsulationKey1024 struct { + key *mlkem.DecapsulationKey1024 +} + +// GenerateKey1024 generates a new decapsulation key, drawing random bytes from +// the default crypto/rand source. The decapsulation key must be kept secret. +func GenerateKey1024() (*DecapsulationKey1024, error) { + key, err := mlkem.GenerateKey1024() + if err != nil { + return nil, err + } + + return &DecapsulationKey1024{key}, nil +} + +// NewDecapsulationKey1024 expands a decapsulation key from a 64-byte seed in the +// "d || z" form. The seed must be uniformly random. +func NewDecapsulationKey1024(seed []byte) (*DecapsulationKey1024, error) { + key, err := mlkem.NewDecapsulationKey1024(seed) + if err != nil { + return nil, err + } + + return &DecapsulationKey1024{key}, nil +} + +// Bytes returns the decapsulation key as a 64-byte seed in the "d || z" form. +// +// The decapsulation key must be kept secret. +func (dk *DecapsulationKey1024) Bytes() []byte { + return dk.key.Bytes() +} + +// Decapsulate generates a shared key from a ciphertext and a decapsulation +// key. If the ciphertext is not valid, Decapsulate returns an error. +// +// The shared key must be kept secret. +func (dk *DecapsulationKey1024) Decapsulate(ciphertext []byte) (sharedKey []byte, err error) { + return dk.key.Decapsulate(ciphertext) +} + +// EncapsulationKey returns the public encapsulation key necessary to produce +// ciphertexts. +func (dk *DecapsulationKey1024) EncapsulationKey() *EncapsulationKey1024 { + return &EncapsulationKey1024{dk.key.EncapsulationKey()} +} + +// An EncapsulationKey1024 is the public key used to produce ciphertexts to be +// decapsulated by the corresponding DecapsulationKey1024. +type EncapsulationKey1024 struct { + key *mlkem.EncapsulationKey1024 +} + +// NewEncapsulationKey1024 parses an encapsulation key from its encoded form. If +// the encapsulation key is not valid, NewEncapsulationKey1024 returns an error. +func NewEncapsulationKey1024(encapsulationKey []byte) (*EncapsulationKey1024, error) { + key, err := mlkem.NewEncapsulationKey1024(encapsulationKey) + if err != nil { + return nil, err + } + + return &EncapsulationKey1024{key}, nil +} + +// Bytes returns the encapsulation key as a byte slice. +func (ek *EncapsulationKey1024) Bytes() []byte { + return ek.key.Bytes() +} + +// Encapsulate generates a shared key and an associated ciphertext from an +// encapsulation key, drawing random bytes from the default crypto/rand source. +// +// The shared key must be kept secret. +func (ek *EncapsulationKey1024) Encapsulate() (sharedKey, ciphertext []byte) { + return ek.key.Encapsulate() +} diff --git a/crypto/mlkem/mlkem_test.go b/crypto/mlkem/mlkem_test.go new file mode 100644 index 00000000000..dad8e5b8b5f --- /dev/null +++ b/crypto/mlkem/mlkem_test.go @@ -0,0 +1,335 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package mlkem + +import ( + "bytes" + "encoding/hex" + "flag" + "testing" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" +) + +type encapsulationKey interface { + Bytes() []byte + Encapsulate() ([]byte, []byte) +} + +type decapsulationKey[E encapsulationKey] interface { + Bytes() []byte + Decapsulate([]byte) ([]byte, error) + EncapsulationKey() E +} + +func TestRoundTrip(t *testing.T) { + t.Run("768", func(t *testing.T) { + testRoundTrip(t, GenerateKey768, NewEncapsulationKey768, NewDecapsulationKey768) + }) + t.Run("1024", func(t *testing.T) { + testRoundTrip(t, GenerateKey1024, NewEncapsulationKey1024, NewDecapsulationKey1024) + }) +} + +func testRoundTrip[E encapsulationKey, D decapsulationKey[E]]( + t *testing.T, generateKey func() (D, error), + newEncapsulationKey func([]byte) (E, error), + newDecapsulationKey func([]byte) (D, error)) { + dk, err := generateKey() + if err != nil { + t.Fatal(err) + } + ek := dk.EncapsulationKey() + Ke, c := ek.Encapsulate() + Kd, err := dk.Decapsulate(c) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(Ke, Kd) { + t.Fail() + } + + ek1, err := newEncapsulationKey(ek.Bytes()) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(ek.Bytes(), ek1.Bytes()) { + t.Fail() + } + dk1, err := newDecapsulationKey(dk.Bytes()) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(dk.Bytes(), dk1.Bytes()) { + t.Fail() + } + Ke1, c1 := ek1.Encapsulate() + Kd1, err := dk1.Decapsulate(c1) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(Ke1, Kd1) { + t.Fail() + } + + dk2, err := generateKey() + if err != nil { + t.Fatal(err) + } + if bytes.Equal(dk.EncapsulationKey().Bytes(), dk2.EncapsulationKey().Bytes()) { + t.Fail() + } + if bytes.Equal(dk.Bytes(), dk2.Bytes()) { + t.Fail() + } + + Ke2, c2 := dk.EncapsulationKey().Encapsulate() + if bytes.Equal(c, c2) { + t.Fail() + } + if bytes.Equal(Ke, Ke2) { + t.Fail() + } +} + +func TestBadLengths(t *testing.T) { + t.Run("768", func(t *testing.T) { + testBadLengths(t, GenerateKey768, NewEncapsulationKey768, NewDecapsulationKey768) + }) + t.Run("1024", func(t *testing.T) { + testBadLengths(t, GenerateKey1024, NewEncapsulationKey1024, NewDecapsulationKey1024) + }) +} + +func testBadLengths[E encapsulationKey, D decapsulationKey[E]]( + t *testing.T, generateKey func() (D, error), + newEncapsulationKey func([]byte) (E, error), + newDecapsulationKey func([]byte) (D, error)) { + dk, err := generateKey() + dkBytes := dk.Bytes() + if err != nil { + t.Fatal(err) + } + ek := dk.EncapsulationKey() + ekBytes := dk.EncapsulationKey().Bytes() + _, c := ek.Encapsulate() + + for i := 0; i < len(dkBytes)-1; i++ { + if _, err := newDecapsulationKey(dkBytes[:i]); err == nil { + t.Errorf("expected error for dk length %d", i) + } + } + dkLong := dkBytes + for i := 0; i < 100; i++ { + dkLong = append(dkLong, 0) + if _, err := newDecapsulationKey(dkLong); err == nil { + t.Errorf("expected error for dk length %d", len(dkLong)) + } + } + + for i := 0; i < len(ekBytes)-1; i++ { + if _, err := newEncapsulationKey(ekBytes[:i]); err == nil { + t.Errorf("expected error for ek length %d", i) + } + } + ekLong := ekBytes + for i := 0; i < 100; i++ { + ekLong = append(ekLong, 0) + if _, err := newEncapsulationKey(ekLong); err == nil { + t.Errorf("expected error for ek length %d", len(ekLong)) + } + } + + for i := 0; i < len(c)-1; i++ { + if _, err := dk.Decapsulate(c[:i]); err == nil { + t.Errorf("expected error for c length %d", i) + } + } + cLong := c + for i := 0; i < 100; i++ { + cLong = append(cLong, 0) + if _, err := dk.Decapsulate(cLong); err == nil { + t.Errorf("expected error for c length %d", len(cLong)) + } + } +} + +var millionFlag = flag.Bool("million", false, "run the million vector test") + +// TestAccumulated accumulates 10k (or 100, or 1M) random vectors and checks the +// hash of the result, to avoid checking in 150MB of test vectors. +func TestAccumulated(t *testing.T) { + n := 10000 + expected := "8a518cc63da366322a8e7a818c7a0d63483cb3528d34a4cf42f35d5ad73f22fc" + if testing.Short() { + n = 100 + expected = "1114b1b6699ed191734fa339376afa7e285c9e6acf6ff0177d346696ce564415" + } + if *millionFlag { + n = 1000000 + expected = "424bf8f0e8ae99b78d788a6e2e8e9cdaf9773fc0c08a6f433507cb559edfd0f0" + } + + s := sha3.NewShake128() + o := sha3.NewShake128() + seed := make([]byte, SeedSize) + var msg [32]byte + ct1 := make([]byte, CiphertextSize768) + + for i := 0; i < n; i++ { + s.Read(seed) + dk, err := NewDecapsulationKey768(seed) + if err != nil { + t.Fatal(err) + } + ek := dk.EncapsulationKey() + o.Write(ek.Bytes()) + + s.Read(msg[:]) + k, ct := ek.key.EncapsulateInternal(&msg) + o.Write(ct) + o.Write(k) + + kk, err := dk.Decapsulate(ct) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(kk, k) { + t.Errorf("k: got %x, expected %x", kk, k) + } + + s.Read(ct1) + k1, err := dk.Decapsulate(ct1) + if err != nil { + t.Fatal(err) + } + o.Write(k1) + } + + got := hex.EncodeToString(o.Sum(nil)) + if got != expected { + t.Errorf("got %s, expected %s", got, expected) + } +} + +var sink byte + +func BenchmarkKeyGen(b *testing.B) { + var d, z [32]byte + rand.Read(d[:]) + rand.Read(z[:]) + b.ResetTimer() + for i := 0; i < b.N; i++ { + dk := mlkem.GenerateKeyInternal768(&d, &z) + sink ^= dk.EncapsulationKey().Bytes()[0] + } +} + +func BenchmarkEncaps(b *testing.B) { + seed := make([]byte, SeedSize) + rand.Read(seed) + var m [32]byte + rand.Read(m[:]) + dk, err := NewDecapsulationKey768(seed) + if err != nil { + b.Fatal(err) + } + ekBytes := dk.EncapsulationKey().Bytes() + b.ResetTimer() + for i := 0; i < b.N; i++ { + ek, err := NewEncapsulationKey768(ekBytes) + if err != nil { + b.Fatal(err) + } + K, c := ek.key.EncapsulateInternal(&m) + sink ^= c[0] ^ K[0] + } +} + +func BenchmarkDecaps(b *testing.B) { + dk, err := GenerateKey768() + if err != nil { + b.Fatal(err) + } + ek := dk.EncapsulationKey() + _, c := ek.Encapsulate() + b.ResetTimer() + for i := 0; i < b.N; i++ { + K, _ := dk.Decapsulate(c) + sink ^= K[0] + } +} + +func BenchmarkRoundTrip(b *testing.B) { + dk, err := GenerateKey768() + if err != nil { + b.Fatal(err) + } + ek := dk.EncapsulationKey() + ekBytes := ek.Bytes() + _, c := ek.Encapsulate() + if err != nil { + b.Fatal(err) + } + b.Run("Alice", func(b *testing.B) { + for i := 0; i < b.N; i++ { + dkS, err := GenerateKey768() + if err != nil { + b.Fatal(err) + } + ekS := dkS.EncapsulationKey().Bytes() + sink ^= ekS[0] + + Ks, err := dk.Decapsulate(c) + if err != nil { + b.Fatal(err) + } + sink ^= Ks[0] + } + }) + b.Run("Bob", func(b *testing.B) { + for i := 0; i < b.N; i++ { + ek, err := NewEncapsulationKey768(ekBytes) + if err != nil { + b.Fatal(err) + } + Ks, cS := ek.Encapsulate() + if err != nil { + b.Fatal(err) + } + sink ^= cS[0] ^ Ks[0] + } + }) +} + +// Test that the constants from the public API match the corresponding values from the internal API. +func TestConstantSizes(t *testing.T) { + if SharedKeySize != mlkem.SharedKeySize { + t.Errorf("SharedKeySize mismatch: got %d, want %d", SharedKeySize, mlkem.SharedKeySize) + } + + if SeedSize != mlkem.SeedSize { + t.Errorf("SeedSize mismatch: got %d, want %d", SeedSize, mlkem.SeedSize) + } + + if CiphertextSize768 != mlkem.CiphertextSize768 { + t.Errorf("CiphertextSize768 mismatch: got %d, want %d", CiphertextSize768, mlkem.CiphertextSize768) + } + + if EncapsulationKeySize768 != mlkem.EncapsulationKeySize768 { + t.Errorf("EncapsulationKeySize768 mismatch: got %d, want %d", EncapsulationKeySize768, mlkem.EncapsulationKeySize768) + } + + if CiphertextSize1024 != mlkem.CiphertextSize1024 { + t.Errorf("CiphertextSize1024 mismatch: got %d, want %d", CiphertextSize1024, mlkem.CiphertextSize1024) + } + + if EncapsulationKeySize1024 != mlkem.EncapsulationKeySize1024 { + t.Errorf("EncapsulationKeySize1024 mismatch: got %d, want %d", EncapsulationKeySize1024, mlkem.EncapsulationKeySize1024) + } +} diff --git a/crypto/pbkdf2/pbkdf2.go b/crypto/pbkdf2/pbkdf2.go new file mode 100644 index 00000000000..7433e1b734e --- /dev/null +++ b/crypto/pbkdf2/pbkdf2.go @@ -0,0 +1,55 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package pbkdf2 implements the key derivation function PBKDF2 as defined in +// RFC 8018 (PKCS #5 v2.1). +// +// A key derivation function is useful when encrypting data based on a password +// or any other not-fully-random data. It uses a pseudorandom function to derive +// a secure encryption key based on the password. +package pbkdf2 + +import ( + "errors" + "hash" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/pbkdf2" + "github.com/runZeroInc/excrypto/crypto/internal/fips140hash" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" +) + +// Key derives a key from the password, salt and iteration count, returning a +// []byte of length keyLength that can be used as cryptographic key. The key is +// derived based on the method described as PBKDF2 with the HMAC variant using +// the supplied hash function. +// +// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you +// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by +// doing: +// +// dk := pbkdf2.Key(sha1.New, []byte("some password"), salt, 4096, 32) +// +// Remember to get a good random salt. At least 8 bytes is recommended by the +// RFC. +// +// Using a higher iteration count will increase the cost of an exhaustive +// search but will also make derivation proportionally slower. +// +// keyLength must be a positive integer between 1 and (2^32 - 1) * h.Size(). +// Setting keyLength to a value outside of this range will result in an error. +func Key[Hash hash.Hash](h func() Hash, password string, salt []byte, iter, keyLength int) ([]byte, error) { + fh := fips140hash.UnwrapNew(h) + if fips140only.Enabled { + if keyLength < 112/8 { + return nil, errors.New("crypto/pbkdf2: use of keys shorter than 112 bits is not allowed in FIPS 140-only mode") + } + if len(salt) < 128/8 { + return nil, errors.New("crypto/pbkdf2: use of salts shorter than 128 bits is not allowed in FIPS 140-only mode") + } + if !fips140only.ApprovedHash(fh()) { + return nil, errors.New("crypto/pbkdf2: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + } + return pbkdf2.Key(fh, password, salt, iter, keyLength) +} diff --git a/crypto/pbkdf2/pbkdf2_test.go b/crypto/pbkdf2/pbkdf2_test.go new file mode 100644 index 00000000000..78bea2b81b7 --- /dev/null +++ b/crypto/pbkdf2/pbkdf2_test.go @@ -0,0 +1,254 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pbkdf2_test + +import ( + "bytes" + "hash" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" + "github.com/runZeroInc/excrypto/crypto/pbkdf2" + "github.com/runZeroInc/excrypto/crypto/sha1" + "github.com/runZeroInc/excrypto/crypto/sha256" +) + +type testVector struct { + password string + salt string + iter int + output []byte +} + +// Test vectors from RFC 6070, http://tools.ietf.org/html/rfc6070 +var sha1TestVectors = []testVector{ + { + "password", + "salt", + 1, + []byte{ + 0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71, + 0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06, + 0x2f, 0xe0, 0x37, 0xa6, + }, + }, + { + "password", + "salt", + 2, + []byte{ + 0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c, + 0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0, + 0xd8, 0xde, 0x89, 0x57, + }, + }, + { + "password", + "salt", + 4096, + []byte{ + 0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a, + 0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0, + 0x65, 0xa4, 0x29, 0xc1, + }, + }, + // // This one takes too long + // { + // "password", + // "salt", + // 16777216, + // []byte{ + // 0xee, 0xfe, 0x3d, 0x61, 0xcd, 0x4d, 0xa4, 0xe4, + // 0xe9, 0x94, 0x5b, 0x3d, 0x6b, 0xa2, 0x15, 0x8c, + // 0x26, 0x34, 0xe9, 0x84, + // }, + // }, + { + "passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + []byte{ + 0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b, + 0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a, + 0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70, + 0x38, + }, + }, + { + "pass\000word", + "sa\000lt", + 4096, + []byte{ + 0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d, + 0xcc, 0x37, 0xd7, 0xf0, 0x34, 0x25, 0xe0, 0xc3, + }, + }, +} + +// Test vectors from +// http://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors +var sha256TestVectors = []testVector{ + { + "password", + "salt", + 1, + []byte{ + 0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c, + 0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37, + 0xa8, 0x65, 0x48, 0xc9, + }, + }, + { + "password", + "salt", + 2, + []byte{ + 0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3, + 0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0, + 0x2a, 0x30, 0x3f, 0x8e, + }, + }, + { + "password", + "salt", + 4096, + []byte{ + 0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41, + 0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d, + 0x96, 0x28, 0x93, 0xa0, + }, + }, + { + "passwordPASSWORDpassword", + "saltSALTsaltSALTsaltSALTsaltSALTsalt", + 4096, + []byte{ + 0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f, + 0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf, + 0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18, + 0x1c, + }, + }, + { + "pass\000word", + "sa\000lt", + 4096, + []byte{ + 0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89, + 0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87, + }, + }, +} + +func testHash(t *testing.T, h func() hash.Hash, hashName string, vectors []testVector) { + for i, v := range vectors { + o, err := pbkdf2.Key(h, v.password, []byte(v.salt), v.iter, len(v.output)) + if err != nil { + t.Error(err) + } + if !bytes.Equal(o, v.output) { + t.Errorf("%s %d: expected %x, got %x", hashName, i, v.output, o) + } + } +} + +func TestWithHMACSHA1(t *testing.T) { + testHash(t, sha1.New, "SHA1", sha1TestVectors) +} + +func TestWithHMACSHA256(t *testing.T) { + testHash(t, sha256.New, "SHA256", sha256TestVectors) +} + +var sink uint8 + +func benchmark(b *testing.B, h func() hash.Hash) { + var err error + password := make([]byte, h().Size()) + salt := make([]byte, 8) + for i := 0; i < b.N; i++ { + password, err = pbkdf2.Key(h, string(password), salt, 4096, len(password)) + if err != nil { + b.Error(err) + } + } + sink += password[0] +} + +func BenchmarkHMACSHA1(b *testing.B) { + benchmark(b, sha1.New) +} + +func BenchmarkHMACSHA256(b *testing.B) { + benchmark(b, sha256.New) +} + +func TestPBKDF2ServiceIndicator(t *testing.T) { + if boring.Enabled { + t.Skip("in BoringCrypto mode PBKDF2 is not from the Go FIPS module") + } + + goodSalt := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10} + + fips140.ResetServiceIndicator() + _, err := pbkdf2.Key(sha256.New, "password", goodSalt, 1, 32) + if err != nil { + t.Error(err) + } + if !fips140.ServiceIndicator() { + t.Error("FIPS service indicator should be set") + } + + // Salt too short + fips140.ResetServiceIndicator() + _, err = pbkdf2.Key(sha256.New, "password", goodSalt[:8], 1, 32) + if err != nil { + t.Error(err) + } + if fips140.ServiceIndicator() { + t.Error("FIPS service indicator should not be set") + } + + // Key length too short + fips140.ResetServiceIndicator() + _, err = pbkdf2.Key(sha256.New, "password", goodSalt, 1, 10) + if err != nil { + t.Error(err) + } + if fips140.ServiceIndicator() { + t.Error("FIPS service indicator should not be set") + } +} + +func TestMaxKeyLength(t *testing.T) { + // This error cannot be triggered on platforms where int is 31 bits (i.e. + // 32-bit platforms), since the max value for keyLength is 1<<31-1 and + // 1<<31-1 * hLen will always be less than 1<<32-1 * hLen. + keySize := int64(1<<63 - 1) + if int64(int(keySize)) != keySize { + t.Skip("cannot be replicated on platforms where int is 31 bits") + } + _, err := pbkdf2.Key(sha256.New, "password", []byte("salt"), 1, int(keySize)) + if err == nil { + t.Fatal("expected pbkdf2.Key to fail with extremely large keyLength") + } + keySize = int64(1<<32-1) * (sha256.Size + 1) + _, err = pbkdf2.Key(sha256.New, "password", []byte("salt"), 1, int(keySize)) + if err == nil { + t.Fatal("expected pbkdf2.Key to fail with extremely large keyLength") + } +} + +func TestZeroKeyLength(t *testing.T) { + _, err := pbkdf2.Key(sha256.New, "password", []byte("salt"), 1, 0) + if err == nil { + t.Fatal("expected pbkdf2.Key to fail with zero keyLength") + } + _, err = pbkdf2.Key(sha256.New, "password", []byte("salt"), 1, -1) + if err == nil { + t.Fatal("expected pbkdf2.Key to fail with negative keyLength") + } +} diff --git a/crypto/rc4/rc4.go b/crypto/rc4/rc4.go index b5bb9c860b1..1402410304e 100644 --- a/crypto/rc4/rc4.go +++ b/crypto/rc4/rc4.go @@ -10,9 +10,11 @@ package rc4 import ( + "errors" "strconv" - "github.com/runZeroInc/excrypto/crypto/internal/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/alias" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" ) // A Cipher is an instance of RC4 using a particular key. @@ -30,6 +32,9 @@ func (k KeySizeError) Error() string { // NewCipher creates and returns a new [Cipher]. The key argument should be the // RC4 key, at least 1 byte and at most 256 bytes. func NewCipher(key []byte) (*Cipher, error) { + if fips140only.Enabled { + return nil, errors.New("crypto/rc4: use of RC4 is not allowed in FIPS 140-only mode") + } k := len(key) if k < 1 || k > 256 { return nil, KeySizeError(k) diff --git a/crypto/aes/_asm/gcm/go.sum b/crypto/rsa/boring.go similarity index 100% rename from crypto/aes/_asm/gcm/go.sum rename to crypto/rsa/boring.go diff --git a/crypto/rsa/boring_test.go b/crypto/rsa/boring_test.go new file mode 100644 index 00000000000..0a534ba8a84 --- /dev/null +++ b/crypto/rsa/boring_test.go @@ -0,0 +1,152 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build boringcrypto + +// Note: Can run these tests against the non-BoringCrypto +// version of the code by using "CGO_ENABLED=0 go test". + +package rsa + +import ( + "crypto" + "encoding/hex" + "math/big" + "runtime" + "runtime/debug" + "sync" + "testing" + + "github.com/runZeroInc/excrypto/encoding/asn1" + + "crypto/rand" +) + +func TestBoringASN1Marshal(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") + + k, err := GenerateKey(rand.Reader, 128) + if err != nil { + t.Fatal(err) + } + _, err = asn1.Marshal(k.PublicKey) + if err != nil { + t.Fatal(err) + } +} + +func TestBoringVerify(t *testing.T) { + // Check that signatures that lack leading zeroes don't verify. + key := &PublicKey{ + N: bigFromHex("c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be1"), + E: 65537, + } + + hash := fromHex("019c5571724fb5d0e47a4260c940e9803ba05a44") + paddedHash := fromHex("3021300906052b0e03021a05000414019c5571724fb5d0e47a4260c940e9803ba05a44") + + // signature is one byte shorter than key.N. + sig := fromHex("5edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56") + + err := VerifyPKCS1v15(key, 0, paddedHash, sig) + if err == nil { + t.Errorf("raw: expected verification error") + } + + err = VerifyPKCS1v15(key, crypto.SHA1, hash, sig) + if err == nil { + t.Errorf("sha1: expected verification error") + } +} + +func BenchmarkBoringVerify(b *testing.B) { + // Check that signatures that lack leading zeroes don't verify. + key := &PublicKey{ + N: bigFromHex("c4fdf7b40a5477f206e6ee278eaef888ca73bf9128a9eef9f2f1ddb8b7b71a4c07cfa241f028a04edb405e4d916c61d6beabc333813dc7b484d2b3c52ee233c6a79b1eea4e9cc51596ba9cd5ac5aeb9df62d86ea051055b79d03f8a4fa9f38386f5bd17529138f3325d46801514ea9047977e0829ed728e68636802796801be1"), + E: 65537, + } + + hash := fromHex("019c5571724fb5d0e47a4260c940e9803ba05a44") + + // signature is one byte shorter than key.N. + sig := fromHex("5edfbeb6a73e7225ad3cc52724e2872e04260d7daf0d693c170d8c4b243b8767bc7785763533febc62ec2600c30603c433c095453ede59ff2fcabeb84ce32e0ed9d5cf15ffcbc816202b64370d4d77c1e9077d74e94a16fb4fa2e5bec23a56d7a73cf275f91691ae1801a976fcde09e981a2f6327ac27ea1fecf3185df0d56") + + b.ReportAllocs() + + for i := 0; i < b.N; i++ { + err := VerifyPKCS1v15(key, crypto.SHA1, hash, sig) + if err == nil { + b.Fatalf("sha1: expected verification error") + } + } +} + +func TestBoringGenerateKey(t *testing.T) { + k, err := GenerateKey(rand.Reader, 2048) // 2048 is smallest size BoringCrypto might kick in for + if err != nil { + t.Fatal(err) + } + + // Non-Boring GenerateKey always sets CRTValues to a non-nil (possibly empty) slice. + if k.Precomputed.CRTValues == nil { + t.Fatalf("GenerateKey: Precomputed.CRTValues = nil") + } +} + +func TestBoringFinalizers(t *testing.T) { + if runtime.GOOS == "nacl" || runtime.GOOS == "js" { + // Times out on nacl and js/wasm (without BoringCrypto) + // but not clear why - probably consuming rand.Reader too quickly + // and being throttled. Also doesn't really matter. + t.Skipf("skipping on %s/%s", runtime.GOOS, runtime.GOARCH) + } + + k, err := GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + + // Run test with GOGC=10, to make bug more likely. + // Without the KeepAlives, the loop usually dies after + // about 30 iterations. + defer debug.SetGCPercent(debug.SetGCPercent(10)) + for n := 0; n < 200; n++ { + // Clear the underlying BoringCrypto object cache. + privCache.Clear() + + // Race to create the underlying BoringCrypto object. + // The ones that lose the race are prime candidates for + // being GC'ed too early if the finalizers are not being + // used correctly. + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + sum := make([]byte, 32) + _, err := SignPKCS1v15(rand.Reader, k, crypto.SHA256, sum) + if err != nil { + panic(err) // usually caused by memory corruption, so hard stop + } + }() + } + wg.Wait() + } +} + +func bigFromHex(hex string) *big.Int { + n, ok := new(big.Int).SetString(hex, 16) + if !ok { + panic("bad hex: " + hex) + } + return n +} + +func fromHex(hexStr string) []byte { + s, err := hex.DecodeString(hexStr) + if err != nil { + panic(err) + } + return s +} diff --git a/crypto/rsa/equal_test.go b/crypto/rsa/equal_test.go index f4ebad79e2f..19398df6ba3 100644 --- a/crypto/rsa/equal_test.go +++ b/crypto/rsa/equal_test.go @@ -5,17 +5,17 @@ package rsa_test import ( + "crypto" "testing" - "crypto/rand" - - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/crypto/x509" ) func TestEqual(t *testing.T) { - private, _ := rsa.GenerateKey(rand.Reader, 512) + t.Setenv("GODEBUG", "rsa1024min=0") + + private := test512Key public := &private.PublicKey if !public.Equal(public) { @@ -43,7 +43,7 @@ func TestEqual(t *testing.T) { t.Errorf("private key is not equal to itself after decoding: %v", private) } - other, _ := rsa.GenerateKey(rand.Reader, 512) + other := test512KeyTwo if public.Equal(other.Public()) { t.Errorf("different public keys are Equal") } diff --git a/crypto/rsa/example_test.go b/crypto/rsa/example_test.go index 0fdeec0c1ef..2a98651fb59 100644 --- a/crypto/rsa/example_test.go +++ b/crypto/rsa/example_test.go @@ -5,19 +5,77 @@ package rsa_test import ( + "crypto" "encoding/hex" + "encoding/pem" "fmt" "os" + "strings" "crypto/rand" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/aes" "github.com/runZeroInc/excrypto/crypto/cipher" "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/crypto/sha256" + "github.com/runZeroInc/excrypto/crypto/x509" ) +func ExampleGenerateKey() { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + fmt.Fprintf(os.Stderr, "Error generating RSA key: %s", err) + return + } + + der, err := x509.MarshalPKCS8PrivateKey(privateKey) + if err != nil { + fmt.Fprintf(os.Stderr, "Error marshalling RSA private key: %s", err) + return + } + + fmt.Printf("%s", pem.EncodeToMemory(&pem.Block{ + Type: "PRIVATE KEY", + Bytes: der, + })) +} + +func ExampleGenerateKey_testKey() { + // This is an insecure, test-only key from RFC 9500, Section 2.1. + // It can be used in tests to avoid slow key generation. + block, _ := pem.Decode([]byte(strings.ReplaceAll( + `-----BEGIN RSA TESTING KEY----- +MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso +tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE +89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU +l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s +B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59 +3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+ +dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI +FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J +aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2 +BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx +IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/ +fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u +pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT +Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl +u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD +fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X +Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE +k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo +qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS +CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ +XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw +AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r +UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0 +2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5 +7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3 +-----END RSA TESTING KEY-----`, "TESTING KEY", "PRIVATE KEY"))) + testRSA2048, _ := x509.ParsePKCS1PrivateKey(block.Bytes) + + fmt.Println("Private key bit size:", testRSA2048.N.BitLen()) +} + // RSA is able to encrypt only a very limited amount of data. In order // to encrypt reasonable amounts of data a hybrid scheme is commonly // used: RSA is used to encrypt a key for a symmetric primitive like diff --git a/crypto/rsa/fips.go b/crypto/rsa/fips.go new file mode 100644 index 00000000000..d9052f6f275 --- /dev/null +++ b/crypto/rsa/fips.go @@ -0,0 +1,436 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rsa + +import ( + "crypto" + "errors" + "hash" + "io" + + "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140hash" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" +) + +const ( + // PSSSaltLengthAuto causes the salt in a PSS signature to be as large + // as possible when signing, and to be auto-detected when verifying. + // + // When signing in FIPS 140-3 mode, the salt length is capped at the length + // of the hash function used in the signature. + PSSSaltLengthAuto = 0 + // PSSSaltLengthEqualsHash causes the salt length to equal the length + // of the hash used in the signature. + PSSSaltLengthEqualsHash = -1 +) + +// PSSOptions contains options for creating and verifying PSS signatures. +type PSSOptions struct { + // SaltLength controls the length of the salt used in the PSS signature. It + // can either be a positive number of bytes, or one of the special + // PSSSaltLength constants. + SaltLength int + + // Hash is the hash function used to generate the message digest. If not + // zero, it overrides the hash function passed to SignPSS. It's required + // when using PrivateKey.Sign. + Hash crypto.Hash +} + +// HashFunc returns opts.Hash so that [PSSOptions] implements [crypto.SignerOpts]. +func (opts *PSSOptions) HashFunc() crypto.Hash { + return opts.Hash +} + +func (opts *PSSOptions) saltLength() int { + if opts == nil { + return PSSSaltLengthAuto + } + return opts.SaltLength +} + +// SignPSS calculates the signature of digest using PSS. +// +// digest must be the result of hashing the input message using the given hash +// function. The opts argument may be nil, in which case sensible defaults are +// used. If opts.Hash is set, it overrides hash. +// +// The signature is randomized depending on the message, key, and salt size, +// using bytes from rand. Most applications should use [crypto/rand.Reader] as +// rand. +func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) { + if err := checkPublicKeySize(&priv.PublicKey); err != nil { + return nil, err + } + + if opts != nil && opts.Hash != 0 { + hash = opts.Hash + } + + if boring.Enabled && rand == boring.RandReader { + bkey, err := boringPrivateKey(priv) + if err != nil { + return nil, err + } + return boring.SignRSAPSS(bkey, hash, digest, opts.saltLength()) + } + boring.UnreachableExceptTests() + + h := fips140hash.Unwrap(hash.New()) + + if err := checkFIPS140OnlyPrivateKey(priv); err != nil { + return nil, err + } + if fips140only.Enabled && !fips140only.ApprovedHash(h) { + return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + if fips140only.Enabled && !fips140only.ApprovedRandomReader(rand) { + return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") + } + + k, err := fipsPrivateKey(priv) + if err != nil { + return nil, err + } + + saltLength := opts.saltLength() + if fips140only.Enabled && saltLength > h.Size() { + return nil, errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode") + } + switch saltLength { + case PSSSaltLengthAuto: + saltLength, err = rsa.PSSMaxSaltLength(k.PublicKey(), h) + if err != nil { + return nil, fipsError(err) + } + case PSSSaltLengthEqualsHash: + saltLength = h.Size() + default: + // If we get here saltLength is either > 0 or < -1, in the + // latter case we fail out. + if saltLength <= 0 { + return nil, errors.New("crypto/rsa: invalid PSS salt length") + } + } + + return fipsError2(rsa.SignPSS(rand, k, h, digest, saltLength)) +} + +// VerifyPSS verifies a PSS signature. +// +// A valid signature is indicated by returning a nil error. digest must be the +// result of hashing the input message using the given hash function. The opts +// argument may be nil, in which case sensible defaults are used. opts.Hash is +// ignored. +// +// The inputs are not considered confidential, and may leak through timing side +// channels, or if an attacker has control of part of the inputs. +func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts *PSSOptions) error { + if err := checkPublicKeySize(pub); err != nil { + return err + } + + if boring.Enabled { + bkey, err := boringPublicKey(pub) + if err != nil { + return err + } + if err := boring.VerifyRSAPSS(bkey, hash, digest, sig, opts.saltLength()); err != nil { + return ErrVerification + } + return nil + } + + h := fips140hash.Unwrap(hash.New()) + + if err := checkFIPS140OnlyPublicKey(pub); err != nil { + return err + } + if fips140only.Enabled && !fips140only.ApprovedHash(h) { + return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + + k, err := fipsPublicKey(pub) + if err != nil { + return err + } + + saltLength := opts.saltLength() + if fips140only.Enabled && saltLength > h.Size() { + return errors.New("crypto/rsa: use of PSS salt longer than the hash is not allowed in FIPS 140-only mode") + } + switch saltLength { + case PSSSaltLengthAuto: + return fipsError(rsa.VerifyPSS(k, h, digest, sig)) + case PSSSaltLengthEqualsHash: + return fipsError(rsa.VerifyPSSWithSaltLength(k, h, digest, sig, h.Size())) + default: + return fipsError(rsa.VerifyPSSWithSaltLength(k, h, digest, sig, saltLength)) + } +} + +// EncryptOAEP encrypts the given message with RSA-OAEP. +// +// OAEP is parameterised by a hash function that is used as a random oracle. +// Encryption and decryption of a given message must use the same hash function +// and sha256.New() is a reasonable choice. +// +// The random parameter is used as a source of entropy to ensure that +// encrypting the same message twice doesn't result in the same ciphertext. +// Most applications should use [crypto/rand.Reader] as random. +// +// The label parameter may contain arbitrary data that will not be encrypted, +// but which gives important context to the message. For example, if a given +// public key is used to encrypt two types of messages then distinct label +// values could be used to ensure that a ciphertext for one purpose cannot be +// used for another by an attacker. If not required it can be empty. +// +// The message must be no longer than the length of the public modulus minus +// twice the hash length, minus a further 2. +func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { + if err := checkPublicKeySize(pub); err != nil { + return nil, err + } + + defer hash.Reset() + + if boring.Enabled && random == boring.RandReader { + hash.Reset() + k := pub.Size() + if len(msg) > k-2*hash.Size()-2 { + return nil, ErrMessageTooLong + } + bkey, err := boringPublicKey(pub) + if err != nil { + return nil, err + } + return boring.EncryptRSAOAEP(hash, hash, bkey, msg, label) + } + boring.UnreachableExceptTests() + + hash = fips140hash.Unwrap(hash) + + if err := checkFIPS140OnlyPublicKey(pub); err != nil { + return nil, err + } + if fips140only.Enabled && !fips140only.ApprovedHash(hash) { + return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + if fips140only.Enabled && !fips140only.ApprovedRandomReader(random) { + return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") + } + + k, err := fipsPublicKey(pub) + if err != nil { + return nil, err + } + return fipsError2(rsa.EncryptOAEP(hash, hash, random, k, msg, label)) +} + +// DecryptOAEP decrypts ciphertext using RSA-OAEP. +// +// OAEP is parameterised by a hash function that is used as a random oracle. +// Encryption and decryption of a given message must use the same hash function +// and sha256.New() is a reasonable choice. +// +// The random parameter is legacy and ignored, and it can be nil. +// +// The label parameter must match the value given when encrypting. See +// [EncryptOAEP] for details. +func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { + defer hash.Reset() + return decryptOAEP(hash, hash, priv, ciphertext, label) +} + +func decryptOAEP(hash, mgfHash hash.Hash, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { + if err := checkPublicKeySize(&priv.PublicKey); err != nil { + return nil, err + } + + if boring.Enabled { + k := priv.Size() + if len(ciphertext) > k || + k < hash.Size()*2+2 { + return nil, ErrDecryption + } + bkey, err := boringPrivateKey(priv) + if err != nil { + return nil, err + } + out, err := boring.DecryptRSAOAEP(hash, mgfHash, bkey, ciphertext, label) + if err != nil { + return nil, ErrDecryption + } + return out, nil + } + + hash = fips140hash.Unwrap(hash) + mgfHash = fips140hash.Unwrap(mgfHash) + + if err := checkFIPS140OnlyPrivateKey(priv); err != nil { + return nil, err + } + if fips140only.Enabled { + if !fips140only.ApprovedHash(hash) || !fips140only.ApprovedHash(mgfHash) { + return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + } + + k, err := fipsPrivateKey(priv) + if err != nil { + return nil, err + } + + return fipsError2(rsa.DecryptOAEP(hash, mgfHash, k, ciphertext, label)) +} + +// SignPKCS1v15 calculates the signature of hashed using +// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS #1 v1.5. Note that hashed must +// be the result of hashing the input message using the given hash +// function. If hash is zero, hashed is signed directly. This isn't +// advisable except for interoperability. +// +// The random parameter is legacy and ignored, and it can be nil. +// +// This function is deterministic. Thus, if the set of possible +// messages is small, an attacker may be able to build a map from +// messages to signatures and identify the signed messages. As ever, +// signatures provide authenticity, not confidentiality. +func SignPKCS1v15(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { + var hashName string + if hash != crypto.Hash(0) { + if len(hashed) != hash.Size() { + return nil, errors.New("crypto/rsa: input must be hashed message") + } + hashName = hash.String() + } + + if err := checkPublicKeySize(&priv.PublicKey); err != nil { + return nil, err + } + + if boring.Enabled { + bkey, err := boringPrivateKey(priv) + if err != nil { + return nil, err + } + return boring.SignRSAPKCS1v15(bkey, hash, hashed) + } + + if err := checkFIPS140OnlyPrivateKey(priv); err != nil { + return nil, err + } + if fips140only.Enabled && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { + return nil, errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + + k, err := fipsPrivateKey(priv) + if err != nil { + return nil, err + } + return fipsError2(rsa.SignPKCS1v15(k, hashName, hashed)) +} + +// VerifyPKCS1v15 verifies an RSA PKCS #1 v1.5 signature. +// hashed is the result of hashing the input message using the given hash +// function and sig is the signature. A valid signature is indicated by +// returning a nil error. If hash is zero then hashed is used directly. This +// isn't advisable except for interoperability. +// +// The inputs are not considered confidential, and may leak through timing side +// channels, or if an attacker has control of part of the inputs. +func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error { + var hashName string + if hash != crypto.Hash(0) { + if len(hashed) != hash.Size() { + return errors.New("crypto/rsa: input must be hashed message") + } + hashName = hash.String() + } + + if err := checkPublicKeySize(pub); err != nil { + return err + } + + if boring.Enabled { + bkey, err := boringPublicKey(pub) + if err != nil { + return err + } + if err := boring.VerifyRSAPKCS1v15(bkey, hash, hashed, sig); err != nil { + return ErrVerification + } + return nil + } + + if err := checkFIPS140OnlyPublicKey(pub); err != nil { + return err + } + if fips140only.Enabled && !fips140only.ApprovedHash(fips140hash.Unwrap(hash.New())) { + return errors.New("crypto/rsa: use of hash functions other than SHA-2 or SHA-3 is not allowed in FIPS 140-only mode") + } + + k, err := fipsPublicKey(pub) + if err != nil { + return err + } + return fipsError(rsa.VerifyPKCS1v15(k, hashName, hashed, sig)) +} + +func fipsError(err error) error { + switch err { + case rsa.ErrDecryption: + return ErrDecryption + case rsa.ErrVerification: + return ErrVerification + case rsa.ErrMessageTooLong: + return ErrMessageTooLong + } + return err +} + +func fipsError2[T any](x T, err error) (T, error) { + return x, fipsError(err) +} + +func checkFIPS140OnlyPublicKey(pub *PublicKey) error { + if !fips140only.Enabled { + return nil + } + if pub.N == nil { + return errors.New("crypto/rsa: public key missing N") + } + if pub.N.BitLen() < 2048 { + return errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode") + } + if pub.N.BitLen()%2 == 1 { + return errors.New("crypto/rsa: use of keys with odd size is not allowed in FIPS 140-only mode") + } + if pub.E <= 1<<16 { + return errors.New("crypto/rsa: use of public exponent <= 2¹⁶ is not allowed in FIPS 140-only mode") + } + if pub.E&1 == 0 { + return errors.New("crypto/rsa: use of even public exponent is not allowed in FIPS 140-only mode") + } + return nil +} + +func checkFIPS140OnlyPrivateKey(priv *PrivateKey) error { + if !fips140only.Enabled { + return nil + } + if err := checkFIPS140OnlyPublicKey(&priv.PublicKey); err != nil { + return err + } + if len(priv.Primes) != 2 { + return errors.New("crypto/rsa: use of multi-prime keys is not allowed in FIPS 140-only mode") + } + if priv.Primes[0] == nil || priv.Primes[1] == nil || priv.Primes[0].BitLen() != priv.Primes[1].BitLen() { + return errors.New("crypto/rsa: use of primes of different sizes is not allowed in FIPS 140-only mode") + } + return nil +} diff --git a/crypto/rsa/pkcs1v15.go b/crypto/rsa/pkcs1v15.go index 7dbff21002e..3c220c8f5f6 100644 --- a/crypto/rsa/pkcs1v15.go +++ b/crypto/rsa/pkcs1v15.go @@ -5,12 +5,12 @@ package rsa import ( - "bytes" "errors" "io" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/internal/randutil" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -41,11 +41,16 @@ type PKCS1v15DecryptOptions struct { // WARNING: use of this function to encrypt plaintexts other than // session keys is dangerous. Use RSA OAEP in new protocols. func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, error) { - randutil.MaybeReadByte(random) + if fips140only.Enabled { + return nil, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode") + } - if err := checkPub(pub); err != nil { + if err := checkPublicKeySize(pub); err != nil { return nil, err } + + randutil.MaybeReadByte(random) + k := pub.Size() if len(msg) > k-11 { return nil, ErrMessageTooLong @@ -80,7 +85,11 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro return boring.EncryptRSANoPadding(bkey, em) } - return encrypt(pub, em) + fk, err := fipsPublicKey(pub) + if err != nil { + return nil, err + } + return rsa.Encrypt(fk, em) } // DecryptPKCS1v15 decrypts a plaintext using RSA and the padding scheme from PKCS #1 v1.5. @@ -92,7 +101,7 @@ func EncryptPKCS1v15(random io.Reader, pub *PublicKey, msg []byte) ([]byte, erro // forge signatures as if they had the private key. See // DecryptPKCS1v15SessionKey for a way of solving this problem. func DecryptPKCS1v15(random io.Reader, priv *PrivateKey, ciphertext []byte) ([]byte, error) { - if err := checkPub(&priv.PublicKey); err != nil { + if err := checkPublicKeySize(&priv.PublicKey); err != nil { return nil, err } @@ -153,9 +162,10 @@ func DecryptPKCS1v15(random io.Reader, priv *PrivateKey, ciphertext []byte) ([]b // - [1] RFC 3218, Preventing the Million Message Attack on CMS, // https://www.rfc-editor.org/rfc/rfc3218.html func DecryptPKCS1v15SessionKey(random io.Reader, priv *PrivateKey, ciphertext []byte, key []byte) error { - if err := checkPub(&priv.PublicKey); err != nil { + if err := checkPublicKeySize(&priv.PublicKey); err != nil { return err } + k := priv.Size() if k-(len(key)+3+8) < 0 { return ErrDecryption @@ -184,26 +194,34 @@ func DecryptPKCS1v15SessionKey(random io.Reader, priv *PrivateKey, ciphertext [] // access patterns. If the plaintext was valid then index contains the index of // the original message in em, to allow constant time padding removal. func decryptPKCS1v15(priv *PrivateKey, ciphertext []byte) (valid int, em []byte, index int, err error) { + if fips140only.Enabled { + return 0, nil, 0, errors.New("crypto/rsa: use of PKCS#1 v1.5 encryption is not allowed in FIPS 140-only mode") + } + k := priv.Size() if k < 11 { err = ErrDecryption - return + return 0, nil, 0, err } if boring.Enabled { var bkey *boring.PrivateKeyRSA bkey, err = boringPrivateKey(priv) if err != nil { - return + return 0, nil, 0, err } em, err = boring.DecryptRSANoPadding(bkey, ciphertext) if err != nil { - return + return 0, nil, 0, ErrDecryption } } else { - em, err = decrypt(priv, ciphertext, noCheck) + fk, err := fipsPrivateKey(priv) + if err != nil { + return 0, nil, 0, err + } + em, err = rsa.DecryptWithoutCheck(fk, ciphertext) if err != nil { - return + return 0, nil, 0, ErrDecryption } } @@ -252,128 +270,3 @@ func nonZeroRandomBytes(s []byte, random io.Reader) (err error) { return } - -// These are ASN1 DER structures: -// -// DigestInfo ::= SEQUENCE { -// digestAlgorithm AlgorithmIdentifier, -// digest OCTET STRING -// } -// -// For performance, we don't use the generic ASN1 encoder. Rather, we -// precompute a prefix of the digest value that makes a valid ASN1 DER string -// with the correct contents. -var hashPrefixes = map[crypto.Hash][]byte{ - crypto.MD5: {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, - crypto.SHA1: {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, - crypto.SHA224: {0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04, 0x1c}, - crypto.SHA256: {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, - crypto.SHA384: {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, - crypto.SHA512: {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}, - crypto.MD5SHA1: {}, // A special TLS case which doesn't use an ASN1 prefix. - crypto.RIPEMD160: {0x30, 0x20, 0x30, 0x08, 0x06, 0x06, 0x28, 0xcf, 0x06, 0x03, 0x00, 0x31, 0x04, 0x14}, -} - -// SignPKCS1v15 calculates the signature of hashed using -// RSASSA-PKCS1-V1_5-SIGN from RSA PKCS #1 v1.5. Note that hashed must -// be the result of hashing the input message using the given hash -// function. If hash is zero, hashed is signed directly. This isn't -// advisable except for interoperability. -// -// The random parameter is legacy and ignored, and it can be nil. -// -// This function is deterministic. Thus, if the set of possible -// messages is small, an attacker may be able to build a map from -// messages to signatures and identify the signed messages. As ever, -// signatures provide authenticity, not confidentiality. -func SignPKCS1v15(random io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) ([]byte, error) { - // pkcs1v15ConstructEM is called before boring.SignRSAPKCS1v15 to return - // consistent errors, including ErrMessageTooLong. - em, err := pkcs1v15ConstructEM(&priv.PublicKey, hash, hashed) - if err != nil { - return nil, err - } - - if boring.Enabled { - bkey, err := boringPrivateKey(priv) - if err != nil { - return nil, err - } - return boring.SignRSAPKCS1v15(bkey, hash, hashed) - } - - return decrypt(priv, em, withCheck) -} - -func pkcs1v15ConstructEM(pub *PublicKey, hash crypto.Hash, hashed []byte) ([]byte, error) { - // Special case: crypto.Hash(0) is used to indicate that the data is - // signed directly. - var prefix []byte - if hash != 0 { - if len(hashed) != hash.Size() { - return nil, errors.New("crypto/rsa: input must be hashed message") - } - var ok bool - prefix, ok = hashPrefixes[hash] - if !ok { - return nil, errors.New("crypto/rsa: unsupported hash function") - } - } - - // EM = 0x00 || 0x01 || PS || 0x00 || T - k := pub.Size() - if k < len(prefix)+len(hashed)+2+8+1 { - return nil, ErrMessageTooLong - } - em := make([]byte, k) - em[1] = 1 - for i := 2; i < k-len(prefix)-len(hashed)-1; i++ { - em[i] = 0xff - } - copy(em[k-len(prefix)-len(hashed):], prefix) - copy(em[k-len(hashed):], hashed) - return em, nil -} - -// VerifyPKCS1v15 verifies an RSA PKCS #1 v1.5 signature. -// hashed is the result of hashing the input message using the given hash -// function and sig is the signature. A valid signature is indicated by -// returning a nil error. If hash is zero then hashed is used directly. This -// isn't advisable except for interoperability. -// -// The inputs are not considered confidential, and may leak through timing side -// channels, or if an attacker has control of part of the inputs. -func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) error { - if boring.Enabled { - bkey, err := boringPublicKey(pub) - if err != nil { - return err - } - if err := boring.VerifyRSAPKCS1v15(bkey, hash, hashed, sig); err != nil { - return ErrVerification - } - return nil - } - - // RFC 8017 Section 8.2.2: If the length of the signature S is not k - // octets (where k is the length in octets of the RSA modulus n), output - // "invalid signature" and stop. - if pub.Size() != len(sig) { - return ErrVerification - } - - em, err := encrypt(pub, sig) - if err != nil { - return ErrVerification - } - - expected, err := pkcs1v15ConstructEM(pub, hash, hashed) - if err != nil { - return ErrVerification - } - if !bytes.Equal(em, expected) { - return ErrVerification - } - - return nil -} diff --git a/crypto/rsa/pkcs1v15_test.go b/crypto/rsa/pkcs1v15_test.go index 21ee8d85006..d886c2aea4e 100644 --- a/crypto/rsa/pkcs1v15_test.go +++ b/crypto/rsa/pkcs1v15_test.go @@ -56,12 +56,14 @@ var decryptPKCS1v15Tests = []DecryptPKCS1v15Test{ } func TestDecryptPKCS1v15(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") + decryptionFuncs := []func([]byte) ([]byte, error){ func(ciphertext []byte) (plaintext []byte, err error) { - return DecryptPKCS1v15(nil, rsaPrivateKey, ciphertext) + return DecryptPKCS1v15(nil, test512Key, ciphertext) }, func(ciphertext []byte) (plaintext []byte, err error) { - return rsaPrivateKey.Decrypt(nil, ciphertext, nil) + return test512Key.Decrypt(nil, ciphertext, nil) }, } @@ -141,9 +143,10 @@ var decryptPKCS1v15SessionKeyTests = []DecryptPKCS1v15Test{ } func TestEncryptPKCS1v15SessionKey(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") for i, test := range decryptPKCS1v15SessionKeyTests { key := []byte("FAIL") - err := DecryptPKCS1v15SessionKey(nil, rsaPrivateKey, decodeBase64(test.in), key) + err := DecryptPKCS1v15SessionKey(nil, test512Key, decodeBase64(test.in), key) if err != nil { t.Errorf("#%d error decrypting", i) } @@ -155,8 +158,9 @@ func TestEncryptPKCS1v15SessionKey(t *testing.T) { } func TestEncryptPKCS1v15DecrypterSessionKey(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") for i, test := range decryptPKCS1v15SessionKeyTests { - plaintext, err := rsaPrivateKey.Decrypt(rand.Reader, decodeBase64(test.in), &PKCS1v15DecryptOptions{SessionKeyLen: 4}) + plaintext, err := test512Key.Decrypt(rand.Reader, decodeBase64(test.in), &PKCS1v15DecryptOptions{SessionKeyLen: 4}) if err != nil { t.Fatalf("#%d: error decrypting: %s", i, err) } @@ -198,12 +202,13 @@ var signPKCS1v15Tests = []signPKCS1v15Test{ } func TestSignPKCS1v15(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") for i, test := range signPKCS1v15Tests { h := sha1.New() h.Write([]byte(test.in)) digest := h.Sum(nil) - s, err := SignPKCS1v15(nil, rsaPrivateKey, crypto.SHA1, digest) + s, err := SignPKCS1v15(nil, test512Key, crypto.SHA1, digest) if err != nil { t.Errorf("#%d %s", i, err) } @@ -216,6 +221,7 @@ func TestSignPKCS1v15(t *testing.T) { } func TestVerifyPKCS1v15(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") for i, test := range signPKCS1v15Tests { h := sha1.New() h.Write([]byte(test.in)) @@ -223,7 +229,7 @@ func TestVerifyPKCS1v15(t *testing.T) { sig, _ := hex.DecodeString(test.out) - err := VerifyPKCS1v15(&rsaPrivateKey.PublicKey, crypto.SHA1, digest, sig) + err := VerifyPKCS1v15(&test512Key.PublicKey, crypto.SHA1, digest, sig) if err != nil { t.Errorf("#%d %s", i, err) } @@ -231,14 +237,17 @@ func TestVerifyPKCS1v15(t *testing.T) { } func TestOverlongMessagePKCS1v15(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") ciphertext := decodeBase64("fjOVdirUzFoLlukv80dBllMLjXythIf22feqPrNo0YoIjzyzyoMFiLjAc/Y4krkeZ11XFThIrEvw\nkRiZcCq5ng==") - _, err := DecryptPKCS1v15(nil, rsaPrivateKey, ciphertext) + _, err := DecryptPKCS1v15(nil, test512Key, ciphertext) if err == nil { t.Error("RSA decrypted a message that was too long.") } } func TestUnpaddedSignature(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") + msg := []byte("Thu Dec 19 18:06:16 EST 2013\n") // This base64 value was generated with: // % echo Thu Dec 19 18:06:16 EST 2013 > /tmp/msg @@ -248,14 +257,14 @@ func TestUnpaddedSignature(t *testing.T) { // file. expectedSig := decodeBase64("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==") - sig, err := SignPKCS1v15(nil, rsaPrivateKey, crypto.Hash(0), msg) + sig, err := SignPKCS1v15(nil, test512Key, crypto.Hash(0), msg) if err != nil { t.Fatalf("SignPKCS1v15 failed: %s", err) } if !bytes.Equal(sig, expectedSig) { t.Fatalf("signature is not expected value: got %x, want %x", sig, expectedSig) } - if err := VerifyPKCS1v15(&rsaPrivateKey.PublicKey, crypto.Hash(0), msg, sig); err != nil { + if err := VerifyPKCS1v15(&test512Key.PublicKey, crypto.Hash(0), msg, sig); err != nil { t.Fatalf("signature failed to verify: %s", err) } } @@ -280,16 +289,6 @@ func TestShortSessionKey(t *testing.T) { } } -var rsaPrivateKey = parseKey(testingKey(`-----BEGIN RSA TESTING KEY----- -MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0 -fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu -/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu -RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/ -EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A -IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS -tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V ------END RSA TESTING KEY-----`)) - func parsePublicKey(s string) *PublicKey { p, _ := pem.Decode([]byte(s)) k, err := x509.ParsePKCS1PublicKey(p.Bytes) diff --git a/crypto/rsa/pss.go b/crypto/rsa/pss.go deleted file mode 100644 index d31c15530fb..00000000000 --- a/crypto/rsa/pss.go +++ /dev/null @@ -1,386 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package rsa - -// This file implements the RSASSA-PSS signature scheme according to RFC 8017. - -import ( - "bytes" - "errors" - "hash" - "io" - - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/crypto/internal/boring" -) - -// Per RFC 8017, Section 9.1 -// -// EM = MGF1 xor DB || H( 8*0x00 || mHash || salt ) || 0xbc -// -// where -// -// DB = PS || 0x01 || salt -// -// and PS can be empty so -// -// emLen = dbLen + hLen + 1 = psLen + sLen + hLen + 2 -// - -func emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error) { - // See RFC 8017, Section 9.1.1. - - hLen := hash.Size() - sLen := len(salt) - emLen := (emBits + 7) / 8 - - // 1. If the length of M is greater than the input limitation for the - // hash function (2^61 - 1 octets for SHA-1), output "message too - // long" and stop. - // - // 2. Let mHash = Hash(M), an octet string of length hLen. - - if len(mHash) != hLen { - return nil, errors.New("crypto/rsa: input must be hashed with given hash") - } - - // 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. - - if emLen < hLen+sLen+2 { - return nil, ErrMessageTooLong - } - - em := make([]byte, emLen) - psLen := emLen - sLen - hLen - 2 - db := em[:psLen+1+sLen] - h := em[psLen+1+sLen : emLen-1] - - // 4. Generate a random octet string salt of length sLen; if sLen = 0, - // then salt is the empty string. - // - // 5. Let - // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; - // - // M' is an octet string of length 8 + hLen + sLen with eight - // initial zero octets. - // - // 6. Let H = Hash(M'), an octet string of length hLen. - - var prefix [8]byte - - hash.Write(prefix[:]) - hash.Write(mHash) - hash.Write(salt) - - h = hash.Sum(h[:0]) - hash.Reset() - - // 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2 - // zero octets. The length of PS may be 0. - // - // 8. Let DB = PS || 0x01 || salt; DB is an octet string of length - // emLen - hLen - 1. - - db[psLen] = 0x01 - copy(db[psLen+1:], salt) - - // 9. Let dbMask = MGF(H, emLen - hLen - 1). - // - // 10. Let maskedDB = DB \xor dbMask. - - mgf1XOR(db, hash, h) - - // 11. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in - // maskedDB to zero. - - db[0] &= 0xff >> (8*emLen - emBits) - - // 12. Let EM = maskedDB || H || 0xbc. - em[emLen-1] = 0xbc - - // 13. Output EM. - return em, nil -} - -func emsaPSSVerify(mHash, em []byte, emBits, sLen int, hash hash.Hash) error { - // See RFC 8017, Section 9.1.2. - - hLen := hash.Size() - if sLen == PSSSaltLengthEqualsHash { - sLen = hLen - } - emLen := (emBits + 7) / 8 - if emLen != len(em) { - return errors.New("rsa: internal error: inconsistent length") - } - - // 1. If the length of M is greater than the input limitation for the - // hash function (2^61 - 1 octets for SHA-1), output "inconsistent" - // and stop. - // - // 2. Let mHash = Hash(M), an octet string of length hLen. - if hLen != len(mHash) { - return ErrVerification - } - - // 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. - if emLen < hLen+sLen+2 { - return ErrVerification - } - - // 4. If the rightmost octet of EM does not have hexadecimal value - // 0xbc, output "inconsistent" and stop. - if em[emLen-1] != 0xbc { - return ErrVerification - } - - // 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and - // let H be the next hLen octets. - db := em[:emLen-hLen-1] - h := em[emLen-hLen-1 : emLen-1] - - // 6. If the leftmost 8 * emLen - emBits bits of the leftmost octet in - // maskedDB are not all equal to zero, output "inconsistent" and - // stop. - var bitMask byte = 0xff >> (8*emLen - emBits) - if em[0] & ^bitMask != 0 { - return ErrVerification - } - - // 7. Let dbMask = MGF(H, emLen - hLen - 1). - // - // 8. Let DB = maskedDB \xor dbMask. - mgf1XOR(db, hash, h) - - // 9. Set the leftmost 8 * emLen - emBits bits of the leftmost octet in DB - // to zero. - db[0] &= bitMask - - // If we don't know the salt length, look for the 0x01 delimiter. - if sLen == PSSSaltLengthAuto { - psLen := bytes.IndexByte(db, 0x01) - if psLen < 0 { - return ErrVerification - } - sLen = len(db) - psLen - 1 - } - - // 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero - // or if the octet at position emLen - hLen - sLen - 1 (the leftmost - // position is "position 1") does not have hexadecimal value 0x01, - // output "inconsistent" and stop. - psLen := emLen - hLen - sLen - 2 - for _, e := range db[:psLen] { - if e != 0x00 { - return ErrVerification - } - } - if db[psLen] != 0x01 { - return ErrVerification - } - - // 11. Let salt be the last sLen octets of DB. - salt := db[len(db)-sLen:] - - // 12. Let - // M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt ; - // M' is an octet string of length 8 + hLen + sLen with eight - // initial zero octets. - // - // 13. Let H' = Hash(M'), an octet string of length hLen. - var prefix [8]byte - hash.Write(prefix[:]) - hash.Write(mHash) - hash.Write(salt) - - h0 := hash.Sum(nil) - - // 14. If H = H', output "consistent." Otherwise, output "inconsistent." - if !bytes.Equal(h0, h) { // TODO: constant time? - return ErrVerification - } - return nil -} - -// signPSSWithSalt calculates the signature of hashed using PSS with specified salt. -// Note that hashed must be the result of hashing the input message using the -// given hash function. salt is a random sequence of bytes whose length will be -// later used to verify the signature. -func signPSSWithSalt(priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) ([]byte, error) { - emBits := priv.N.BitLen() - 1 - em, err := emsaPSSEncode(hashed, emBits, salt, hash.New()) - if err != nil { - return nil, err - } - - if boring.Enabled { - bkey, err := boringPrivateKey(priv) - if err != nil { - return nil, err - } - // Note: BoringCrypto always does decrypt "withCheck". - // (It's not just decrypt.) - s, err := boring.DecryptRSANoPadding(bkey, em) - if err != nil { - return nil, err - } - return s, nil - } - - // RFC 8017: "Note that the octet length of EM will be one less than k if - // modBits - 1 is divisible by 8 and equal to k otherwise, where k is the - // length in octets of the RSA modulus n." 🙄 - // - // This is extremely annoying, as all other encrypt and decrypt inputs are - // always the exact same size as the modulus. Since it only happens for - // weird modulus sizes, fix it by padding inefficiently. - if emLen, k := len(em), priv.Size(); emLen < k { - emNew := make([]byte, k) - copy(emNew[k-emLen:], em) - em = emNew - } - - return decrypt(priv, em, withCheck) -} - -const ( - // PSSSaltLengthAuto causes the salt in a PSS signature to be as large - // as possible when signing, and to be auto-detected when verifying. - PSSSaltLengthAuto = 0 - // PSSSaltLengthEqualsHash causes the salt length to equal the length - // of the hash used in the signature. - PSSSaltLengthEqualsHash = -1 -) - -// PSSOptions contains options for creating and verifying PSS signatures. -type PSSOptions struct { - // SaltLength controls the length of the salt used in the PSS signature. It - // can either be a positive number of bytes, or one of the special - // PSSSaltLength constants. - SaltLength int - - // Hash is the hash function used to generate the message digest. If not - // zero, it overrides the hash function passed to SignPSS. It's required - // when using PrivateKey.Sign. - Hash crypto.Hash -} - -// HashFunc returns opts.Hash so that [PSSOptions] implements [crypto.SignerOpts]. -func (opts *PSSOptions) HashFunc() crypto.Hash { - return opts.Hash -} - -func (opts *PSSOptions) saltLength() int { - if opts == nil { - return PSSSaltLengthAuto - } - return opts.SaltLength -} - -var invalidSaltLenErr = errors.New("crypto/rsa: PSSOptions.SaltLength cannot be negative") - -// SignPSS calculates the signature of digest using PSS. -// -// digest must be the result of hashing the input message using the given hash -// function. The opts argument may be nil, in which case sensible defaults are -// used. If opts.Hash is set, it overrides hash. -// -// The signature is randomized depending on the message, key, and salt size, -// using bytes from rand. Most applications should use [crypto/rand.Reader] as -// rand. -func SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, digest []byte, opts *PSSOptions) ([]byte, error) { - // Note that while we don't commit to deterministic execution with respect - // to the rand stream, we also don't apply MaybeReadByte, so per Hyrum's Law - // it's probably relied upon by some. It's a tolerable promise because a - // well-specified number of random bytes is included in the signature, in a - // well-specified way. - - if opts != nil && opts.Hash != 0 { - hash = opts.Hash - } - - if boring.Enabled && rand == boring.RandReader { - bkey, err := boringPrivateKey(priv) - if err != nil { - return nil, err - } - return boring.SignRSAPSS(bkey, hash, digest, opts.saltLength()) - } - boring.UnreachableExceptTests() - - saltLength := opts.saltLength() - switch saltLength { - case PSSSaltLengthAuto: - saltLength = (priv.N.BitLen()-1+7)/8 - 2 - hash.Size() - if saltLength < 0 { - return nil, ErrMessageTooLong - } - case PSSSaltLengthEqualsHash: - saltLength = hash.Size() - default: - // If we get here saltLength is either > 0 or < -1, in the - // latter case we fail out. - if saltLength <= 0 { - return nil, invalidSaltLenErr - } - } - salt := make([]byte, saltLength) - if _, err := io.ReadFull(rand, salt); err != nil { - return nil, err - } - return signPSSWithSalt(priv, hash, digest, salt) -} - -// VerifyPSS verifies a PSS signature. -// -// A valid signature is indicated by returning a nil error. digest must be the -// result of hashing the input message using the given hash function. The opts -// argument may be nil, in which case sensible defaults are used. opts.Hash is -// ignored. -// -// The inputs are not considered confidential, and may leak through timing side -// channels, or if an attacker has control of part of the inputs. -func VerifyPSS(pub *PublicKey, hash crypto.Hash, digest []byte, sig []byte, opts *PSSOptions) error { - if boring.Enabled { - bkey, err := boringPublicKey(pub) - if err != nil { - return err - } - if err := boring.VerifyRSAPSS(bkey, hash, digest, sig, opts.saltLength()); err != nil { - return ErrVerification - } - return nil - } - if len(sig) != pub.Size() { - return ErrVerification - } - // Salt length must be either one of the special constants (-1 or 0) - // or otherwise positive. If it is < PSSSaltLengthEqualsHash (-1) - // we return an error. - if opts.saltLength() < PSSSaltLengthEqualsHash { - return invalidSaltLenErr - } - - emBits := pub.N.BitLen() - 1 - emLen := (emBits + 7) / 8 - em, err := encrypt(pub, sig) - if err != nil { - return ErrVerification - } - - // Like in signPSSWithSalt, deal with mismatches between emLen and the size - // of the modulus. The spec would have us wire emLen into the encoding - // function, but we'd rather always encode to the size of the modulus and - // then strip leading zeroes if necessary. This only happens for weird - // modulus sizes anyway. - for len(em) > emLen && len(em) > 0 { - if em[0] != 0 { - return ErrVerification - } - em = em[1:] - } - - return emsaPSSVerify(digest, em, emBits, opts.saltLength(), hash.New()) -} diff --git a/crypto/rsa/pss_test.go b/crypto/rsa/pss_test.go index e01da05ee97..22ae0f0dcff 100644 --- a/crypto/rsa/pss_test.go +++ b/crypto/rsa/pss_test.go @@ -6,8 +6,9 @@ package rsa_test import ( "bufio" - "bytes" "compress/bzip2" + "crypto" + . "crypto/rsa" "encoding/hex" "math/big" "os" @@ -17,66 +18,11 @@ import ( "crypto/rand" - "github.com/runZeroInc/excrypto/crypto" - . "github.com/runZeroInc/excrypto/crypto/rsa" - "github.com/runZeroInc/excrypto/crypto/sha1" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" "github.com/runZeroInc/excrypto/crypto/sha256" "github.com/runZeroInc/excrypto/crypto/sha512" ) -func TestEMSAPSS(t *testing.T) { - // Test vector in file pss-int.txt from: ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip - msg := []byte{ - 0x85, 0x9e, 0xef, 0x2f, 0xd7, 0x8a, 0xca, 0x00, 0x30, 0x8b, - 0xdc, 0x47, 0x11, 0x93, 0xbf, 0x55, 0xbf, 0x9d, 0x78, 0xdb, - 0x8f, 0x8a, 0x67, 0x2b, 0x48, 0x46, 0x34, 0xf3, 0xc9, 0xc2, - 0x6e, 0x64, 0x78, 0xae, 0x10, 0x26, 0x0f, 0xe0, 0xdd, 0x8c, - 0x08, 0x2e, 0x53, 0xa5, 0x29, 0x3a, 0xf2, 0x17, 0x3c, 0xd5, - 0x0c, 0x6d, 0x5d, 0x35, 0x4f, 0xeb, 0xf7, 0x8b, 0x26, 0x02, - 0x1c, 0x25, 0xc0, 0x27, 0x12, 0xe7, 0x8c, 0xd4, 0x69, 0x4c, - 0x9f, 0x46, 0x97, 0x77, 0xe4, 0x51, 0xe7, 0xf8, 0xe9, 0xe0, - 0x4c, 0xd3, 0x73, 0x9c, 0x6b, 0xbf, 0xed, 0xae, 0x48, 0x7f, - 0xb5, 0x56, 0x44, 0xe9, 0xca, 0x74, 0xff, 0x77, 0xa5, 0x3c, - 0xb7, 0x29, 0x80, 0x2f, 0x6e, 0xd4, 0xa5, 0xff, 0xa8, 0xba, - 0x15, 0x98, 0x90, 0xfc, - } - salt := []byte{ - 0xe3, 0xb5, 0xd5, 0xd0, 0x02, 0xc1, 0xbc, 0xe5, 0x0c, 0x2b, - 0x65, 0xef, 0x88, 0xa1, 0x88, 0xd8, 0x3b, 0xce, 0x7e, 0x61, - } - expected := []byte{ - 0x66, 0xe4, 0x67, 0x2e, 0x83, 0x6a, 0xd1, 0x21, 0xba, 0x24, - 0x4b, 0xed, 0x65, 0x76, 0xb8, 0x67, 0xd9, 0xa4, 0x47, 0xc2, - 0x8a, 0x6e, 0x66, 0xa5, 0xb8, 0x7d, 0xee, 0x7f, 0xbc, 0x7e, - 0x65, 0xaf, 0x50, 0x57, 0xf8, 0x6f, 0xae, 0x89, 0x84, 0xd9, - 0xba, 0x7f, 0x96, 0x9a, 0xd6, 0xfe, 0x02, 0xa4, 0xd7, 0x5f, - 0x74, 0x45, 0xfe, 0xfd, 0xd8, 0x5b, 0x6d, 0x3a, 0x47, 0x7c, - 0x28, 0xd2, 0x4b, 0xa1, 0xe3, 0x75, 0x6f, 0x79, 0x2d, 0xd1, - 0xdc, 0xe8, 0xca, 0x94, 0x44, 0x0e, 0xcb, 0x52, 0x79, 0xec, - 0xd3, 0x18, 0x3a, 0x31, 0x1f, 0xc8, 0x96, 0xda, 0x1c, 0xb3, - 0x93, 0x11, 0xaf, 0x37, 0xea, 0x4a, 0x75, 0xe2, 0x4b, 0xdb, - 0xfd, 0x5c, 0x1d, 0xa0, 0xde, 0x7c, 0xec, 0xdf, 0x1a, 0x89, - 0x6f, 0x9d, 0x8b, 0xc8, 0x16, 0xd9, 0x7c, 0xd7, 0xa2, 0xc4, - 0x3b, 0xad, 0x54, 0x6f, 0xbe, 0x8c, 0xfe, 0xbc, - } - - hash := sha1.New() - hash.Write(msg) - hashed := hash.Sum(nil) - - encoded, err := EMSAPSSEncode(hashed, 1023, salt, sha1.New()) - if err != nil { - t.Errorf("Error from emsaPSSEncode: %s\n", err) - } - if !bytes.Equal(encoded, expected) { - t.Errorf("Bad encoding. got %x, want %x", encoded, expected) - } - - if err = EMSAPSSVerify(hashed, encoded, 1023, len(salt), sha1.New()); err != nil { - t.Errorf("Bad verification: %s", err) - } -} - // TestPSSGolden tests all the test vectors in pss-vect.txt from // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip func TestPSSGolden(t *testing.T) { @@ -171,6 +117,8 @@ func TestPSSGolden(t *testing.T) { // TestPSSOpenSSL ensures that we can verify a PSS signature from OpenSSL with // the default options. OpenSSL sets the salt length to be maximal. func TestPSSOpenSSL(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") + hash := crypto.SHA256 h := hash.New() h.Write([]byte("testing")) @@ -187,7 +135,7 @@ func TestPSSOpenSSL(t *testing.T) { 0x0a, 0x37, 0x9c, 0x69, } - if err := VerifyPSS(&rsaPrivateKey.PublicKey, hash, hashed, sig, nil); err != nil { + if err := VerifyPSS(&test512Key.PublicKey, hash, hashed, sig, nil); err != nil { t.Error(err) } } @@ -204,17 +152,20 @@ func TestPSSNilOpts(t *testing.T) { func TestPSSSigning(t *testing.T) { var saltLengthCombinations = []struct { signSaltLength, verifySaltLength int - good bool + good, fipsGood bool }{ - {PSSSaltLengthAuto, PSSSaltLengthAuto, true}, - {PSSSaltLengthEqualsHash, PSSSaltLengthAuto, true}, - {PSSSaltLengthEqualsHash, PSSSaltLengthEqualsHash, true}, - {PSSSaltLengthEqualsHash, 8, false}, - {PSSSaltLengthAuto, PSSSaltLengthEqualsHash, false}, - {8, 8, true}, - {PSSSaltLengthAuto, 42, true}, - {PSSSaltLengthAuto, 20, false}, - {PSSSaltLengthAuto, -2, false}, + {PSSSaltLengthAuto, PSSSaltLengthAuto, true, true}, + {PSSSaltLengthEqualsHash, PSSSaltLengthAuto, true, true}, + {PSSSaltLengthEqualsHash, PSSSaltLengthEqualsHash, true, true}, + {PSSSaltLengthEqualsHash, 8, false, false}, + {8, 8, true, true}, + {8, PSSSaltLengthAuto, true, true}, + {42, PSSSaltLengthAuto, true, true}, + // In FIPS mode, PSSSaltLengthAuto is capped at PSSSaltLengthEqualsHash. + {PSSSaltLengthAuto, PSSSaltLengthEqualsHash, false, true}, + {PSSSaltLengthAuto, 106, true, false}, + {PSSSaltLengthAuto, 20, false, true}, + {PSSSaltLengthAuto, -2, false, false}, } hash := crypto.SHA1 @@ -233,7 +184,11 @@ func TestPSSSigning(t *testing.T) { opts.SaltLength = test.verifySaltLength err = VerifyPSS(&rsaPrivateKey.PublicKey, hash, hashed, sig, &opts) - if (err == nil) != test.good { + good := test.good + if fips140.Enabled { + good = test.fipsGood + } + if (err == nil) != good { t.Errorf("#%d: bad result, wanted: %t, got: %s", i, test.good, err) } } @@ -243,6 +198,7 @@ func TestPSS513(t *testing.T) { // See Issue 42741, and separately, RFC 8017: "Note that the octet length of // EM will be one less than k if modBits - 1 is divisible by 8 and equal to // k otherwise, where k is the length in octets of the RSA modulus n." + t.Setenv("GODEBUG", "rsa1024min=0") key, err := GenerateKey(rand.Reader, 513) if err != nil { t.Fatal(err) @@ -286,19 +242,18 @@ func fromHex(hexStr string) []byte { } func TestInvalidPSSSaltLength(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") key, err := GenerateKey(rand.Reader, 245) if err != nil { t.Fatal(err) } digest := sha256.Sum256([]byte("message")) - // We don't check the exact error matches, because crypto/rsa and crypto/internal/boring - // return two different error variables, which have the same content but are not equal. if _, err := SignPSS(rand.Reader, key, crypto.SHA256, digest[:], &PSSOptions{ SaltLength: -2, Hash: crypto.SHA256, - }); err.Error() != InvalidSaltLenErr.Error() { - t.Fatalf("SignPSS unexpected error: got %v, want %v", err, InvalidSaltLenErr) + }); err.Error() != "crypto/rsa: invalid PSS salt length" { + t.Fatalf("SignPSS unexpected error: got %v, want %v", err, "crypto/rsa: invalid PSS salt length") } // We don't check the specific error here, because crypto/rsa and crypto/internal/boring @@ -311,20 +266,15 @@ func TestInvalidPSSSaltLength(t *testing.T) { } func TestHashOverride(t *testing.T) { - key, err := GenerateKey(rand.Reader, 1024) - if err != nil { - t.Fatal(err) - } - digest := sha512.Sum512([]byte("message")) // opts.Hash overrides the passed hash argument. - sig, err := SignPSS(rand.Reader, key, crypto.SHA256, digest[:], &PSSOptions{Hash: crypto.SHA512}) + sig, err := SignPSS(rand.Reader, test2048Key, crypto.SHA256, digest[:], &PSSOptions{Hash: crypto.SHA512}) if err != nil { t.Fatalf("SignPSS unexpected error: got %v, want nil", err) } // VerifyPSS has the inverse behavior, opts.Hash is always ignored, check this is true. - if err := VerifyPSS(&key.PublicKey, crypto.SHA512, digest[:], sig, &PSSOptions{Hash: crypto.SHA256}); err != nil { + if err := VerifyPSS(&test2048Key.PublicKey, crypto.SHA512, digest[:], sig, &PSSOptions{Hash: crypto.SHA256}); err != nil { t.Fatalf("VerifyPSS unexpected error: got %v, want nil", err) } } diff --git a/crypto/rsa/rsa.go b/crypto/rsa/rsa.go index 7331bb54257..6cb99fd8176 100644 --- a/crypto/rsa/rsa.go +++ b/crypto/rsa/rsa.go @@ -20,23 +20,42 @@ // Decrypter and Signer interfaces from the crypto package. // // Operations involving private keys are implemented using constant-time -// algorithms, except for [GenerateKey], [PrivateKey.Precompute], and -// [PrivateKey.Validate]. +// algorithms, except for [GenerateKey] and for some operations involving +// deprecated multi-prime keys. +// +// # Minimum key size +// +// [GenerateKey] returns an error if a key of less than 1024 bits is requested, +// and all Sign, Verify, Encrypt, and Decrypt methods return an error if used +// with a key smaller than 1024 bits. Such keys are insecure and should not be +// used. +// +// The `rsa1024min=0` GODEBUG setting suppresses this error, but we recommend +// doing so only in tests, if necessary. Tests can use [testing.T.Setenv] or +// include `//go:debug rsa1024min=0` in a `_test.go` source file to set it. +// +// Alternatively, see the [GenerateKey (TestKey)] example for a pregenerated +// test-only 2048-bit key. +// +// [GenerateKey (TestKey)]: #example-GenerateKey-TestKey package rsa import ( + "crypto" "errors" - "hash" + "fmt" + "internal/godebug" "io" "math" "math/big" "crypto/rand" - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/crypto/internal/bigmod" "github.com/runZeroInc/excrypto/crypto/internal/boring" "github.com/runZeroInc/excrypto/crypto/internal/boring/bbig" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/bigmod" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" "github.com/runZeroInc/excrypto/crypto/internal/randutil" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -86,30 +105,6 @@ type OAEPOptions struct { Label []byte } -var ( - errPublicModulus = errors.New("crypto/rsa: missing public modulus") - errPublicExponentSmall = errors.New("crypto/rsa: public exponent too small") - errPublicExponentLarge = errors.New("crypto/rsa: public exponent too large") -) - -// checkPub sanity checks the public key before we use it. -// We require pub.E to fit into a 32-bit integer so that we -// do not have different behavior depending on whether -// int is 32 or 64 bits. See also -// https://www.imperialviolet.org/2012/03/16/rsae.html. -func checkPub(pub *PublicKey) error { - if pub.N == nil { - return errPublicModulus - } - if pub.E < 2 { - return errPublicExponentSmall - } - if pub.E > 1<<31-1 { - return errPublicExponentLarge - } - return nil -} - // A PrivateKey represents an RSA key type PrivateKey struct { PublicKey // public part. @@ -181,9 +176,9 @@ func (priv *PrivateKey) Decrypt(rand io.Reader, ciphertext []byte, opts crypto.D switch opts := opts.(type) { case *OAEPOptions: if opts.MGFHash == 0 { - return decryptOAEP(opts.Hash.New(), opts.Hash.New(), rand, priv, ciphertext, opts.Label) + return decryptOAEP(opts.Hash.New(), opts.Hash.New(), priv, ciphertext, opts.Label) } else { - return decryptOAEP(opts.Hash.New(), opts.MGFHash.New(), rand, priv, ciphertext, opts.Label) + return decryptOAEP(opts.Hash.New(), opts.MGFHash.New(), priv, ciphertext, opts.Label) } case *PKCS1v15DecryptOptions: @@ -220,7 +215,7 @@ type PrecomputedValues struct { // complexity. CRTValues []CRTValue - n, p, q *bigmod.Modulus // moduli for CRT with Montgomery precomputed constants + fips *rsa.PrivateKey } // CRTValue contains the precomputed Chinese remainder theorem values. @@ -232,74 +227,62 @@ type CRTValue struct { // Validate performs basic sanity checks on the key. // It returns nil if the key is valid, or else an error describing a problem. +// +// It runs faster on valid keys if run after [Precompute]. func (priv *PrivateKey) Validate() error { - if err := checkPub(&priv.PublicKey); err != nil { - return err - } + // We can operate on keys based on d alone, but it isn't possible to encode + // with [crypto/x509.MarshalPKCS1PrivateKey], which unfortunately doesn't + // return an error. + if len(priv.Primes) < 2 { + return errors.New("crypto/rsa: missing primes") + } + // If Precomputed.fips is set, then the key has been validated by + // [rsa.NewPrivateKey] or [rsa.NewPrivateKeyWithoutCRT]. + if priv.Precomputed.fips != nil { + return nil + } + _, err := priv.precompute() + return err +} - // Check that Πprimes == n. - modulus := new(big.Int).Set(bigOne) - for _, prime := range priv.Primes { - // Any primes ≤ 1 will cause divide-by-zero panics later. - if prime.Cmp(bigOne) <= 0 { - return errors.New("crypto/rsa: invalid prime value") - } - modulus.Mul(modulus, prime) +// rsa1024min is a GODEBUG that re-enables weak RSA keys if set to "0". +// See https://go.dev/issue/68762. +var rsa1024min = godebug.New("rsa1024min") + +func checkKeySize(size int) error { + if size >= 1024 { + return nil } - if modulus.Cmp(priv.N) != 0 { - return errors.New("crypto/rsa: invalid modulus") + if rsa1024min.Value() == "0" { + rsa1024min.IncNonDefault() + return nil } + return fmt.Errorf("crypto/rsa: %d-bit keys are insecure (see https://go.dev/pkg/crypto/rsa#hdr-Minimum_key_size)", size) +} - // Check that de ≡ 1 mod p-1, for each prime. - // This implies that e is coprime to each p-1 as e has a multiplicative - // inverse. Therefore e is coprime to lcm(p-1,q-1,r-1,...) = - // exponent(ℤ/nℤ). It also implies that a^de ≡ a mod p as a^(p-1) ≡ 1 - // mod p. Thus a^de ≡ a mod n for all a coprime to n, as required. - congruence := new(big.Int) - de := new(big.Int).SetInt64(int64(priv.E)) - de.Mul(de, priv.D) - for _, prime := range priv.Primes { - pminus1 := new(big.Int).Sub(prime, bigOne) - congruence.Mod(de, pminus1) - if congruence.Cmp(bigOne) != 0 { - return errors.New("crypto/rsa: invalid exponents") - } +func checkPublicKeySize(k *PublicKey) error { + if k.N == nil { + return errors.New("crypto/rsa: missing public modulus") } - return nil + return checkKeySize(k.N.BitLen()) } // GenerateKey generates a random RSA private key of the given bit size. // +// If bits is less than 1024, [GenerateKey] returns an error. See the "[Minimum +// key size]" section for further details. +// // Most applications should use [crypto/rand.Reader] as rand. Note that the // returned key does not depend deterministically on the bytes read from rand, // and may change between calls and/or between versions. -func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { - return GenerateMultiPrimeKey(random, 2, bits) -} - -// GenerateMultiPrimeKey generates a multi-prime RSA keypair of the given bit -// size and the given random source. -// -// Table 1 in "[On the Security of Multi-prime RSA]" suggests maximum numbers of -// primes for a given bit size. -// -// Although the public keys are compatible (actually, indistinguishable) from -// the 2-prime case, the private keys are not. Thus it may not be possible to -// export multi-prime private keys in certain formats or to subsequently import -// them into other code. -// -// This package does not implement CRT optimizations for multi-prime RSA, so the -// keys with more than two primes will have worse performance. // -// Deprecated: The use of this function with a number of primes different from -// two is not recommended for the above security, compatibility, and performance -// reasons. Use [GenerateKey] instead. -// -// [On the Security of Multi-prime RSA]: http://www.cacr.math.uwaterloo.ca/techreports/2006/cacr2006-16.pdf -func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey, error) { - randutil.MaybeReadByte(random) +// [Minimum key size]: #hdr-Minimum_key_size +func GenerateKey(random io.Reader, bits int) (*PrivateKey, error) { + if err := checkKeySize(bits); err != nil { + return nil, err + } - if boring.Enabled && random == boring.RandReader && nprimes == 2 && + if boring.Enabled && random == boring.RandReader && (bits == 2048 || bits == 3072 || bits == 4096) { bN, bE, bD, bP, bQ, bDp, bDq, bQinv, err := boring.GenerateKeyRSA(bits) if err != nil { @@ -318,19 +301,6 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey return nil, errors.New("crypto/rsa: generated key exponent too large") } - mn, err := bigmod.NewModulusFromBig(N) - if err != nil { - return nil, err - } - mp, err := bigmod.NewModulusFromBig(P) - if err != nil { - return nil, err - } - mq, err := bigmod.NewModulusFromBig(Q) - if err != nil { - return nil, err - } - key := &PrivateKey{ PublicKey: PublicKey{ N: N, @@ -343,14 +313,91 @@ func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey Dq: Dq, Qinv: Qinv, CRTValues: make([]CRTValue, 0), // non-nil, to match Precompute - n: mn, - p: mp, - q: mq, }, } return key, nil } + if fips140only.Enabled && bits < 2048 { + return nil, errors.New("crypto/rsa: use of keys smaller than 2048 bits is not allowed in FIPS 140-only mode") + } + if fips140only.Enabled && bits%2 == 1 { + return nil, errors.New("crypto/rsa: use of keys with odd size is not allowed in FIPS 140-only mode") + } + if fips140only.Enabled && !fips140only.ApprovedRandomReader(random) { + return nil, errors.New("crypto/rsa: only crypto/rand.Reader is allowed in FIPS 140-only mode") + } + + k, err := rsa.GenerateKey(random, bits) + if bits < 256 && err != nil { + // Toy-sized keys have a non-negligible chance of hitting two hard + // failure cases: p == q and d <= 2^(nlen / 2). + // + // Since these are impossible to hit for real keys, we don't want to + // make the production code path more complex and harder to think about + // to handle them. + // + // Instead, just rerun the whole process a total of 8 times, which + // brings the chance of failure for 32-bit keys down to the same as for + // 256-bit keys. + for i := 1; i < 8 && err != nil; i++ { + k, err = rsa.GenerateKey(random, bits) + } + } + if err != nil { + return nil, err + } + N, e, d, p, q, dP, dQ, qInv := k.Export() + key := &PrivateKey{ + PublicKey: PublicKey{ + N: new(big.Int).SetBytes(N), + E: e, + }, + D: new(big.Int).SetBytes(d), + Primes: []*big.Int{ + new(big.Int).SetBytes(p), + new(big.Int).SetBytes(q), + }, + Precomputed: PrecomputedValues{ + fips: k, + Dp: new(big.Int).SetBytes(dP), + Dq: new(big.Int).SetBytes(dQ), + Qinv: new(big.Int).SetBytes(qInv), + CRTValues: make([]CRTValue, 0), // non-nil, to match Precompute + }, + } + return key, nil +} + +// GenerateMultiPrimeKey generates a multi-prime RSA keypair of the given bit +// size and the given random source. +// +// Table 1 in "[On the Security of Multi-prime RSA]" suggests maximum numbers of +// primes for a given bit size. +// +// Although the public keys are compatible (actually, indistinguishable) from +// the 2-prime case, the private keys are not. Thus it may not be possible to +// export multi-prime private keys in certain formats or to subsequently import +// them into other code. +// +// This package does not implement CRT optimizations for multi-prime RSA, so the +// keys with more than two primes will have worse performance. +// +// Deprecated: The use of this function with a number of primes different from +// two is not recommended for the above security, compatibility, and performance +// reasons. Use [GenerateKey] instead. +// +// [On the Security of Multi-prime RSA]: http://www.cacr.math.uwaterloo.ca/techreports/2006/cacr2006-16.pdf +func GenerateMultiPrimeKey(random io.Reader, nprimes int, bits int) (*PrivateKey, error) { + if nprimes == 2 { + return GenerateKey(random, bits) + } + if fips140only.Enabled { + return nil, errors.New("crypto/rsa: multi-prime RSA is not allowed in FIPS 140-only mode") + } + + randutil.MaybeReadByte(random) + priv := new(PrivateKey) priv.E = 65537 @@ -437,42 +484,11 @@ NextSetOfPrimes: } priv.Precompute() - return priv, nil -} - -// incCounter increments a four byte, big-endian counter. -func incCounter(c *[4]byte) { - if c[3]++; c[3] != 0 { - return - } - if c[2]++; c[2] != 0 { - return - } - if c[1]++; c[1] != 0 { - return + if err := priv.Validate(); err != nil { + return nil, err } - c[0]++ -} -// mgf1XOR XORs the bytes in out with a mask generated using the MGF1 function -// specified in PKCS #1 v2.1. -func mgf1XOR(out []byte, hash hash.Hash, seed []byte) { - var counter [4]byte - var digest []byte - - done := 0 - for done < len(out) { - hash.Write(seed) - hash.Write(counter[0:4]) - digest = hash.Sum(digest[:0]) - hash.Reset() - - for i := 0; i < len(digest) && done < len(out); i++ { - out[done] ^= digest[i] - done++ - } - incCounter(&counter) - } + return priv, nil } // ErrMessageTooLong is returned when attempting to encrypt or sign a message @@ -480,303 +496,148 @@ func mgf1XOR(out []byte, hash hash.Hash, seed []byte) { // be returned if the size of the salt is too large. var ErrMessageTooLong = errors.New("crypto/rsa: message too long for RSA key size") -func encrypt(pub *PublicKey, plaintext []byte) ([]byte, error) { - boring.Unreachable() +// ErrDecryption represents a failure to decrypt a message. +// It is deliberately vague to avoid adaptive attacks. +var ErrDecryption = errors.New("crypto/rsa: decryption error") - N, err := bigmod.NewModulusFromBig(pub.N) - if err != nil { - return nil, err +// ErrVerification represents a failure to verify a signature. +// It is deliberately vague to avoid adaptive attacks. +var ErrVerification = errors.New("crypto/rsa: verification error") + +// Precompute performs some calculations that speed up private key operations +// in the future. It is safe to run on non-validated private keys. +func (priv *PrivateKey) Precompute() { + if priv.Precomputed.fips != nil { + return } - m, err := bigmod.NewNat().SetBytes(plaintext, N) + + precomputed, err := priv.precompute() if err != nil { - return nil, err + // We don't have a way to report errors, so just leave the key + // unmodified. Validate will re-run precompute. + return } - e := uint(pub.E) - - return bigmod.NewNat().ExpShortVarTime(m, e, N).Bytes(N), nil + priv.Precomputed = precomputed } -// EncryptOAEP encrypts the given message with RSA-OAEP. -// -// OAEP is parameterised by a hash function that is used as a random oracle. -// Encryption and decryption of a given message must use the same hash function -// and sha256.New() is a reasonable choice. -// -// The random parameter is used as a source of entropy to ensure that -// encrypting the same message twice doesn't result in the same ciphertext. -// Most applications should use [crypto/rand.Reader] as random. -// -// The label parameter may contain arbitrary data that will not be encrypted, -// but which gives important context to the message. For example, if a given -// public key is used to encrypt two types of messages then distinct label -// values could be used to ensure that a ciphertext for one purpose cannot be -// used for another by an attacker. If not required it can be empty. -// -// The message must be no longer than the length of the public modulus minus -// twice the hash length, minus a further 2. -func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error) { - // Note that while we don't commit to deterministic execution with respect - // to the random stream, we also don't apply MaybeReadByte, so per Hyrum's - // Law it's probably relied upon by some. It's a tolerable promise because a - // well-specified number of random bytes is included in the ciphertext, in a - // well-specified way. - - if err := checkPub(pub); err != nil { - return nil, err +func (priv *PrivateKey) precompute() (PrecomputedValues, error) { + var precomputed PrecomputedValues + + if priv.N == nil { + return precomputed, errors.New("crypto/rsa: missing public modulus") + } + if priv.D == nil { + return precomputed, errors.New("crypto/rsa: missing private exponent") } - hash.Reset() - k := pub.Size() - if len(msg) > k-2*hash.Size()-2 { - return nil, ErrMessageTooLong + if len(priv.Primes) != 2 { + return priv.precomputeLegacy() + } + if priv.Primes[0] == nil { + return precomputed, errors.New("crypto/rsa: prime P is nil") + } + if priv.Primes[1] == nil { + return precomputed, errors.New("crypto/rsa: prime Q is nil") } - if boring.Enabled && random == boring.RandReader { - bkey, err := boringPublicKey(pub) + // If the CRT values are already set, use them. + if priv.Precomputed.Dp != nil && priv.Precomputed.Dq != nil && priv.Precomputed.Qinv != nil { + k, err := rsa.NewPrivateKeyWithPrecomputation(priv.N.Bytes(), priv.E, priv.D.Bytes(), + priv.Primes[0].Bytes(), priv.Primes[1].Bytes(), + priv.Precomputed.Dp.Bytes(), priv.Precomputed.Dq.Bytes(), priv.Precomputed.Qinv.Bytes()) if err != nil { - return nil, err + return precomputed, err } - return boring.EncryptRSAOAEP(hash, hash, bkey, msg, label) + precomputed = priv.Precomputed + precomputed.fips = k + precomputed.CRTValues = make([]CRTValue, 0) + return precomputed, nil } - boring.UnreachableExceptTests() - hash.Write(label) - lHash := hash.Sum(nil) - hash.Reset() + k, err := rsa.NewPrivateKey(priv.N.Bytes(), priv.E, priv.D.Bytes(), + priv.Primes[0].Bytes(), priv.Primes[1].Bytes()) + if err != nil { + return precomputed, err + } - em := make([]byte, k) - seed := em[1 : 1+hash.Size()] - db := em[1+hash.Size():] + precomputed.fips = k + _, _, _, _, _, dP, dQ, qInv := k.Export() + precomputed.Dp = new(big.Int).SetBytes(dP) + precomputed.Dq = new(big.Int).SetBytes(dQ) + precomputed.Qinv = new(big.Int).SetBytes(qInv) + precomputed.CRTValues = make([]CRTValue, 0) + return precomputed, nil +} - copy(db[0:hash.Size()], lHash) - db[len(db)-len(msg)-1] = 1 - copy(db[len(db)-len(msg):], msg) +func (priv *PrivateKey) precomputeLegacy() (PrecomputedValues, error) { + var precomputed PrecomputedValues - _, err := io.ReadFull(random, seed) + k, err := rsa.NewPrivateKeyWithoutCRT(priv.N.Bytes(), priv.E, priv.D.Bytes()) if err != nil { - return nil, err + return precomputed, err } + precomputed.fips = k - mgf1XOR(db, hash, seed) - mgf1XOR(seed, hash, db) - - if boring.Enabled { - var bkey *boring.PublicKeyRSA - bkey, err = boringPublicKey(pub) - if err != nil { - return nil, err - } - return boring.EncryptRSANoPadding(bkey, em) + if len(priv.Primes) < 2 { + return precomputed, nil } - return encrypt(pub, em) -} - -// ErrDecryption represents a failure to decrypt a message. -// It is deliberately vague to avoid adaptive attacks. -var ErrDecryption = errors.New("crypto/rsa: decryption error") - -// ErrVerification represents a failure to verify a signature. -// It is deliberately vague to avoid adaptive attacks. -var ErrVerification = errors.New("crypto/rsa: verification error") - -// Precompute performs some calculations that speed up private key operations -// in the future. -func (priv *PrivateKey) Precompute() { - if priv.Precomputed.n == nil && len(priv.Primes) == 2 { - // Precomputed values _should_ always be valid, but if they aren't - // just return. We could also panic. - var err error - priv.Precomputed.n, err = bigmod.NewModulusFromBig(priv.N) - if err != nil { - return - } - priv.Precomputed.p, err = bigmod.NewModulusFromBig(priv.Primes[0]) - if err != nil { - // Unset previous values, so we either have everything or nothing - priv.Precomputed.n = nil - return + // Ensure the Mod and ModInverse calls below don't panic. + for _, prime := range priv.Primes { + if prime == nil { + return precomputed, errors.New("crypto/rsa: prime factor is nil") } - priv.Precomputed.q, err = bigmod.NewModulusFromBig(priv.Primes[1]) - if err != nil { - // Unset previous values, so we either have everything or nothing - priv.Precomputed.n, priv.Precomputed.p = nil, nil - return + if prime.Cmp(bigOne) <= 0 { + return precomputed, errors.New("crypto/rsa: prime factor is <= 1") } } - // Fill in the backwards-compatibility *big.Int values. - if priv.Precomputed.Dp != nil { - return - } - - priv.Precomputed.Dp = new(big.Int).Sub(priv.Primes[0], bigOne) - priv.Precomputed.Dp.Mod(priv.D, priv.Precomputed.Dp) + precomputed.Dp = new(big.Int).Sub(priv.Primes[0], bigOne) + precomputed.Dp.Mod(priv.D, precomputed.Dp) - priv.Precomputed.Dq = new(big.Int).Sub(priv.Primes[1], bigOne) - priv.Precomputed.Dq.Mod(priv.D, priv.Precomputed.Dq) + precomputed.Dq = new(big.Int).Sub(priv.Primes[1], bigOne) + precomputed.Dq.Mod(priv.D, precomputed.Dq) - priv.Precomputed.Qinv = new(big.Int).ModInverse(priv.Primes[1], priv.Primes[0]) + precomputed.Qinv = new(big.Int).ModInverse(priv.Primes[1], priv.Primes[0]) + if precomputed.Qinv == nil { + return precomputed, errors.New("crypto/rsa: prime factors are not relatively prime") + } r := new(big.Int).Mul(priv.Primes[0], priv.Primes[1]) - priv.Precomputed.CRTValues = make([]CRTValue, len(priv.Primes)-2) + precomputed.CRTValues = make([]CRTValue, len(priv.Primes)-2) for i := 2; i < len(priv.Primes); i++ { prime := priv.Primes[i] - values := &priv.Precomputed.CRTValues[i-2] + values := &precomputed.CRTValues[i-2] values.Exp = new(big.Int).Sub(prime, bigOne) values.Exp.Mod(priv.D, values.Exp) values.R = new(big.Int).Set(r) values.Coeff = new(big.Int).ModInverse(r, prime) - - r.Mul(r, prime) - } -} - -const withCheck = true -const noCheck = false - -// decrypt performs an RSA decryption of ciphertext into out. If check is true, -// m^e is calculated and compared with ciphertext, in order to defend against -// errors in the CRT computation. -func decrypt(priv *PrivateKey, ciphertext []byte, check bool) ([]byte, error) { - if len(priv.Primes) <= 2 { - boring.Unreachable() - } - - var ( - err error - m, c *bigmod.Nat - N *bigmod.Modulus - t0 = bigmod.NewNat() - ) - if priv.Precomputed.n == nil { - N, err = bigmod.NewModulusFromBig(priv.N) - if err != nil { - return nil, ErrDecryption - } - c, err = bigmod.NewNat().SetBytes(ciphertext, N) - if err != nil { - return nil, ErrDecryption - } - m = bigmod.NewNat().Exp(c, priv.D.Bytes(), N) - } else { - N = priv.Precomputed.n - P, Q := priv.Precomputed.p, priv.Precomputed.q - Qinv, err := bigmod.NewNat().SetBytes(priv.Precomputed.Qinv.Bytes(), P) - if err != nil { - return nil, ErrDecryption - } - c, err = bigmod.NewNat().SetBytes(ciphertext, N) - if err != nil { - return nil, ErrDecryption + if values.Coeff == nil { + return precomputed, errors.New("crypto/rsa: prime factors are not relatively prime") } - // m = c ^ Dp mod p - m = bigmod.NewNat().Exp(t0.Mod(c, P), priv.Precomputed.Dp.Bytes(), P) - // m2 = c ^ Dq mod q - m2 := bigmod.NewNat().Exp(t0.Mod(c, Q), priv.Precomputed.Dq.Bytes(), Q) - // m = m - m2 mod p - m.Sub(t0.Mod(m2, P), P) - // m = m * Qinv mod p - m.Mul(Qinv, P) - // m = m * q mod N - m.ExpandFor(N).Mul(t0.Mod(Q.Nat(), N), N) - // m = m + m2 mod N - m.Add(m2.ExpandFor(N), N) - } - - if check { - c1 := bigmod.NewNat().ExpShortVarTime(m, uint(priv.E), N) - if c1.Equal(c) != 1 { - return nil, ErrDecryption - } + r.Mul(r, prime) } - return m.Bytes(N), nil + return precomputed, nil } -// DecryptOAEP decrypts ciphertext using RSA-OAEP. -// -// OAEP is parameterised by a hash function that is used as a random oracle. -// Encryption and decryption of a given message must use the same hash function -// and sha256.New() is a reasonable choice. -// -// The random parameter is legacy and ignored, and it can be nil. -// -// The label parameter must match the value given when encrypting. See -// [EncryptOAEP] for details. -func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { - return decryptOAEP(hash, hash, random, priv, ciphertext, label) -} - -func decryptOAEP(hash, mgfHash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error) { - if err := checkPub(&priv.PublicKey); err != nil { +func fipsPublicKey(pub *PublicKey) (*rsa.PublicKey, error) { + N, err := bigmod.NewModulus(pub.N.Bytes()) + if err != nil { return nil, err } - k := priv.Size() - if len(ciphertext) > k || - k < hash.Size()*2+2 { - return nil, ErrDecryption - } + return &rsa.PublicKey{N: N, E: pub.E}, nil +} - if boring.Enabled { - bkey, err := boringPrivateKey(priv) - if err != nil { - return nil, err - } - out, err := boring.DecryptRSAOAEP(hash, mgfHash, bkey, ciphertext, label) - if err != nil { - return nil, ErrDecryption - } - return out, nil +func fipsPrivateKey(priv *PrivateKey) (*rsa.PrivateKey, error) { + if priv.Precomputed.fips != nil { + return priv.Precomputed.fips, nil } - - em, err := decrypt(priv, ciphertext, noCheck) + precomputed, err := priv.precompute() if err != nil { return nil, err } - - hash.Write(label) - lHash := hash.Sum(nil) - hash.Reset() - - firstByteIsZero := subtle.ConstantTimeByteEq(em[0], 0) - - seed := em[1 : hash.Size()+1] - db := em[hash.Size()+1:] - - mgf1XOR(seed, mgfHash, db) - mgf1XOR(db, mgfHash, seed) - - lHash2 := db[0:hash.Size()] - - // We have to validate the plaintext in constant time in order to avoid - // attacks like: J. Manger. A Chosen Ciphertext Attack on RSA Optimal - // Asymmetric Encryption Padding (OAEP) as Standardized in PKCS #1 - // v2.0. In J. Kilian, editor, Advances in Cryptology. - lHash2Good := subtle.ConstantTimeCompare(lHash, lHash2) - - // The remainder of the plaintext must be zero or more 0x00, followed - // by 0x01, followed by the message. - // lookingForIndex: 1 iff we are still looking for the 0x01 - // index: the offset of the first 0x01 byte - // invalid: 1 iff we saw a non-zero byte before the 0x01. - var lookingForIndex, index, invalid int - lookingForIndex = 1 - rest := db[hash.Size():] - - for i := 0; i < len(rest); i++ { - equals0 := subtle.ConstantTimeByteEq(rest[i], 0) - equals1 := subtle.ConstantTimeByteEq(rest[i], 1) - index = subtle.ConstantTimeSelect(lookingForIndex&equals1, i, index) - lookingForIndex = subtle.ConstantTimeSelect(equals1, 0, lookingForIndex) - invalid = subtle.ConstantTimeSelect(lookingForIndex&^equals0, 1, invalid) - } - - if firstByteIsZero&lHash2Good&^invalid&^lookingForIndex != 1 { - return nil, ErrDecryption - } - - return rest[index+1:], nil + return precomputed.fips, nil } diff --git a/crypto/rsa/rsa_export_test.go b/crypto/rsa/rsa_export_test.go index 70406decf17..6b6afa822f6 100644 --- a/crypto/rsa/rsa_export_test.go +++ b/crypto/rsa/rsa_export_test.go @@ -5,6 +5,3 @@ package rsa var NonZeroRandomBytes = nonZeroRandomBytes -var EMSAPSSEncode = emsaPSSEncode -var EMSAPSSVerify = emsaPSSVerify -var InvalidSaltLenErr = invalidSaltLenErr diff --git a/crypto/rsa/rsa_test.go b/crypto/rsa/rsa_test.go index 7f6475684f7..85eb7210d6c 100644 --- a/crypto/rsa/rsa_test.go +++ b/crypto/rsa/rsa_test.go @@ -7,43 +7,58 @@ package rsa_test import ( "bufio" "bytes" + "crypto" + . "crypto/rsa" + "encoding/hex" "encoding/pem" "flag" "fmt" + "io" "math/big" + "os" "strings" "testing" "crypto/rand" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" - . "github.com/runZeroInc/excrypto/crypto/rsa" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" "github.com/runZeroInc/excrypto/crypto/sha1" "github.com/runZeroInc/excrypto/crypto/sha256" + "github.com/runZeroInc/excrypto/crypto/sha512" "github.com/runZeroInc/excrypto/crypto/x509" - "github.com/runZeroInc/excrypto/internal/testenv" ) func TestKeyGeneration(t *testing.T) { - for _, size := range []int{128, 1024, 2048, 3072} { - priv, err := GenerateKey(rand.Reader, size) - if err != nil { - t.Errorf("GenerateKey(%d): %v", size, err) - } - if bits := priv.N.BitLen(); bits != size { - t.Errorf("key too short (%d vs %d)", bits, size) - } - testKeyBasics(t, priv) - if testing.Short() { - break - } + sizes := []int{128, 512, 1024, 2048, 3072, 4096} + if testing.Short() { + sizes = sizes[:2] + } + for _, size := range sizes { + t.Run(fmt.Sprintf("%d", size), func(t *testing.T) { + if size < 1024 { + _, err := GenerateKey(rand.Reader, size) + if err == nil { + t.Errorf("GenerateKey(%d) succeeded without GODEBUG", size) + } + t.Setenv("GODEBUG", "rsa1024min=0") + } + priv, err := GenerateKey(rand.Reader, size) + if err != nil { + t.Errorf("GenerateKey(%d): %v", size, err) + } + if bits := priv.N.BitLen(); bits != size { + t.Errorf("key too short (%d vs %d)", bits, size) + } + testKeyBasics(t, priv) + }) } } func Test3PrimeKeyGeneration(t *testing.T) { - size := 768 + size := 1024 if testing.Short() { + t.Setenv("GODEBUG", "rsa1024min=0") size = 256 } @@ -55,8 +70,9 @@ func Test3PrimeKeyGeneration(t *testing.T) { } func Test4PrimeKeyGeneration(t *testing.T) { - size := 768 + size := 1024 if testing.Short() { + t.Setenv("GODEBUG", "rsa1024min=0") size = 256 } @@ -68,6 +84,7 @@ func Test4PrimeKeyGeneration(t *testing.T) { } func TestNPrimeKeyGeneration(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") primeSize := 64 maxN := 24 if testing.Short() { @@ -86,8 +103,9 @@ func TestNPrimeKeyGeneration(t *testing.T) { } func TestImpossibleKeyGeneration(t *testing.T) { - // This test ensures that trying to generate toy RSA keys doesn't enter - // an infinite loop. + // This test ensures that trying to generate or validate toy RSA keys + // doesn't enter an infinite loop or panic. + t.Setenv("GODEBUG", "rsa1024min=0") for i := 0; i < 32; i++ { GenerateKey(rand.Reader, i) GenerateMultiPrimeKey(rand.Reader, 3, i) @@ -96,7 +114,25 @@ func TestImpossibleKeyGeneration(t *testing.T) { } } +func TestTinyKeyGeneration(t *testing.T) { + // Toy-sized keys can randomly hit hard failures in GenerateKey. + if testing.Short() { + t.Skip("skipping in short mode") + } + t.Setenv("GODEBUG", "rsa1024min=0") + for range 10000 { + k, err := GenerateKey(rand.Reader, 32) + if err != nil { + t.Fatalf("GenerateKey(32): %v", err) + } + if err := k.Validate(); err != nil { + t.Fatalf("Validate(32): %v", err) + } + } +} + func TestGnuTLSKey(t *testing.T) { + t.Setenv("GODEBUG", "rsa1024min=0") // This is a key generated by `certtool --generate-privkey --bits 128`. // It's such that de ≢ 1 mod φ(n), but is congruent mod the order of // the group. @@ -134,10 +170,7 @@ func testKeyBasics(t *testing.T, priv *PrivateKey) { } func TestAllocations(t *testing.T) { - if boring.Enabled { - t.Skip("skipping allocations test with BoringCrypto") - } - testenv.SkipIfOptimizationOff(t) + cryptotest.SkipTestAllocations(t) m := []byte("Hello Gophers") c, err := EncryptPKCS1v15(rand.Reader, &test2048Key.PublicKey, m) @@ -161,11 +194,20 @@ func TestAllocations(t *testing.T) { var allFlag = flag.Bool("all", false, "test all key sizes up to 2048") func TestEverything(t *testing.T) { - min := 32 - max := 560 // any smaller than this and not all tests will run if testing.Short() { - min = max + // Skip key generation, but still test real sizes. + for _, key := range []*PrivateKey{test1024Key, test2048Key} { + t.Run(fmt.Sprintf("%d", key.N.BitLen()), func(t *testing.T) { + t.Parallel() + testEverything(t, key) + }) + } + return } + + t.Setenv("GODEBUG", "rsa1024min=0") + min := 32 + max := 560 // any smaller than this and not all tests will run if *allFlag { max = 2048 } @@ -175,7 +217,7 @@ func TestEverything(t *testing.T) { t.Parallel() priv, err := GenerateKey(rand.Reader, size) if err != nil { - t.Errorf("GenerateKey(%d): %v", size, err) + t.Fatalf("GenerateKey(%d): %v", size, err) } if bits := priv.N.BitLen(); bits != size { t.Errorf("key too short (%d vs %d)", bits, size) @@ -228,8 +270,14 @@ func testEverything(t *testing.T, priv *PrivateKey) { } } + const hashMsg = "crypto/rsa: input must be hashed message" + sig, err := SignPKCS1v15(nil, priv, crypto.SHA256, msg) + if err == nil || err.Error() != hashMsg { + t.Errorf("SignPKCS1v15 with bad hash: err = %q, want %q", err, hashMsg) + } + hash := sha256.Sum256(msg) - sig, err := SignPKCS1v15(nil, priv, crypto.SHA256, hash[:]) + sig, err = SignPKCS1v15(nil, priv, crypto.SHA256, hash[:]) if err == ErrMessageTooLong { t.Log("key too small for SignPKCS1v15") } else if err != nil { @@ -326,6 +374,61 @@ func testEverything(t *testing.T, priv *PrivateKey) { if err == nil { t.Errorf("DecryptPKCS1v15 accepted a long ciphertext") } + + der, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + t.Errorf("MarshalPKCS8PrivateKey: %v", err) + } + key, err := x509.ParsePKCS8PrivateKey(der) + if err != nil { + t.Errorf("ParsePKCS8PrivateKey: %v", err) + } + if !key.(*PrivateKey).Equal(priv) { + t.Errorf("private key mismatch") + } + + der, err = x509.MarshalPKIXPublicKey(&priv.PublicKey) + if err != nil { + t.Errorf("MarshalPKIXPublicKey: %v", err) + } + pub, err := x509.ParsePKIXPublicKey(der) + if err != nil { + t.Errorf("ParsePKIXPublicKey: %v", err) + } + if !pub.(*PublicKey).Equal(&priv.PublicKey) { + t.Errorf("public key mismatch") + } +} + +func TestKeyTooSmall(t *testing.T) { + checkErr := func(err error) { + t.Helper() + if err == nil { + t.Error("expected error") + } + if !strings.Contains(err.Error(), "insecure") { + t.Errorf("unexpected error: %v", err) + } + } + checkErr2 := func(_ []byte, err error) { + t.Helper() + checkErr(err) + } + + buf := make([]byte, 512/8) + checkErr2(test512Key.Sign(rand.Reader, buf, crypto.SHA512)) + checkErr2(test512Key.Sign(rand.Reader, buf, &PSSOptions{SaltLength: PSSSaltLengthEqualsHash})) + checkErr2(test512Key.Decrypt(rand.Reader, buf, &PKCS1v15DecryptOptions{})) + checkErr2(test512Key.Decrypt(rand.Reader, buf, &OAEPOptions{Hash: crypto.SHA512})) + checkErr(VerifyPKCS1v15(&test512Key.PublicKey, crypto.SHA512, buf, buf)) + checkErr(VerifyPSS(&test512Key.PublicKey, crypto.SHA512, buf, buf, &PSSOptions{SaltLength: PSSSaltLengthEqualsHash})) + checkErr2(SignPKCS1v15(rand.Reader, test512Key, crypto.SHA512, buf)) + checkErr2(SignPSS(rand.Reader, test512Key, crypto.SHA512, buf, &PSSOptions{SaltLength: PSSSaltLengthEqualsHash})) + checkErr2(EncryptPKCS1v15(rand.Reader, &test512Key.PublicKey, buf)) + checkErr2(EncryptOAEP(sha512.New(), rand.Reader, &test512Key.PublicKey, buf, nil)) + checkErr2(DecryptPKCS1v15(nil, test512Key, buf)) + checkErr2(DecryptOAEP(sha512.New(), nil, test512Key, buf, nil)) + checkErr(DecryptPKCS1v15SessionKey(nil, test512Key, buf, buf)) } func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } @@ -346,7 +449,46 @@ func parseKey(s string) *PrivateKey { return k } -var test2048Key = parseKey(testingKey(`-----BEGIN TESTING KEY----- +var rsaPrivateKey = test1024Key + +var test512Key = parseKey(testingKey(`-----BEGIN RSA TESTING KEY----- +MIIBOgIBAAJBALKZD0nEffqM1ACuak0bijtqE2QrI/KLADv7l3kK3ppMyCuLKoF0 +fd7Ai2KW5ToIwzFofvJcS/STa6HA5gQenRUCAwEAAQJBAIq9amn00aS0h/CrjXqu +/ThglAXJmZhOMPVn4eiu7/ROixi9sex436MaVeMqSNf7Ex9a8fRNfWss7Sqd9eWu +RTUCIQDasvGASLqmjeffBNLTXV2A5g4t+kLVCpsEIZAycV5GswIhANEPLmax0ME/ +EO+ZJ79TJKN5yiGBRsv5yvx5UiHxajEXAiAhAol5N4EUyq6I9w1rYdhPMGpLfk7A +IU2snfRJ6Nq2CQIgFrPsWRCkV+gOYcajD17rEqmuLrdIRexpg8N1DOSXoJ8CIGlS +tAboUGBxTDq3ZroNism3DaMIbKPyYrAqhKov1h5V +-----END RSA TESTING KEY-----`)) + +var test512KeyTwo = parseKey(testingKey(`-----BEGIN TESTING KEY----- +MIIBVgIBADANBgkqhkiG9w0BAQEFAASCAUAwggE8AgEAAkEA0wLCoguSfgskR8tY +Fh2AzXQzBpSEmPucxtVe93HzPdQpxvtSTvZe5kIsdvPc7QZ0dCc/qbnUBRbuGIAl +Ir0c9QIDAQABAkAzul+AXhnhcFXKi9ziPwVOWIgRuuLupe//BluriXG53BEBSVrV +Hr7qFqwnSLSLroMzqhZwoqyRgjsLYyGEHDGBAiEA8T0sDPuht3w2Qv61IAvBwjLH +H4HXjRUEWYRn1XjHqAUCIQDf7BYlANRqFfvg1YK3VCM4YyK2mH1UivDi8wdPlJRk +MQIhAMp5i2WCNeNpD6n/WkqBU6kJMXPSaPZy82mm5feYHgt5AiEAkg/QnhB9fjma +1BzRqD4Uv0pDMXIkhooe+Rrn0OwtI3ECIQDP6nxML3JOjbAS7ydFBv176uVsMJib +r4PZozCXKuuGNg== +-----END PRIVATE KEY-----`)) + +var test1024Key = parseKey(testingKey(`-----BEGIN RSA TESTING KEY----- +MIICXQIBAAKBgQCw0YNSqI9T1VFvRsIOejZ9feiKz1SgGfbe9Xq5tEzt2yJCsbyg ++xtcuCswNhdqY5A1ZN7G60HbL4/Hh/TlLhFJ4zNHVylz9mDDx3yp4IIcK2lb566d +fTD0B5EQ9Iqub4twLUdLKQCBfyhmJJvsEqKxm4J4QWgI+Brh/Pm3d4piPwIDAQAB +AoGASC6fj6TkLfMNdYHLQqG9kOlPfys4fstarpZD7X+fUBJ/H/7y5DzeZLGCYAIU ++QeAHWv6TfZIQjReW7Qy00RFJdgwFlTFRCsKXhG5x+IB+jL0Grr08KbgPPDgy4Jm +xirRHZVtU8lGbkiZX+omDIU28EHLNWL6rFEcTWao/tERspECQQDp2G5Nw0qYWn7H +Wm9Up1zkUTnkUkCzhqtxHbeRvNmHGKE7ryGMJEk2RmgHVstQpsvuFY4lIUSZEjAc +DUFJERhFAkEAwZH6O1ULORp8sHKDdidyleYcZU8L7y9Y3OXJYqELfddfBgFUZeVQ +duRmJj7ryu0g0uurOTE+i8VnMg/ostxiswJBAOc64Dd8uLJWKa6uug+XPr91oi0n +OFtM+xHrNK2jc+WmcSg3UJDnAI3uqMc5B+pERLq0Dc6hStehqHjUko3RnZECQEGZ +eRYWciE+Cre5dzfZkomeXE0xBrhecV0bOq6EKWLSVE+yr6mAl05ThRK9DCfPSOpy +F6rgN3QiyCA9J/1FluUCQQC5nX+PTU1FXx+6Ri2ZCi6EjEKMHr7gHcABhMinZYOt +N59pra9UdVQw9jxCU9G7eMyb0jJkNACAuEwakX3gi27b +-----END RSA TESTING KEY-----`)) + +var test2048KeyPEM = testingKey(`-----BEGIN TESTING KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDNoyFUYeDuqw+k iyv47iBy/udbWmQdpbUZ8JobHv8uQrvL7sQN6l83teHgNJsXqtiLF3MC+K+XI6Dq hxUWfQwLip8WEnv7Jx/+53S8yp/CS4Jw86Q1bQHbZjFDpcoqSuwAxlegw18HNZCY @@ -373,7 +515,9 @@ mCSL4FGK02ImUNDsd0RVVFw51DRId4rmsuJYMK9NAoGAKlYdc4784ixTD2ZICIOC ZWPxPAyQUEA7EkuUhAX1bVNG6UJTYA8kmGcUCG4jPTgWzi00IyUUr8jK7efyU/zs qiJuVs1bia+flYIQpysMl1VzZh8gW1nkB4SVPm5l2wBvVJDIr9Mc6rueC/oVNkh2 fLVGuFoTVIu2bF0cWAjNNMg= ------END TESTING KEY-----`)) +-----END TESTING KEY-----`) + +var test2048Key = parseKey(test2048KeyPEM) var test3072Key = parseKey(testingKey(`-----BEGIN TESTING KEY----- MIIG/gIBADANBgkqhkiG9w0BAQEFAASCBugwggbkAgEAAoIBgQDJrvevql7G07LM @@ -556,19 +700,48 @@ func BenchmarkEncryptOAEP(b *testing.B) { } func BenchmarkSignPKCS1v15(b *testing.B) { - b.Run("2048", func(b *testing.B) { - hashed := sha256.Sum256([]byte("testing")) + b.Run("2048", func(b *testing.B) { benchmarkSignPKCS1v15(b, test2048Key) }) + b.Run("2048/noprecomp/OnlyD", func(b *testing.B) { + benchmarkSignPKCS1v15(b, &PrivateKey{ + PublicKey: test2048Key.PublicKey, + D: test2048Key.D, + }) + }) + b.Run("2048/noprecomp/Primes", func(b *testing.B) { + benchmarkSignPKCS1v15(b, &PrivateKey{ + PublicKey: test2048Key.PublicKey, + D: test2048Key.D, + Primes: test2048Key.Primes, + }) + }) + // This is different from "2048" because it's only the public precomputed + // values, and not the crypto/internal/fips140/rsa.PrivateKey. + b.Run("2048/noprecomp/AllValues", func(b *testing.B) { + benchmarkSignPKCS1v15(b, &PrivateKey{ + PublicKey: test2048Key.PublicKey, + D: test2048Key.D, + Primes: test2048Key.Primes, + Precomputed: PrecomputedValues{ + Dp: test2048Key.Precomputed.Dp, + Dq: test2048Key.Precomputed.Dq, + Qinv: test2048Key.Precomputed.Qinv, + }, + }) + }) +} - var sink byte - b.ResetTimer() - for i := 0; i < b.N; i++ { - s, err := SignPKCS1v15(rand.Reader, test2048Key, crypto.SHA256, hashed[:]) - if err != nil { - b.Fatal(err) - } - sink ^= s[0] +func benchmarkSignPKCS1v15(b *testing.B, k *PrivateKey) { + hashed := sha256.Sum256([]byte("testing")) + + var sink byte + b.ResetTimer() + for i := 0; i < b.N; i++ { + s, err := SignPKCS1v15(rand.Reader, k, crypto.SHA256, hashed[:]) + if err != nil { + b.Fatal(err) } - }) + sink ^= s[0] + } } func BenchmarkVerifyPKCS1v15(b *testing.B) { @@ -623,6 +796,70 @@ func BenchmarkVerifyPSS(b *testing.B) { }) } +func BenchmarkParsePKCS8PrivateKey(b *testing.B) { + b.Run("2048", func(b *testing.B) { + p, _ := pem.Decode([]byte(test2048KeyPEM)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := x509.ParsePKCS8PrivateKey(p.Bytes); err != nil { + b.Fatal(err) + } + } + }) +} + +func BenchmarkGenerateKey(b *testing.B) { + b.Run("2048", func(b *testing.B) { + primes, err := os.ReadFile("testdata/keygen2048.txt") + if err != nil { + b.Fatal(err) + } + for b.Loop() { + r := &testPrimeReader{primes: string(primes)} + if _, err := GenerateKey(r, 2048); err != nil { + b.Fatal(err) + } + } + }) +} + +// testPrimeReader feeds prime candidates from a text file, +// one per line in hex, to GenerateKey. +type testPrimeReader struct { + primes string +} + +func (r *testPrimeReader) Read(p []byte) (n int, err error) { + // Neutralize randutil.MaybeReadByte. + // + // DO NOT COPY this. We *will* break you. We can do this because we're + // in the standard library, and can update this along with the + // GenerateKey implementation if necessary. + // + // You have been warned. + if len(p) == 1 { + return 1, nil + } + + var line string + for line == "" || line[0] == '#' { + var ok bool + line, r.primes, ok = strings.Cut(r.primes, "\n") + if !ok { + return 0, io.EOF + } + } + b, err := hex.DecodeString(line) + if err != nil { + return 0, err + } + if len(p) != len(b) { + return 0, fmt.Errorf("unexpected read length: %d", len(p)) + } + copy(p, b) + return len(p), nil +} + type testEncryptOAEPMessage struct { in []byte seed []byte @@ -892,3 +1129,21 @@ var testEncryptOAEPData = []testEncryptOAEPStruct{ }, }, } + +func TestPSmallerThanQ(t *testing.T) { + // This key has a 256-bit P and a 257-bit Q. + k := parseKey(testingKey(`-----BEGIN RSA TESTING KEY----- +MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu +KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm +o3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k +TQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7 +9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy +v/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs +/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00 +-----END RSA TESTING KEY-----`)) + t.Setenv("GODEBUG", "rsa1024min=0") + if boring.Enabled { + t.Skip("BoringCrypto mode returns the wrong error from SignPSS") + } + testEverything(t, k) +} diff --git a/crypto/rsa/testdata/keygen2048.txt b/crypto/rsa/testdata/keygen2048.txt new file mode 100644 index 00000000000..31854020d75 --- /dev/null +++ b/crypto/rsa/testdata/keygen2048.txt @@ -0,0 +1,719 @@ +# Prime candidates for RSA 2048 key generation, one per line, in big endian hex. +# This file contains two primes, one of which is at the end of the file. +# The totients of the primes are coprime with 65537. +# +# The number of composites, the distribution of their small divisors, and the +# number of trailing zeros are all chosen to be representative of the expected +# average key generation run, to provide a useful benchmark target. +# +# https://c2sp.org/CCTV/keygen#rsa-key-generation-benchmark +# https://words.filippo.io/rsa-keygen-bench/ + +c3280c027a24d6e2277d1227e4531c1ee765bda9d304bfe7a59519bf2686fd5435c5e5a1200b74cba47c49f444e89b0e0991d05824119ee9dee73e4bd6ce3e93a10604239677fd0b735438b2360d0da9e6e929e1c564df0f0287eb0804cb9dba824a53156098ce8c8efdb8197d12ac040baef4e90710670aa7a33d9a0cd0c62d +d658a5dea2c73517326aba6bddc24b677d47178dc77dd584160827702d203908c504bcb305309c6ced76c7bb2f891bd99119e95bd3d90b1c25c00808c10f94e5ee29aa84d066baf7a17250328286a74173cee433292afec2e3707790d1d590b0ae8d667614a8ec33ed8cd9344dae48dff98631c7e609b7c63c4bb1a5dcdcfdb5 +e5ce50dd929f658d7285fa47a175298c0657dfcc98620e92772c6ed6134ba24d7703ce31d6a2e20e82b95be2a72f3818f7f2bad597bed7a8b5c6c3cf408fb49aa70c003182583a95b9c615ffff31343060c98b1058423387f52e3a7992a29b5b494a49256a7dccb25874f9109d7124ddb2c3e6f4430243439b605ba67a1c2f8d +d01fc442afdb8bd631d93d4531bb16f4f9c3bc710f6546c248f809987cb7878e8ab9dec73b7d3d823563e4df53e41830b16d25eb96985f393c17906caa24d5a8a8123439ecea4d34d7b28bc4f16bcdcbf1f2d3a8d53b5c26cb2d217c04a12f3a436510031901bb1af9c5530402384ca801dff5ab6ea922be78857d0290ae11bd +c0047306b9a1a51226ae3d77981ef08f43783f5ae6534ef5783e0044518e53a7c778759f88378970c1ac71e3a57a6f0839e45036d3dd180a713d962f701b139af22c8fe07ae2105c1b56efe0cdb4ef36e94d9b398570db68efe873ccac75186357ffdb5f9e10470e62efd78b09ae76508ddef6c43faeff84cbf8ceef9138e035 +fdac5080d27a1a4213ee721dc344e16b5e8e6bb790e1bb8dc965a2e76b2311889fbaa9967b57d323aaf13e2960dedac6ac719a1a85a6817262703606b176bf3675922502040273ce256939000801cd1dd8888251c14b6223dd572d4c9efee5b522b2a06b0ccb34d302524eacf1f9183350c90adf8f5ccdaf708937f25677817d +c4876a812cbb5fdb31dd80e04656a0bd3e131a2d2eed4b29420f373e9e4ac61a0d0a023713c030bcef917c146647f97aa9937626f251f1fe2eb4106f3eeecb6b8c2346ddc9bd88740e2115b23b72d2d4a4ece93f96ec34b983c61cce8c33bb650107859190c2bfb24b326a534aa0f44fc2210601f39c82b344e184632d825315 +ecfd96ea0fdf3cd7072d6db2d9ae052f50a16b948838353f9d566c383309fcc1e43d3a0ed9cdb9c9d4b0d8552053a1c2561c7df175bae082128c3d380a60c8c578b51049928d0669bc2f20256b80b089f217f390c80252c49038bd1f2f89b265d49b935c5d621a7f9e6b41e8c7c5e454c5b539f90bfd2e5e09ff19ae280c92b5 +d934a84e3d322798b83c86259f93f06f9545a2d5cb911ce4ee19fa887e433baaf6cd3c66285626dc09669b4b2209926193af341683629bc8575f0d3cdff3b558566733aa3e256f4363f4eb4005caf7cef91d2653e20f7b2c35a74411dd73e8be0d1193f0bdfef5f39b680d069d64b3c49de3d31e91e19015b3e14e24884010d5 +dd1f0316593bb2e1df22f1fd618d56e7c4f28d6a85501ce14e262dd316acfdc67d17caaa3d7fbb730c885ef394050eb80ce597cde2510153e67d99ab8c463f6b8d3aa607e49d8303318f9c379df7f48454b9b1093ff29f0a6e78f586d00f26912b2f27db810b96c956586ebafcbee500a701f74cdc42201aabc2c5a1977671ed +e1eda3a00453828b2060ac037e3fdc546a019c30d7ef08c51ec722ef98313148b08583fc495746ab3e6a46efb4f4342013ef44da8821adcde73bfc873b7a521dee1e9cccf3f169e78fba89963a4561fc507c5333befa509cd910ed26e45c62a2a3e0df1d961042e6f38474a99c9c5b366d0a6d81c41774d1ffc4117cb9f000bd +f04402a78861bdb7100df31330193151688bd1c4ecbff887c1e484d67ec705cc62013d49f1b92fef400d5530202dcb4145828a9f657d2a1c454dafcb3b36a1664027ff703d78fbf4712fc6248308b56ca80bd10b19263df5194a74278269b61f5199390d4f77a9934cb88272681a77682dca4af9cdbcf376ab7660b9f3e6007d +faba6e112b7f349d92bba5f80de2437ad692922a33efed347579eb7be667b1df09a2dcdfac534fee0fbd4fef0838a74006dbcba62b8ce6cc0e6d1e1035effa096085716e4761083728f923525c9ac4339d15cbce8164d2abacaf1e134802e28733d1f7e7327f7430f4386102915f02b9c435b7a4f8a0433062973356490c999d +cd1d62358df14d823c9aaef80de713db2bff6dc3f15da163f981601b2096cbbc04ee12767937ff5f7218b758defc27912cbcfd8a7bdffa48e1c5d4accd21ded2cbb99f053103555cd78e9cbe0ce6e89db7fea4718b326f266c62ca997c1102ad9c04a3296405d2f9b8ed4a0e799e4818eba66247c25da68955f03f076dd7f735 +e4ce1badc198ea490b37dc7c04e6748904868129bb8cd4a7638f5cd9b7cc5e7f6f92639faa0bd755c6b84bb8c8361c5869b248ec443b24a81e5a9abf94309653f9777dca00aed9fcecc21dad79f9aa5b1a720d3800728a452d5a4fd4dcf156c20fcbba7e0a10c5c3cc63bd6e24fa63b248ed1a9b24205a801be3c1cb74be2bed +e6212b34639e14987d4820c32930bb2a72ec98670dd8269ffb85583f86be8152b6421798e6f11f64c410966f5fc85074b932944675c1d2e67ef1e831f15cec8dd2b8f7635e84c1624cc830fcf3ce1df011dcf133bb636e2a5c92789bf6e824a0a84c9311d0903af0ccc2ee417400538b745190546f97abb03461efcd924af65d +e64458a6209dfa4288e20607c5efa3ef0023005dd04286bd9d97e835e6316c8b76a6cd471d74d30fae6f84a0605135d85d5b626a9bda82ff24c1245428be2ab06ed0fe994f6bd4189e5620d88dfafb454dcee861b2cdf7362bbc4d2adafd606c17088f190e6bd14d57b51fcb1068b13cef8c454e138694f06138dc7b082dcdad +c0896f1090bcc0994a8888a71d7aa6a484d4a02312a2fffa805bce038a98064f054e097488c9e2e3f8daed458cca270761d07daeab428591d6bc30debf0548949ab2c89b2a4950241f78798100efe858b7667842410091764b6f62061c22e1b2f923de098547632f81525ed7696808097d20c7703b6ae01e11c094aee01a39cd +fe882662e021d8b4329b80e6caacc66174c483c453881389de36331a81b229c8404b4916a8103139fb125289a3746e3ef11370b6453b170635b727e88f3ba7ff760db3d69b87a6613f41a6a0d2af65326b109d6b4fb940b3dd20f1920bf40b88bd6027a11e9bd7e6e1508ca1c45d5011e084085aba7898c439aa9fff2466cf75 +e87cb70f7bba008257d76ae1b6629a6a21d12158eef819d8422a8b4f18830d426d784ee64d204fbbe3d21fc6bcfcb946a08b72e92132563de4f52c7e6682785408b0f612d7a461f413b68a186c637872a6207aaeb3774d302053375b1ea36bc63a968eccbb6034d408d4fd82c93810bd50990d28b5ca037fb541cc0402770a65 +c59ce336ad22514ff1bfcb64706d7812f8fb592fada9adac89b7b4bc74cc100bb7d41f6448a4fe8e8c54ad3eb9f643ff62240f1f24e4f7869c4de12cc7530f8856f5abbbc098a09a0b6f98dbf008ee586eed521b608302c49ff5450117babdcc89c1e1868503557696b7ea13ac4587d1535982cd0d88aac6161bbd3338877785 +d8f6e37189f694e33286bf6489fa969c0b82e21fa3a7fcdd142070bcc05a43b89b55163db9077a3576cb8917c30a3a66a04262e6d065cd5e74a6064de059c502c768edc7e33647f66ab6fd024d3c900b552dfb6c2d6aa21b6c1b569ae31bd75b2bc4d968ae9c49009c770f57d121a9c3ff0ed376f424891e734456bf605a943d +dd66fb98c6b659ed158375316e61785260961deda5c7cd3a620c21da10382f4aa97d02c1107bfa0589fe6362bb883f48dd5ece326f3c7cbc28d91ab7d1fa8ea1a0df6704bf3dfe9b1f053e4e307d5bed6bb7576254a8188f1d60d91963dcc2db5f7723ebfba4adcef5c5de4e56484306da72669305e167fd2224e678edb0c5a5 +c5a1245c78e99291db45c225773d2141b204c9c8627dd8120c4cc331a67c0b2c118e20dd7805436b4f05b36ad6b1d20b128286976e8f61772f2635ff88fb1c1f6ac2f390175eb9f4f556babe6aa1b2be9683ed442261a79789b10c5ab0f91eef7cc7d6b99d0fccf0d8a9780e540a3ece093ce6dcbfe8e62bce995c23ff201c65 +cb8ca7cd1d043349c6b244ea7f7766f717daf97fe5db079c9f94c0a30ee9b7fd6999728fa22802ac02c7e3228b53030956d3a7c6c148620461adc434388eb9b90ddc84bfd6032069a425521548c2cb78b4683c80d2c48c6df5e831fc23731957d490193f15607c396f62ad650c4266b8bd46b0127001b7d36b97e3b0f76ad735 +e89e37f2ae5007402036195b40ebd882f60756b616a827a3267a701cb12a7000723ba42eda4619989a8d080a444e5a8a8d4fd1a635279297f420e20d9e2587391613c9c35c0e4f6135f5c3abed32fe28783e05a2e0253e7bc351e20c4ddb399aa24ebd971fcf9db2263b0d01b628e87650d14cafe341f881b010089052bde375 +ecc17a8b65380f0203aad09dc6fb76d31fbf9d75a2e342a6b8bbd80585f649d1c1fb6ab5db10e2c15353c9baf32730ea797c9743a8e64e5ec31dfd776b0a25b2b80b68ecd612211b90233f2662381eba397fe5b2bd2cf330c1c7a3abaeca21a13ee088a8e24175022df26221de4cbe1357ae6cb0c93e973779c5b23c8707759d +cc377422aa2d5b267df8adeb9e774012bd8ab7e86855deb61466882d7ee02a749426d242d3329ed6a0b98ce56caff22b2a2ec5c1d7565bc7ac1062b3c9bb887184d690a4c39987e706c9b0360a9195bae763ecd9cb4863b7020972aea212df48374bac14101073af8169f8113debe9480af26dbe534ff7ebc078a84fea0452dd +f6b8e7c33db4bf1106731a54905cc34ebb8235ef9c8196ae9a43afc58f9fc478cf45924130526d2b3721cd9e5397665159d9e978676ac5663838bee9db236e389ff21786737b78abb5c34dd44f878a6a8f38b4f9017a8429e0ff7efc7e95783d532681bbb652222e604492e5e9cae70acff964a27274e30f3544758a60603c85 +f45323a64eb82a494ddf732502daf879938c2000b2f68690b013cb3a40d7459d850c41c829fb0d830a8a8ce53e43040c0e2de84e36c8adcf8b2935fc4d603d5065b7530baa21f3fc8566d13f6840dc0000e0531819c833ddb9db52257b1b18d815adf7f2cc1a74a8a2417db14f3578582987324ea66a85d6877e2803df34976d +c4b49ac4794fcad37ffeddf43b27b0f95d504ebd8faec1640fb3d1ec3ecf0d5f433e53f62b436a17436104fd1368903e38a10e173267b2105bfe5f64be5c275f3e1574cd2bc46435b004c158df4eb0ea47d3244a16e2d4eb49b68f06212c3753ba6acb74e59ad0154509ad46491b167f68c118f2ab8ea351f22cf23946b98545 +d9966e5ef2c98ed3ab7665493cb3b29e0b55e4f3593218417dc012d0cb6293a3ec9440f2959f3990afd04def8fd14e016d949c0ae116900fbd3e810addc5545db1285f73927c7828eac881a6bb5c25410ed7a9b28e9bc3a6e96f1a239dfbe2e9db90df2cc1bd832293f06e30b4cebf795945b717ec0d273364fd603a2db06b5d +e0fcd8c3c66391647f3cad90bc667361230cd0907a4f95d63b85dd7ce259952784a2facfd8a707dd58c5a66f1f3f238fd55f6c39a615547448d01ed27c046357edf1c7c8efb17e529a9a28b63e83aa0d203cc687cbfe19201388ca4edfc302fa04e11dca78907ee5401bca9b9ad1650ae8dcd63921aacd3afafe39cc9fc0e18d +c2e60320538adaf01aad0431695c391db850f09bdcd1d7711224ff6117e966c3ff1df74fe32210f805dfe5b9ede44b3f5b4398ba09f41712a0acfd17d3f29d901531870079a2106331127a4ee083b348ae34eb10be594f24f320315535cdaaf40ae6c0a3ed762e5f4f77aa6ccd01a1a8a471e2790210485bfb2179f1b214157d +cf2bea1f18b8b7c173139634d3a75be5b16cb92c08fd01189a75d214a0467380b64eaadc717871926d76a3dbf60c488cb1555eccdf749bb161c6f55bf68c6bce9e89ca4d4165cfeea41e5d7ca0bec8b5e32fe8a4f433a0b94e34949dd60f3a73e108ab3ed1cca9f1bca7a28f111ed8ca5b88253df61d7921d1e53de8839cd4d5 +d846b3f003220a8c32229f05a7cbca5fa373ac6d5b6dc3cc2bca8f0c1ed73092087cff694c43ef25b00e16b3327127259e48c51ac271faf2c6c8d4745f07a08b4df84aeaa95d0a73ede4eb50264198625f39917ca61787e8441f579e75a370a11b51db89851783daf44cc88142af0c558f346ee033317235085a2cd185e0e5cd +c82518eb29d04d44fc4d8ed9cdf5252652db5214be69172f58ebba11ecf8ccbdc76e45054484b270b559aed27f66c97f105b997e83f969d2b8045fc5c4795a0f14aa2f26d66a376245ecc3ba39dfa105044754ca7ed6b96722963168f565f192e40fa1f0bd4c875d325394eb72c13f53af85814aed81db2ccdc76feb4bdfcacd +cd174cce3c2a5a2aa08ed861ad64fb0c6a03407ee93ed2343676f1cafc25193af66589d891a1aac648b4d309b047ff6b116dc7bd974acde138cb95c4d7ee95737aad0742aa8d1a9e1781b7cf2a6f09495eb22d2767412a6743f2d80dbe8ec2b07ff4ea528c13e66a6f90385975f8d441aba9c0db2085b7714e07636914878655 +fdd7dbf9cec219fc1c59527f14b95b1094107a0173afd4435582319719f86d300c59fe8e676e29c4b90953208683694ac537b1bada7696767e0fb69893c102da2ad292318a8c06623844d2baf98ce5cbb8f5f4aac2b8adf1ba6cfb0b0238816d76f48671a0bd1aa42ebddcb5ca36bdc6b163f2fdf808ab06ce8129c88a34a29d +fa040fe20e7648d5452eb22d0449d909131d8aca9afb85d969c029525f06d97c719b5427f767b0c64374a0e85059287903827bf981af82b073909ecc277085e1bd489d8416f5e78584565fac47a39551991fcd6850d3705948630b4777eab04ec4d8211bc174b4f52a02aaf3176817c461aa64c0cc6f8caa83c2dae9785413f5 +eda819394c12ddfad1c5195f590389019ccebdceb5d22bdf8f0b8039f339cfa920726ae970d02788062f05e3f94601f6cc73caa1385d12e26842e8658c4feb352a77fe30cbe16475919ed604306151ff9519500547f02c5baf6d0ffc28c145eb09b64b6513bcc006cffa11b1ce9550eaaca8b52abab2731f0a87c2cb3838bbfd +e0a102fad6c51e75f45f919ff5c994fbedbc2decb9be980fb643c5dc4e8d7715642daa68e907ef73d50f48d9b22f9dd269b587edb7daf5086bf9d524de5b0ced07ec6ddcdbb6a7b80a2811a041b43d80ef92d80d26f626712921b2f454b16f9ce469979f9020058df410a8c0fa58f79a1ab8909258d136ea7565cce3db5583b5 +e29cac64588f2d390d569480051aa315a28651b17b5debe2f11937c590f7a6acf553bfd700d56ca0e9557e0002f8078e5c459dcd68621a7684938324207e686d5641210d9c25030892671e28dddbbfb8a5592162a9988e6201916cde8d4c19469edcbf2d539c9d13225ed75642edaf5a5a41f70498f6ad7bcce75521c56ec055 +c8f655d328cd97dddb6eb73dae1c513547e1f9eb924c9d3ab85c12e93f55e7585bd78605483a941d93c951ecb7703f70acfd134898f2d8c3b2eb852694feb12a536b68b1b31df677bcbc1818d00cec3ec77c69dfba074b773458fb688d166147942c76d2e90fe3bf6b1c63062bfa85591216a3a9a1fa89762c1c9d0941c1c5f5 +e4e8eb15317afd379b4739eef0e964d365f45e52838aae4c04671bc51ab06aa583fe7efa805f3beba9bd6c94d9cf1c4816d1d30a6cd0603d8e9e08d367125e6d70b3d1f4bac36d2d81442136d06bdb68618a895ecf5a45d27c9bab3f5302bf93c770680fb74d79fc3ebb0cb23c70a2c798b30e4341bfeb0ae224d9adee129aad +e9b15d8bc80ca4b280c5ff6ee0c92de7efe5d70c356855de5e23fbc6d7c3fbb9ebb2018cc8d3c82ec24ea057005aeb8504d68579c13aaa90adcb5719b8cc633e19ff70ed00f5ea38230eee0fb6c0185ad5a946798b8477e5e0a40961891ca48da0e92db23ee17549212cb337094a9074759544226d88b6a4ad0795f97adad15d +d7a679cb982749b84280e8e68593a578b1df3c3f3cea32c5f85cb8ad9e916a2e46804709b7d13ba3fedafcd5363eed258513077f8618c187a5ead4d80e365afd07ca9b94790e9ff312b7fbfa363571cbed16f7ecbc4958c56d8d2834a80841d55d200fbdd68d6f2edfd53ad9bd98b87467bb33f75fbf679805c691892180f6b5 +cc223cfc403e44f7b1b6b1b4923f5e601174288ebb449c591c7acd53629fd1d54e713a06f3f0c5bae6e0ec6c60f63a39628ad674607ebde06ded53451758cd3f46a2804625ccb5c6b4b8d3387863ff0f191f241ebc76ef2c002ae25415844b968887f5afc058a8a84a73c00708956bb108e22afc824481bc5f635ceb5b612df5 +c8cebb9900596d86f78b675e0b9b6e2efa078d5338e2e8c411b686eaf9fdf2009b0ddc390a54f9ed042363a55faaf61b1db8f81138701bec4ed242a96116111baa7a3a1a58f290ead2dfac414451ca6349b40a006cdaf8a7279413b774da1ec860e60d4cf8bd496c4daf6d67a071672b5813ed77e8cdc39463bbe9e833fc80b5 +ff877d7617c7692de75b95c6aa2b938e06917fe3964679d4d99909571a4aea438d5d9d749ea1cfe9edc3ea5ecaeb2cbf67df63e1d77f2e99841d52401fab7dc8dc8e7db0bb4bab56c2a738c0567fdd9e1d5cad05f143d9a208f1d3c261e2ea1fafb523e45b7ba00d3aa14f8d0ea6843f978289988325b778a74a503afc03dc75 +cdcbd7112b042912bd54805d11c334564f5b49da681c0300afbbf0657f50655b73775b8494160160c721758f848b8f5fdce80accbff0a819779a3ea8a081d2c8204e1ffe325bb2bb20646a3e99e0afb359fac49747724d30fb2861d4fa3576e157ea7ca9a5f8acf8d978a680cf20eac31c46ce888214306d279257c61372182d +cc8bfc877a0f189775fd8663bdda40e940dede0b46a834cb46e57e08ee7ee103275eda18677b67a7d15aefb25311e15bbb8320c72794426976af19f61709b596318d6a181afdbdce9f663e48da148de140c099b10cd8ad5c56befe57b612a3543ca80ae0b1828da26f30498d4b685d3a3bf0d9821aee4eeff19dcd095312861d +f57c84d6f79dabd40b86d9abb9b211355dee5a80404a0858450122d5c9b7f8850aa9399fbe271fd3d551b3fc5cb1f965e9f7ef04d725f8be2fa1473bd2925541b006934939bbb4cc6a061b12c9c4b2b6c3e72c05c9ec303ed089e71cc33b7954004cf5ea29c43dc207da28ce80feb805c804a371adde587970c4a6eb4c1b04c5 +d1be804b2c5442d6f2b8a6410d7b499e8a992a72fa4d532f9715f3c24abc6be301d5eeb7156d8b2010b0b61978ae0bc4205b3b9fff4d0bcc9842d43ee99b56d741d7c06b70b9928f9455802e6b714d01622ad14112f78ced761f10393fdba4e27626638c5b578da4db161f3e4d52edd897cf0dca2c83f1bca4422af1325fc82d +c3e6cc9373a12a14d3916be237b0b016fe7f240c9e7c631e50d2078d63552a898c781d5c1d23d4a4d4881ec211af661527fac39d5937e9f84bdb8182e69fce9a2a31618480bc538cc14e3d778cb0b7b3ba5ff2e4ca04282bda5df1333ed840e437b2ba8daa1679b4288f95ea19b5051ee77fc0dffbeed396df58796803b7eb95 +d36abf4625822935c2e14f94141ac1ab6a700f62010d9d5fb56ec9d582fe5e8ccbbace8496b16c215575df4692fdb5e5603cf0ca4680d9cc2e9440896221c86c0d3d7a96dba443d2eb651e95326d2663d8777c724c8b75be3e3b2319b23ee3561748560be33e8e4fafe01b40490732ff991a7c0f971672fa60f7adb59ecc3475 +ed3d82f546fb076502e0f5c4db6089fd203a6f8cfb7a3000e8c0d98ca15fdc784c4ffe0331bbf753663f7754c52886c6175465114a0d009829843629dea455d6ca5e940d88e1fbddab15ba018ae467a7bb2ac7fd9df12707611134a10e6b008f52bb0d2c3889a9b6dd8695fb7287bd2644d38094222df4b6e0a3799d95a32cd5 +e9faa255b3a1b7a6514cf14b7a62fa3ef57c1e7c62943aabc0ae690ed22c656d7477e17a42bd104e4243909576d03f93805c50873b8af7b6ad4a67350842c84ef4fff257d7c7b21dea8a907bf2be9bfa7261dcb50cb6d80ac1ee9e045027d0d6c4760541cbec65db502eac75e340555b2607779fbd4550218c0b8f8303692e5d +c3995bb359e84240b30d6927e9999a3c8c31577b42dbd527dea888466f209724156f57b3392566ea36066eb321924d8bdc5868c6a3fd546c413f31a3a85e8cde6977c4253af146c8acdafd179a5f0356d6219f3db83d565ce1c1286b5083b9226c982641ffbd5cf76738eac94b55ff7ff37817afc68761499762525ac97d8b05 +df98867af5ade57a001271bed2114f69b037ce084870e224d7b0b0e1af1f911bd91e50cfb6017706bf027c0031ebf403420690dd73ea6b862ad2340a1281063a52c647e793d852ee7d610de95d8eb418c9d091e8651f8a9f4c2becd70ee006abdd439e38a773373ed57210044143472bac3ab9d587b84ea9d166f61b9c91c1ad +d799dc56a7286200cfc1959e434161e67551f5b74a92e515466ab1332df4332b9e07af5803cfdd4aea7e6b76f24049cf402fabbdb4529c180d63b1fba3badb09b23eae4bc8784baf2487236b94684663e3bde39acd41c875ebdf1e5143a55fd15be61b2991bc24f9fed25d9779c75176f597755f6f0d2a95211f4756c1516fb5 +c6310962a28f5ef6bbae4ad219aaed7a5fd3021b64ef4f92b3c0ecd605cad3a9f97c8a8019724432318b1e65452a95b3e8a91d2fcd4a0a72fe084cee5c526b4ffb8ce02e2a394fc1dd4c4781aa06be5c2e55ed015c15c4f4b7c408af2ecabaa3b2909b427960571eb7cbbd9bb18a9751b6a92935d41e9f7176c97b64ed55354d +e5192872a3d1e009df8a07cfb29361e86c95183d9702d02d257bb5bbec7830e44f14ca100ba2a59fb3dec712ff6c274f592a45daac1e5c2b4e31b737ff1a57ac338e4e3fac6dac68671e3b7a6d5ee21cfd8604c04bed23a45c00ea0b7d0d1f122af3229ca667ee8808f86fe2e740c016729ca1082873f71ec822f145d9b76aa5 +e44d270ef565b5e7e27244463c6ff3bfaca373f5248970a917e3c76f80f274f328793273323bcc30f8779dfa3454787931c7f7a21ebf41b8532b8d7dc86c1f67edd627e7425aa3b86b3f865c848a8c5941ee098caa2f307436e886983552c6aef44c25d65186e1d0ca31d32a9f784a7e9d42d704c331cb4bf2815c084b356315 +e0edc4ddc48bfbd3d7509a7a9aac58c5538218b81f4c3eaab6fa616954c1d19458271a91925dd6a2da62cf33fc137687c3027fbd671d8c272d4d74e4afd343530e9a62a90de771d7340cb3acaf85444c3a861b7f73f0f1194d9f1bc72482b7926348d41ddc5d74286349cd7570fcff35521f0747ec8d3cee3d6ffafd61402c4d +cd5729475237d63b4dba8e30ebb93d68a0a867e2e4b7d624117adb20708c61102b0ddd4fc2c14001ec81fa9638f6bab94afd4ed51b3dc669ed3aa800b6b9c52a396f93a24b0715dbe17a1a8ed2d773c373570a415647794b28a9e4fd450a6a3f4e6af36dbf31201f194ccd6b09905982e01d5e56a743f4507645b84a28435b75 +f629fb24dc8ead8c4fd6b2a95c080d547d158c17f19ab53deb4eefe36857a5a1ade5fa87db6928a3b0b1cf9dad51819d93f4ae71d3881a26e54cbb10216a63687f2a6dee8ef58cf6d8602b8b394e6b9495874b8f92aa88e2edd842ca9174290c411f7e582ec2a691968ccec291d849d3f2d0834aae803b77839bcdf42f3bc97d +f8b09d6e5cee1df04800701b4583c8b963e34224d6c9566bfd83b11afe5d8b1b26255918ea42df2c389f5c355476ae841391282399de66a055d02bcf1cc71e1b22d1903e4dcbe0bdeb478a9949690cd694fb86ba6d7e83ed73e7a739486d5219cdb38a7781a5144476f01818db6abd1d73be57b0355231f24ea0154c20f5050d +f4163ed4e0ddf79fd97c43604c8e7b0ee09580b62166b6735c55ff4fd849c938716c0d129f5874dbe070724a0e4186a9a196677e1a7c52b9f34a117d51fb97281446ff7f3336b225ed287e4dd82b84c024e88047292d18e061dee2f7a9ec5a8f81a7717071a2f86bd53b80f68243bdf19e446232951265c065a60fdd487b7c3d +e1145d235521795fbcecf10eb4ca8a29cbeebad436d82540ab3eac21b46f46891772b08a7e69b1ea1c504047b0f584b47c600e18a6d7511715048c4e68a6c54e1af0ded19d84c83f84aadc1bf6ba030f2f80afe29adb094eb68a76e8cc9c8caa95e51c11fc590c90aa86945456b72324cf7035d13e4a1ea7f39cd265964e2ac5 +f0f245183e09ce283c77f3e17e0cb4ceebf99c23e4433a1241a68829f0709cd929068f8984b0bef578198ac06f3a3a753697695447d0a675d7a897ead5a99ddc0a444b4e6eb568932886ae1cc3a67647a248a3ac95e941517da3a20e9bb301102bf28d812434358035901eb99568b8982ef9a76cea58e874c686b9d1bb7e6cb5 +ea13168958667f214be4cf775d29789ca2851c20360aa05e76b5caa7d1ea4e6208ebaaef15dc9eb2b0cf54a9211bc0d615c72d251f361f861d0edfcdacc8c8a71cdcde40baa97d8febb5220fc253b53759b208a6905e00ee4970334270777dccfef096274fffe21699a37a187d4e771dc33d21c8a39e479b1ae4e820561aa5c5 +e666c27528aeebef0bcabad66873b3b44385315ec52177a68d6ad4fe86eb0307ec0aee39d7e671e8dfe432e31ae226cd99e3c3af73b9740de75674621abe63a510b264c2d6597e0963be2257130cdc4c510ac5001370b22d7edb060619dc525380b97787853a80f51492e7d75285a106d4b1f386593b4a8deb46baac39acb12d +dcb4379101c0d50c12a85f4efa5e8d5a3765de6f646060458db7469cd2fe0ebf620d38c64b0a6720080df8859ac74d3a5209e9a59e1c2437860b58a0ff8d88c279b164ff5f817612475cab07c966b275647cc3b765347c21b431a690765afa60f0158da8101214f28d034318a86bf9b592cef2abaae616017e2d5a046edb752d +ea5dab735eb313fae23a38a2aa217c62cd7d67a00a5a56fea73ad5ddd38c4668ad89a4cab1f94f3aeaa1ae7edb27518026e41111b513a74a4d88b3ee9df0bcd991e8adabe8ad233b0fc0bff82d0c87f010314e8a069af98b2d6aa922a8e7792e8a07fd61da0f8325fad04daedde33ad9558e193eeea4bf35a73ad21995f02c55 +c189d07afc0ff09ed9fa600f2b16bc6ade9a253420079dcdc5107946ab42e42a00b2728b4baa608f6ae6376b2c7eab0b15824efc1f9c5eb320f42c837524bc9979322fb29458d40dd65c5c1e629170827876dbf1f37e774d71225ac3a548f4e8126add70d76d6bc802f84484aa402a0dd379f7ea2c9a9d5488bf26c38b46267d +d74d8e36181e5ac15aa040c54bf8e64386aad135468efbcc1cb17c9db0da80a233ee78cbb83c8411c7632955e1e5c151b5631d1bc32683975c9db251e3c8eb3336e7bd888c9607795e040b1eeb85a59180371f7089f9f19e66073a32af1d9c23004cb656eb37955b4672ba85fd707934d0018202d02af9a292fdc537d9335add +eeedecf01b04430f99072f20643d57b88bc4b3d1ab69ae1db4b1867ba7ed56d50fd9ec9f06be6acc702b2ca193147343483c83f77894d98af2f36e6695505ac43c9cce9a6fecc94fcecaa89360bf407b28b6332db5dc5449ea9c68b227ff1abe73a612e4df4632a5d8dff54d721671ceb603ce0750e2ee8ffa2f7b82b3faa015 +db4ffa9ff038ad29458284e3137601bca912bc18344cba7385c3fc73d2d072fbd0942fcd9048321d37ad8c7c1cac6b431c87624639859685302dc830d671e7189a64d3fea5461dacc06ea617a037af08845f86b2907fa3bf7d2308806694c44ef29002a735b58802200391c3a2ab88ac9cf8ec437fb8068902160534788ad4d5 +f49318745e60ce9c01a772f6c051f1b9fd0c1218ef56a442ab235d6e7c6eddd21a564b22f1d587222bc4fd062e668d24b17802da698e2364720a7c1675fb7982972f7cb583eb4deac0baa2d74ebe3759517e9823bbe50bcc3bd94d7b175928f268d95e68e734c6a7e7c9e14b52afc3d2745b4af669f1bf436793cb08a1d5acad +d2fb4ef724dbf7fcddd0ba8686c697f5c64a869c70efc258cfcbecb68e0bd27c5269018b957604f4adc46124e28078a40b410d2b6fedae4f91892d172204327cb4e71f9c6f89b2d2b23f1c40b7389205471358e89db6851829f9b9fdf6f7736dad4ccec19830e38f4f5f4dc5ea9ddf3e26cce3cadc2cfb30752682e4a829ab75 +d5daa77d5cf5869c9c83f3048a11ad7cb39aa6ab44b353c9b7d5a4121aba52954fef42bbd60d475f361af181322f0919b9b1d82339dffb7eb74cd800e1ed8f71aacc66b6fa67613e82e40f522e662fc27604e129ca42c311e4994711e143459d21c89e07a7d14c02a85abbdbf2e32f9b559d9f24a27a98b2521d39acd6c690b5 +eafcb4e1299d16e398fa574e354d79cdb9625797688faefaac2d4a29b23ef552823c7e19d22fef28e2c1a24c6bfa374063ee99fb069b97f8bc472af6d69ced556f13efea086266bdff71c1b4da8e4b3f85e1a72fcf562a70f198c7608328c9277d85247311acc83a98866a521b7fb9aa0a7f4043109746274b624d8dbefd4745 +c7e8eb5f561d14193634b83e7944b8149e67f8bef30702c3a04acff906d4481134d4755317cfbdc39043dfa9d7b4c0f98e5b01a4f50ef49c7d17ad7c4164d1bd9a504de03bd419b33980cb0d0938f0e6aef5a7e69c1da4a31b4e8398eb00208f0175bd1c4f46e4496580dfc26bd9ed2715a9672e144e07e49f44e91f68a0b8bd +f08ec66071a5b495fa35b81ebc06eaf3fcfeacb3fd78b753e5de57cdeee8be92bb77db13d1b62490f29e6c1821e54cd30d83b54cd9e9eefc349c7cb4333b67e692fd459ed9f2591166693dda538bc39084ddf85014a38b4df0e88350ced2cceab85fc2be8b14888889f7c2d5e9683674f40610855d77ea09f89d705ca753702d +c2140c54f9dbf6e1f412a27fb4d6c11b58434a1ece009dd1dd820ef05f59dc0da64f05d96be8b9d2b27c6f9f5e49cbccba9b16fe1babf5eee02fe432ad86c04182371d3514a36f416197b9524203c2919b2c79ad55467ecaa56095e3048830b63ccec9efe120272b6ed318804cdad84a62f99bb63464981f552c655babf51435 +d4b69020109685911be78c377d4bd7fec5443a91ea13266a0c8286b0f1289b6df4b3d451ae92c5dfc1c60c9ef38a381ec9257b65f984e1e364e6c060198f3e0823405aa93abd8ddb7daddbd1e467df6b7b749fb31e4edf88fef8d222de401a42bc180fc5c5dda9d7a494c8c88359072381727a59a3ee715035441595e622edc5 +d2b5c8d40f92c7f7af2933c5e7a120977d26637932c98ab9b034c53074ea38ed1279e7e71b062b8d140e3d499f96a2cf696980043611ce272475099dff5002bde2c6f9c936b4764e0adaeb84ac9554f81b8de3e195a56d9314660d6ec76438d6ed9c8a84a2cd1d08c50ca5d12392df85ccf70467ea0407a3985dbbee5293ce7d +d2cccb950749662ae7599688015675cbbbcf7966a03d22dc8af81f4258a1b78e9b9d7bf8b5d6983256a98af801721a139df8cfdcd29e576af1634a7c682e016acdb7a510d058d4514be5b50e733cb9d30c7b0bb33ea184a00f8b3871c72ae3bbb37d9238e2988fbbbce08c2d4a065d0c63e80c7cf40f15535eb55e4f50d360b5 +e81853fbc8f1fd6896a47a6e80539439cd9645e8b0ded1f5c554cf853e617bc0b411d15ca04bb9b22fabd4aa2226df4c119d59ce36ab116166dcaf03129c94f7db155e8638c8d309bfcd64fab7c77e6a35b3d2668a40f02618820f51a5ff241c8887066a45ff2fd930e1d5a6a18e9e9351adfa05dd462dc7c9382fc7494b54b5 +de9f654dde79f9170defd2ad8386a4ab535889fcefc088eeb1addf4aa45a01a0a4d583e40109a9637aaa8c7fcd5314570bef2ff3b8958e80f2fc146c71b3fd79ccd4d06d0a7be94eac5ccb5fd44695df6d78ee4dc7a9a6c51852b95219b9b296a10d5ddffb8f38ac5034351565caadc01881d3575709a0e9e818151852816bed +f89cae294cee1cceaf47188aaacab9ef8e42b584dc85456f370aa6d729e28d018da252093e35c555f242c44c40e1e531dc1f1fb4fc955ef4dcbb13a340adf0c7101e1afd8d11cc65993fe209ea8886855c132b6fc257e4190356375cb8bc004598415f51cf5d952707e42ac26c85c184990906c26f9215b9353a22cb82e2acbd +fa89c077b8fd463079fbb1c9c89b2891e6afc11380297be421cb5a7ff390385686879041f70358f1db73ebc8df6a494393f0303e5c560a3fa2aba714395a20398aa953dca5fde0d901e1bb617790e1f0094203b5b1ab164d86783955e5826fc803cb72c3c6bc77ee9477d3eb7df476e6df97a4b20e59facf160c2ad0f35beb95 +ebfc6e1f84e61274faaf64e51d06e188c3efad1947ba41027041b56e3a1c80dd71281759d7a75ec704e22d9e205673ad6a38f0fa0d936cac6e3c2754b1f0f12946f4ac36f3248880e2d7f57635e80cc27c12b8016864d02a04e5236f537eba37f7d4fc5b46af2d042702110c9f1ece18d74879f776e3b21bf76adcc8617e8fed +de9ad428d3d9b64ed58c7b59ae9fce61cb2c5ec3f95b5955341f67ce61c0db17394fbe596b5ed306a293c2ec9591b5f5bcea5b396bddbbf56f435edc1b4484de6310975688210197160c945b7ee3ebf6b7c9848318be0e45899d364fc67d4ffea82479fd5f672f8df5f89ba157ff7ce6953e9ecc685b5f50f629de639f372a7d +c87d59cbe55203e3b5ea1bee1489173612a0e52d9c366a7e09a0575b0e79ff5d07f3adcc468018a2ca24b974025d2b26220c2a110537de158dcdaf860e8e7e1685e47f8327ce81ad8244385c88c477add2c8b997b3014ffa32a952e179acebd87fa545e91d5a56d4a16e41e50ed5e3d5dbbbd7be870944b2984afc2d57607db5 +d82c4ed9f5670b8b68d2ad054794f78a0b3fdb32e88db4f72f75033983bd1884fa8bf5b3a1427805b2f4d4787b6a1547876a01c8a9c2c74799d5f7c6e0f0482967c02db243be59c78e4fa4f5a34e4895a07ffcca0ec3bd5f24048d10ec4e1bec3945d7280d8144b5672a76746f6c15e42970eb55c840687a915a464cf4b4795d +d500a754e5fad4d58a926c0d18159eade6b51ec72a6445cb782a9954c54176bd7d70d3a60fe5279757a739e6062eb9cfa83e3520e1964f59def2d9808b32da1468af321ba7be5a3bc619b5c75c636efaa86d360e8fa0675f3dcdc4438c417bb51902c9d3565e37c799fab1b94f17854ca143533795ebbcc614bca54f36959c4d +d4d9363f9db9a5e0bc14649810f5c435a731f6a3fffe42b24d8df6a6eedd5ad0562f17be5db9950d46407bb9dbba9f9a3ae29fe3cd6f8d04595ea26849bd35d49ee0e8359c47cf5776b51567c82ac6cef6bec5fc4e1d5f5e712a1a9e8b449e6a577a4104b385bbefeab0fa580ad8b5fe8712779b5391635f8fec655c59d656f5 +cfbd0ea0320f61a647b842dee1bddc13ec5de423c1be6fc590c31e8069884a5f87c37d7e0a2a2ac6fb34188f91bcba00e2b87b65002e2ee25461c2e2ed35a09d7914fd0779d3bb46748a530ace5c5006e200301b9e26d3d7738668b0b1fe5602b7579b7b4a904117010957e55daaba9baed75af81537ddff4d20f16351786c1d +d8c02acc14cc767fa7b05c3a06207d5c861262b850ba868193d9f82b7aec7d9cb656175af823171ae4c0776319a067daf1c7ff913432f4d0133c2b75076e800d6de11debcaf71f6113679e4bc16ee76a3de6b191f6d3cad0f9d2ec82cf415c8412fb2213f0ef13a67d3898ae4ae1166d439c9cf4b9e20f2c2df83319e042c3e5 +e09ab5e7d7b2e37af36a0efa852cf2dd2d0c3356d28a81a8d860745806998293b9c337c88bdcc1c20b41d26f50ce1db86a67c5b08d5bc7caf5082b1f6b9732f8fd6e362d6fc9f3c7dea1babd701a97b41c901320e21cad45dfacfbffd70f2d7484c2df74903b13cceb7c16a3b694c1f3e598185fdcc2fc57e2c6e5a37eb84dbd +e5f48e4163389097f7f2557916abcd1f9312212df0707edef38698f90010aa7b700da25b6c8d3b514d6446b4e91db6f8c17eae2819104f969f30a07e9112ca1ec98ac4461dccaf667a48a552a51a5eb1acbd7a75f0538ff2eb6d8bd2c3b320fccec7a318731274edb1a09b6116eae22be0f3fdc914e462d2575cca09f0ae3365 +e402a052a7a9b2cfa8242c5d82973bae666edb84875b46fc25f708d2b102ec88ffc81e775dd01ff7f80f86974fc5c628e3857827466858b6a8cf5b1609beb4ac5a2c295ea1f07b678a4c5f4c9a9279b91cd314b6bba8ea68243beba2754d4be2159ccc1937d8a65d79934e78fb70c26ef9aeb114cec8aea0c82cb5ede771f8bd +c71fff2a5b3ff0e26dc3822532912b9853215321f7c64f7d9c185021164778ccd16d948dc71373d5ada2dee3e8dc0f9c4504d013d5222e2a04b3c566c31959ca85aadb18a843de651a1280f28c3b171495cd3748d24b21e1d5c05bfabf7a718c88a6af4d4eba69abb4eb558c15ec988708d0e10131e8c7d0d11ea6c64423eb3d +d7cbf7cc537909068a0722855a09cf12b51530e87916e6c89696ae56e23e6fae0788e2bf78c6547bd121e333c76ea86ab23d732b793a2c402799162c44ce27951c1ea985ffcffd7b0f94242b295bdbaf3b8b7082066757d7d9b8c38e4134fb6006fcd89a00e4ecb97da349e5f9f93950e973ce136e0c5b07c4bb1e79fbca0485 +d864ee108f8b62a1d71199d384358b2db77aa201c67689fb2c3034e3eb7fae2fab138756a59809d330e55bd15f41451b1d106a25db63d9df8ddbe26798c03062f0f7dd8e1f1ae43fbec8ac08f919f348090a6b74abed675d9f55028fd9ac991a32ef3b12678d3bf7f6e63705cfde4ca9f0fa864f693e635502ff578828bfb70d +df728818bc24b802d4928b3fed26c687bf8d4da2af081b0a5b7e8408d0e1e2de2afd236decc06e8b58d43e064765535741273691c905a083424bf82ab2ae83563a7c00d43814220569aa54615e7205551bb79fcf36af9ea8c30799bc37e9ecdecb90355a92e5d9d62328059eea240b67e36573b51d11c9df248190839afccfcd +d740d1962a65467ddaafb33dcec0247d6a0d3815d7a4b1f3da3e8ed0ca75e19dc02b16a71dba5fa28d09f38ac61352e0a870f8bfa094b459f5bea40cf42fbb17567211fb0af831b0c2112154762650da96d76b73b58c460f837ab8f8918a2a081f7a04716d85a87f4676404b03b467781ace02ec88ec890464c13688dee050ad +e1762495c9983ae9e73ed7458e667553ad221d6e6b675764f89331005ce0186c6d308d3ef1f911244bf4ce0d588505f7b5559bbee448d8373c51c8ea98ed1f3b3d112f4fed7d560db106c96c5fcb6687779ec1f6f472a6703a70cbfd2d31d607ed5e2149d19204ccae35988684b60c54292091c6510e23e36653d3925b14b525 +f311353a315073e78a275aaabf28914051637a5cb165eb300498365c72c6d1e67bd2510916b31ad0518c52e79e19915d9a361624d4aae90e852ae780c1d43edcea4c27304fbc9c120fff5566e361d9d696c99fd01a09580526292e5f626a8318a6af6f34665971dffba3f676d64b4a9e3cd8d6601de4f049c54e6a69f4fb0d35 +c610a2f93bb2952f6173ffc5cfbac1fcb866c4a61d2d7ce84932b63e5d1cd84ff22f00bc64bcc134e012d0e960c022c20fd406e8b1ed4c50f74a684e081a347e8dd8530f8e4c8bce1eac6532738d067e04d30b74f594ceeba4fdc49c3bc92e5c002b63798edc61b7cfc4a83c8d8e264c094f4d68b81d3be22db9d1d63049989d +dc83d0f2c3f7f9c7fe861259e876a071245fff737b963c686b45d421f094e61df3659c9de88d764072995971fbc688f03c5ba77170ed1786b9aa91b312cc008a9d46cbf48328dcf7a8a3ea5a172b88e16a6de0635770f3cbc47fae8904dec0f998fd814a7415e9f8601938cfd23f38aa9c0972c5712651834a23627676d6d98d +f8a26d32aa3c0a5a555fd3d63856e2dd04a1621e7caa100d3d775c67d864a6f7fca4f0564f5866cea4b6ae6597c9e81c7a67ebc8e7cfead05a511bbe1aa998630e763e4bae47d05c98b6242b32511260fa834b5912970b94ddd996bb712d361c2ed4117a6a7e53543b9b8911eacccfcb4d58d7d4b60d8f4b261b0a426125619d +da00566848c01f70b68f38c714ef36b49121604e951988e32e30b0649b15e5732918c5c7d830bc081a2b1bb3a16340e9fe8e6b4683734fff68c018e85f58f1ff30b54cb047e6abf634efcbdf080f6438e4817fdcc4c10f3003d8528502704c09e99b6a65ce88c100164e9931f4ba544c3deb5690357a0bf5f1cd9b5c6f5f9bed +fa5a96c584235b346dfa9355e3fef370e71edbc85826c3c202b460fe60c72a460c1d0aa5a4668ee910e8058c6ddc20e1f30f5b6a369cc5e2e262001388fecdfb030a01dfcc830f8095e808c439e32c1c22666b81f5c061ee279069c67efa9f905ecd04261da47539e10d1545d76f602acc4eed83275755523d8107dced594d8d +c9c78f76f133f80c60cb1ad3413b7c40afa5da7859ca268b899a75ed06f09d85dc62d054d50d9b2f060f1930bd150e6b0c7e71a2bf0fcf3d02c7425ed17a0b3673d3f4f9437c8375302c8c5f35d8409bccf4eec3438d7d0aab52c25be9706fd376d91cf1e2cd43ba19a07b76dabdeeb2650115499630fd7458aa2d42e128051d +f05036e195b7ff5750867782fe51d704d8717cb0c9ee23d598fa0d2d7db8f1105685495cb095a05a9557ca4dc8fd7948dcf362715637bfb171e5038217469bb646029155af6e83821d82d50a4c8064a2496778e8c073fca484d2d2367c52bf39cdd98f1f2bff5c2362f79ccf871a70db99a404d33b1fe6615d9dcf64b2b2286d +e6389326de9873443429aa1213f8f39322c4dec5f62d5b4fb938db5ef185adcdb29f5d4acaa7b784fbf9abadac995e43b5298c2a06f34c681e08e29d100f240676c8d798b141335afa8fa06e8e102edcd542318dfa190c981be80058855125b1e1bc4e860864a6b2daad46293d166e5ac6d33634e6157731aab64406aa198775 +e28708b6c2ef0e6eb7854970f88cf767a58ef73422bacbe3c74e208b22133ba36ba266c56ed44893fddc0ab3037933fd4d174d9de58fa00fc1a239cf44fd3f53775df9b04aff3c3daef9955480fb6b01374499d0b263952c3759f292455f720274fd892755a58d6cccfed87fe7dd9edc9c75a554cd1921b7eab0acd38836558d +cfe5a09a3363e7fcbb97280be6f5bcef49295b3a2b724d8f11e5eadc42e0f58370fcaee7ce6024972e66fa40afaa26c4762281dce1744664aceb4a80bd356a282d42dcb3e8f3ab25ff9c98d7a06e41db8b1ffa1ce4b878b18c9b06bfdb14a0170af5d2895a125314e2f36b8d3b75e5b6af12b1c7ae99e87ff942bf5723993ea5 +dba8a5680de66065714e55eec51efe354ea04fb3e796023739930227f513f003a03d8d0b70280da58867a8cbc25bec56be3ee98c251185d0182c656246380d6088ba4f37b6440eb8c1646d448e34909e17b4c51ec5696058b87ea9182b509a963e5b2f6b6c1ed0b70c99ad3635338b956951c47fedba1f39681e05cd7bd8d7b5 +cec8ffc8e7c03f70b83775d4a2d3eb6c2949fa9b9deef69c1a698d1ea5aa1e090e7a7a35ccc51b160a94a997409ebd82043b0a6ac7461556118b61305ba9006887d4c0b603ac1649e489380ffa7f4972e8fddaa959811aff55e70e34674a5281525b9ae0750aa69d531d73a8ef7e8aabfe373d66a752877a428fca260093cb3d +d90b40ff3961fdc0b9bd0890d9d6ce6f58f45720b6e46c97ebd876e248a054027c40fa9a1a6b123f1d119ffac30f60a836b558bae286b768926d82ba6bdc5c6c115e76975cd2e9f282e165e434229083fc57f2a025e8de756b01ce83f011e7ac82a0c4a995a6efe6878f2ec937b2b642cac433fadbe6bc05d4d94cf1621b9de5 +c51983be4bf08d37e6bbcfcaea856bb66581438a118537014b3812b01cd7b482b8192d03c21c3e15dc61dda8e729353ac22791ecf4d197ddc7d57b1063ff80e0c6dc6570a2088f670f2a401bad33baec2490941b68c72f2c99f3f9ffae478e3f253c4bc2a5d5df040155da0a484421a0aa061fcc12b5178ca4cbc5494383e0bd +eae065f0dd770e127b9747e3f5d89d33c7094ac37ba2ccbc63dfd7d4f2f208d6500ea8e38d6072e6716a4899dbcbccaf0b72b8812e34a699c6605ec008d8eb58a27ca1e1ab0500bce2dd236af669c53469d658cb7681355d172b65f496871b04d16b83c512858b70f4fbbf9557d51397278b13a922031160c1131b10c3c32b55 +d7543c8648c0a7230ec35252d0e9f4ab6d281cae438670a969728f53b5867fc7120110a1be83d4d4a7f95bae092b85bb37adb630db5524747db6bc3f350fda33ea8539e14b31425e3b099b241439efedc44f0c99a1975b6f545dafb4edaea8c395d00c9dd613a1cbf80d755df8a2d614ec5e5e83562d28a6e739b2bebe3c6c8d +ce98fe4c3e7791ddefcd1b9d35275bd2c1f98500ca0e533aa6fdd1752162852a212a60d1278677fc4b2bc2a32a7e67518c1e8f98fffd3e3bbdee1e4999b36f31cafb59ad586796fa40e5d5f96233dd3e8dd5380dbedba349d529d5ffc763f091fb1a1fdc5b012de244fe3517dee4bbb53a5e89717491a689caacee4a21ccf8a5 +e44142ef41cd39b3bec3bc40d7b8979cb700ee9ff6316feeba9c64ea173ea3af565bb7525929f247e13c5e8d7dee841be0f2752a20c1ccaa019dfc26feedd45fbc601cae0f2b6f170be95216ec176ad746df2b1cb3b49d8ceb9ffdb48de1aaa1ca1fc7cdd7cd6f1ad7392f3a1ed5536a82db646c4c029d10c2545d8655697a25 +d24c24ef31e65a1cbb3418e1a091398c92ad8fe1022a13e90d232aeec05e2021651a774d3bf5700139dfc523b4980e9f287d7f2dc5271db70dcf5e3ae6b12b15e057674a13e5c106975d0b98da8edc756c3a038c95d8bac78a0bd9d59b64a5d65ba3d2eba47a729570b9943a74f1d6838f97a461921854c45f0a55afce001865 +f53009a9c7023f570bb32b16c64df08b5e473ad7a4695479ff799aaadcfe15336c2c41d2d9b4436eab9c51a3f8b46f82f151d10d01e7c23f1d2413e166243a60c30aa686a9d79fd7a06694b56e62eab2e9a38334c95cba056a7e354c9efa052fc5abca6113608bea0654857f608366e3637a46ac63da1ac9bcec8575ba391f7d +effdf430fe6cf83e9ab57fa9af31038676bd7efb035fbd4929e305c88c2e86a9d7fc7d1984255b400a4e3ba69b9c039aea84c3bfc539954ea966b174c7e904968cc3594e62caa9d495b379c69f4be4dab289f9560bb82d86d093dc07c828adeeac564c3afc43a91799e74746b6034937f25a7ffd47a2e95f5efcf216fd6e30f5 +fd312c2d37a7f9bc5747c85cce2bb9b3f11faee8eb0cf9eabb5e2b985b6d79c0be0542b83f730f9b0a7d844c96641ca2c646ab162af73ece2b3dd2b5920746083e48e9bc2aeed08b73129d75aa20118a1ec0f4373403c5a82fc1be24a2d53ae8e127bff6fb842d9365746325e1ff6b825bca1b10bcc44fcb7753cd93b4e7da4d +e19bae43e71c102ec61906cfbe7a1c50ff4f517f9500a647a3d138517eaa32df011a944bcb30fb399844cb8b24c4af8e33d893e002efe6d9513e8279a08e2f0d6343387e181cd287459605999280c964b8e4d1eba55c5476dee6970c4405a9872451bdd013ed58cac1d619b88300e50f83922038a96dac937799bc86de1d8fdd +e45af63733bea0ce335617f63969fef91160902dd6ee6208183a5bb57d91d5ea2a1180b90c5b154f7aefcb4ad2b2229455bc2c4c6abf9bdefce1ab45d2692964779cb7461b85c2c81c6e09eab23eeb00a0aa6bff717d3dcdac23d7a1f8fb1dde321227538cb0a22439b81706dcde9aa697694e421d6ac4cec47f7d585eb1b5e5 +c02e43d1b9223510d290d7eb00a1e7836ae45a4930970651850804396627c634be76b272642b4f9eb9732f3f91635761b708cf52d797fabea83cd640f2e4cc85dcf358ad9f58573ce0a663e3a4dc4b35b3a9e4cba2af9eb34886bd67e09249cae43c1c99419fec69368e7be280d31df88022a062eca2915007d3eec666a19555 +d01d138ec72d3cc8be8a256213d130ddc88ac122f45ce1b8816a6bd828a4881daea455646f470dc5af804ca7e8bfe83944e59f3e625c1de06f86fe8867e50444ce019ed0bbf82a020139bd0bf8a4b0558182d5867c07b1081fd831326e7241908c9c83e459459721c5dfbcc85ab9a4bab4c729f30ccf9031ffc7204264c6468d +fd3915bebfdf090a857dba66cd39ad8c087cd94fbd3127cda816cff9764c6b402f3995f642d85bd820961960188619a4c1b3ba044bbd95ce38aa1c9c53a72d2da6cf8098b63e9e98f8f7293999cf7f061cd8d38b8062bba16dc9a8d1a499a02746478c5b670394e855fedb6e315ee1261fd8f18e40ff50d3821f6d9a5add7a2d +f5ac41e9861e7b80125fcc26557f0fa67cc1d1e2d1afff1bb0dd9e19287ff65b21a276021d0ab8958abe508879f38e91b46414c99fad0b6d301bef9dc68540cc0678adde95b7cbd238840ebfd274d4b0853323f19c625c2996bfc1740e88a5d098c7c9d4659321d5f6d04b5d4bc4eeec9e6aa598b4538cf876d5a475f90b142d +d58d247519881bc39adb7e9ccd1b8ab178c43b5858852d84b0f80f071559af43997a191d1f17ecd5ab37e78beb22d954dc469e7d6b457dffc05f0b908cc526c50e36c484bfdd037a8bd59afe13066cd2c93dbaeda276da3cc8a1404ecdeeef6624494decaca9bc2052ad9d5f1a699d739e2f5972e68002fdc38b850e77a56515 +c6877d5d0212ca82b394c8ab47464c59483bf1868ccb2cb1661545659a65eac46aee551b09f1e465b3169d2eb90ed643143e982d394e9dfdccab3a5917ea2b4e631a828aac0caa24bf3324b1de5484844f176975d93bc5a3b4d457dcd39d0aaa5d29ebb4207e03009ac30160faa29795e9f95338614bed64634c1572bb91ba2d +eb4a8bc143dd915e59c99a1469225be57b152e6fb804e948e7acfb6b36b10eabe0383aff8f9fccd65ea336b6c1427fe88bfb88d4b1265842375c8be6db4620eea2b45c9cbcad7db53470e1df13cbc19017951cfb693c4deaeb932450ade0b22fb5f8189ab090278ee1d28504a5483976d28955adbcc71fc18e6544433ff5c035 +d4a71ec4350f51d22b5546c6d7d6d07f1160b41866101ad847c412c39c84ff052d78cd5e2084c4e6e6f85889eb8e3084a6b0a60d9088b42ceeadb7a0bbb9366d5bbf89814c1b7a4045761d0b16778b0b1e9a8ba1b5a6d1139e52019978ff31ad93963f75ed98ba544ba7805437177876b37f2b7560314af5511c2cb43d8fabe5 +e5a5b5a02380762d273a3d808abf781b6409955e69ee510137bed632f18843deebd4d7f81f45913f57b7f1681cc9731eee76ba99f5fd0da8b2b2bacb15dd9ca9df53b5e758f0b8ce323ca7131d1cf675823ebceadf9f9a816eea392adb963557e28c29d966dc24c29147d59afb9db9561aa9cf200ea3a7ef284dbdb9c4b43d75 +e2399769327dd44829b3915999a66e29d023b82e52606001bf07cf1de15b2cb2685431a0167845fbcd06b8e7cfb6ba78dd8aed5edb44bc7c4935e5feb2ab44c1a35bf97dd24967b21b725a39260b32fede553f678c4cf07b0d4a1f23dcf34a75f49f0bc5b10ea517df13cc57aa11d89c00b23142d07ca274238c3dea132e0545 +d08343b82cda4e6743a5750db867698a28d55d3bf763b12b20b5096979be7a8d82dacb50c9cac513d3bf35adf608b1001723cadcec167ed6400014afea8aeac3f4f9e30e8ee46d740ff202c6236a8a85d75b618d291a081e5e145a07174bf2b3099f140cdd5c656670b5ad209b82a6d751a85a91da8c871ed3843647fa5962b5 +f19d08d244ae38b667a3eafc49ecc02c57ed3765a9b6f5e6df373dcaa34d184727e1e7e2d67f2463945ff05b4cdd28251e02fb979b08cf4110cdb4fa5349e74572bbbc661079a631e1fd4978942b7acc46888865b2fc5304d959f9c389eec03bca5a67b8eb9c85fdf381eccae9235e11aa4cb3bd670ef4d0a695a60c9f345bc5 +ea9733f219602bd2f8b57835c0260e5729c33d805e92768558b76ed0c434c1941ea514a0f636bbe6a62a954ff288d61a617958157231b0e51df8e585dac28cfb49260908c10a561c47cfeb6765a383f28e8a2640301e99ff6f22ad8b310938fe9bdfb1dbc1f977936d31ca524228e047b9771d73bf7f1274dd4c8d3c1d8d725d +ccdc84ffdee0c939b3428220f15ffc02333ba05373bf34461403d5087462ca6e33716806c61f68722dc19016bb168f10c0564a6838ba72e90db7a7e53561ef725a4b2d2fd46642b478c7431c0ef4f867ac42ad9adb88c4cb3c49556d4bcd48c553240099f27d91176bef4260e76b33ea684330384d997d8d863c199e5d2fa12d +fc8dbe56db6199481e0e7b536a7b11d1dfc6725f8c9904d20c26ad95f95c45558e2d4925215493052c3a1922d0746777fec9b10564a2a9352fe6367a089545f54cf1ac34204f683907c4c43faf300e42d21a07cbbd91dc6a2e7ec1182a4d43573b71b1a6e6889b24610e873fabaa71f25224eaeba4c431bcb20e196ec7ca2dfd +f8d0fbe2244f8917d1f3a0f276062d4f55386a924b2c43c027155a604e2b13d97c8c02cbf081b36a7014139626a553ba32b44aaab79b4b32206233256c8e7e4c8084bbff1e98e877a42d39b9ae2a27fbb89a374d1ed342864c4de01c15bf7832c1f1a7e367899e7cda988aaa46e8dd199de7bf596312febcb8ee78fb167cd6ad +f521cf6c8ee30acf897098cc12b22b02fb9413ba7b453b09ee30c1a9054ce49913b0be04caec5215668251b09db8beb273ef6cb98a7d832a8262c9fe0008652000dfa184f138b6087d79ddf3addb92a7b5af54f143eef7e3fa9e1e136add0c4259306dc3853b3cb9f55b341389609adc23ab0774ea48f0cd95ff9cd243e12b85 +e9f9e5446698aea25c754a3a109bf6a98d19b9d8b70254afa85c6c5bab62d1ab9db2a5901b8310fbdf00851bc1d57c6f5e5af582f4bfe3a67a961398f259a08f176c1062cac3b919cea6616b1f35319b844cf9a97a47eb166aa5f470b7a5bbeee6b051f69181f9c8d75ab71118f6459f1ba6c49b41f555f3c36ff562f8734fed +cf0da6581432e2b9bd38e4bb9d6497e3e09ddc23915531002cb1006271692ae28bbca7051ead4ebc16471028679b7454879400ba245cb4af1d7b4f5c41ba8278c6450a58f22dca5df7ebe945e67834012d064645dbfef5084f2567abb2a13944ea141e50fa4ed460295d90ef40d0bf5b0a1b2af8e8c4e9d52e34c2f707b2d675 +fc98b4b70d654a1aa86a64aa10a13db39bcb025a0c9dbf572dc1df22ebe96f155df7a1d624e85387e90cf6c763a32bd955dfa0ed99f37ad71aac4f8570d7a0ccf881b6cc4fcb91a62e1cd60488048294fe2dba071bca8689ca63e07771537c7e36deb5f0205a58d041fe6102e81918aeda2b9752b0d136efd49b71a98c7cdd7d +cbfc166895123f56dd9f5f4844b8a1f566082862f6a7cd0b425e1e90f92e129d64217e3f755923ce47572e2e8b1297aedcf3044c263feec5517caef9657698c3b75d6b257ac61dd60da87aca05da46a840c661963f423b5c76e5812741d54a1793f1e16f1d134d3196cfa670ee8d62a691214648cdef7d12db935da44787aa45 +d258d504c6f3d59ccd8dca3ce517279a027fe433d71e16f389852e341deba5eb2c94dcf8714687e7b6a6322ad390adf5e08942568c02a5e586d49011458f2c823d54a859a03e82410dd602ab3b5ad526e77f54b735065fdcdfe6c1657e31878e6d15689b23e1e079175ff683ac53a611a74032269477b5ea00e2a087cfcf2e4d +f611da44eccba932fc27d436d8ee762e3757744e1b2f0edaf8a7c63afe45675a19f695cb1fbb547ab2e3e5d00c804d8efaaea6f60ae35a5e338e5a648927bbf1fb7242898b376e0f66670ae2c0f16d75fe9279dc97015822725d617d09909d04bb4c48d097defeb5c9de928b0747715637dde13b23a37f97483bd87cee7a9f05 +f748f838957c169a4bcd35da0205727f4e036733a6ba5200cdf695c7a46124b6cb39b876354253d7394f9e3460ae9175e82da7771c9a0b2c812d442e66fc49f2c0ef5c4de0175bc32ecf2a072bdc852aac303923d40e0853e0774c7cc7d3eb6ac6bbc63bcefea3da62aee1d630acf23ca7eb4d0923451d19eb2638abb4dc26bd +eae5a4efe1e981d4ed08ae37b694f24b0e6c16c6991f3d88265accfc88803fd7a51498d9f6cfe2bfa5bd4948ef82ecb87da6b1221495cb117f0a5312284ff3678ed7d640ef2ed7b19e348e7a0bd694dc28196781d2475c45d6252ea2ccb443e1860df490867eb607127e2472c222d7bb36abf85aeaede667decae255dc43348d +e8a6152e7462c848d36f38e356c47d268b763542a5665465e4fc00c57a391bf5a735b808af29b9ba29144e93828a9f10df2aa2a198a767aab846614035963d7175d320e7aca496a8e57784857dcc10b571823526bbae7fa16102b1fe174ee3719b0e9f0b1578bfebbc7b77dd3658ade9d1bd6cad35bde306c01f9a43dd168425 +dce3db498d870b98be37fd5908c1a3f9b80372540bb627ab5f3860fdae340512b848d888d31943c45da506683ed373f9dfb078ad441c4a3445bc0b6c290ac4a53fdd53d758503684628ad1722d5d27d306cf22767c3ccb82d6be665622a6c1614e78e4079b3d537276b4ed16dcb2daf1ae2fd3b7301830ca7f18ee10d5578c65 +dc0262673ff106b22fd8f7b0e954504681bff6492f0d76c4813a8beff028377a21696e04a1875ba357e5a21ef720b22c6da618375d5dbb40677629e5d1f197ef880297cfc735eb7c16b1480e7fc437883118c2a17ff7a9adf24415c6fa80c2540756e73c3cb313a1b7fcb58f4ccdd2f481a528ef30c413594146d86e508c596d +c31f4efa0071274e62c6ad18672d29e8d0a34ddd87332c090b19c48140e531ae99fb16c0e66ef1e9258d417dec22be932eee3e8e2d2d1e32f83714f815903b348a52cc583c6376384b81520427f7ec1a93174885da96c8fe5b6f4d2023936fbd216b67f8e0314b6555c38d245acd5aeb83d7b05359f54171f356a9dab4b07715 +cfdcc840aaf8bdd98c4274ace9f46d8036df8ebbf027c22c22887f7804c93d6dc578659abd3d28a6a5f8ad889645c7e2b56057a3666467734183fa59e703fadd01cc2b08909f76a39d06eb3c06975e9b57f67c3517478af4a4457604962eb656920bb228633040bded473f06f055a46045ac54c2c63d109b515e360e5095613d +cd789dbf2f41a8cae05aadf109ead16729ca86da3a303d3b4ac05ccbf016a4f09e18226a6fbd0698c2f9d52fb37baa4b2427278fbe50d64c791a0ecdec22a1b97294f8a21807d82ec09ab91bd3fafc1a79b9e7da27cc7448813975a165ea18771367896046eb9924b358263c279fed1baf36be8bd82ce49de66a304a7807a505 +fb5fcf913fd6acb1fe6a6fa1cfe623631b3eacb0fb1af53b822a2ffb0bbee8cf544bea5ff17cfe897148470a928fa9290b35a487b8db43e2aedf67efe174b71409c8b15e769bff98888f2a69b3fe93d6e958cc0b185ea58ec5ebda005219446eb55d0736ad2ad32cf4cd580e60d25e26f1e792e637c3ac528fbdf1501cd3851d +d7216df66b03f51eaabd1e945db039ee72fbef1ea3f79846d46ab083ca02d74d7068054ac05d5ee5b90ba84c81f9cad3a969945f7d4ba547c8cdf42de47f95542f886d33f1a6ce7172db2fa72b5daac85462027c9d0aa1ed855b7dc1aad537935f30469beaccd3248dc1b8399f8cf627fa548b03a01c33e9d1238681574d937d +c28409f6abdb5ea38bc20f01eec110058cb90a07c1063f69ce8170cf8879ef8679b25965f2c985371fb7b40a2298e8cf61aefc062090ed32dd7477a99f775e39bbbeeb243b4c2b90d1c6bee88647e595e390e3a0a3d2a2890fc1e5fbc1299d63583170fd614ebaaefaf08e9ad323342c9acdf0dbfe2150f89eca4402499ca4f5 +f135258dfa7b82b185b48a111e343b46830ba562a63f53e1ef0a0cedce0d8b6e3e9323c3cceecd577d859f5659e6365085036ca7f15e75fb2fdb5f8d0819c8454f948309b2571237dfa5d08275182d55786efa8a274c5ac6641563bbf115ecf4f8cdf24b5bde89b90490c72a67fd5ff2f61d92015d4d53b5af3c48aee33838dd +dd23c15c71c7be2ed8d55fc98cb7b00269a3ca2f93ce801013fc77b7aab1917bfab2133dea01e0581ffc8481218eb7097f5ceb0167c1f223c387088f2c2d108b06d2bd1ff68e4255f4cf5508a067fa895fa27d9739bf7be72e661fa69163638a04766d8f9d07fabdcf010cf02f421ba893c7ced966627b5c6739bd2e70613fdd +f542781d273191506f5b5db33aae73a25facdde2967d84f1ea53e0fa98d43fbbec42401cbe771f71e1d1a0d8f48680739fbefc1f701546e856cb7bdd57c65f96aa4d686b9af695aac3b6ae92742d8ade5f6f4574aa60773b6c01f533dccc720d9ba285c04013a627fc0a7fb26f0ea48482e94d0f89437ad9a531cba372233145 +ee4e65895d9283d8717a958c088cf3bdb836ed38f4c6eaa9b9051739a1de986781e90ffd268674aceab08e95b2d0d7505619b9fad815876972f3391ce66b8645370c4050cbb8084f97c4eb7ee7dd85d5eeb8cc4eeb6917984a8bcf36f2539fbad5618b6a80e1409e48cdbab7bfef14d628d1028169fb6787b98a38920a558115 +f34ea6ca038a7aeed8f446b56b1ea9a175e082f8a03e0b764bef95a9e255a27454d4018f697c91f76a588e31dae7a9f9eb18925aeb19ddfcfb0b5cf6cfe253c643f3bf541015b3a0fede9629638c49c81650b8ee9cbe1026e04886a689e0033980a3c364069dc03dba3154da56807b325ddd96412960c64e915a5fba57e657f5 +faea3f46d8d6236c5257177d6f27bd2ff7a2312d54512c5cdedaa96effe4810c216e39075136577c91b2e0022f32ab111acb05f8a9b051c9d11fba8fb708b1e8b8154d8f3d6566a52efbfc0d8414b1162b435bd6b409382e1b34fb3d92615b887be1ad050578978012aaa5192a8cc4612d29bda58989bc50b837592ec327318d +c8094fb8e642491e29d3cfb5f0b214255838b38635f637dc8fff89ecfceed440300a2cd3553633d927d88145109973923a89b427cfa4634f77372938355217dbe7c19ef20121b4e213ec86a790ba4e867dd45fe83fa0a51cfb28fcbef983ff0c3c4a3e8dacbd3eb5a8e2b46a571e000435e7d871e8f932d4b56a808ed9a4dabd +c4f4ebe55a4f83579e37ba2779811b10775250d36bb5876ca8cfa896c53c8ff65be9689fd68a8a39281d053e740bebea27fd781f0b3ab5be3f40308d6998cc5a95f1d5cc095e7896825c6e0504139cc5f2438ae85a048daf28a1698538bcab8231a8bf449b1e1815b442be1eb1abd9019d0058b18e118256be7a70293e133b3d +f712ecdb5544ce9d3c146f317d4850dfe9bfa1b45a9d4d19dc9ab459c592274c282edcb33ad398aeb22e6e41b72670a0a4b0373c73c829570a0acb8caf1c7034d46e849a6be96db32e2f99bf0e0382920b6503a8bdc7acaa801c69ac6f0192b9c8293c7bb79f3c5ca82bb3452b3a314419e0b53f72867048710a7e02f4aea76d +fdabbf57b9d9a703eb7f9d698055cac6e6a541a1878222839a7813b84d7cfb75a25eca26df09d2cd42099ebc2269e7ddc60612f5e3fbcfb152036ed3cb83c56a552aff9ebb00a356705f3a4ce9100a805b9ea0527065b48ad0f6655f342fecc35aae1a715a3e00fd02bf177d352923117599d3ff30828a0c57fa650116df7075 +dce3548054466fabb408c58cab550ebfd604967bc785844da99e08a5e03fcc84a973288fcc3cfdcadf7e3d7f0a13d1146189713658c1c32a43eba7a84e7742c1c988acd1aea84acd4ba0dc7d87e3711933289c2effcdde2570684cf3adff3b54f15350ed40af0a1bf0aec47d77148e1937c9762abdf9c7820bb2626d4a6ba985 +f086ad1a65ac4b2afd897d1bb7895ec8ab5477b96d4fe6b06128515843c5584f5b8d3f4b5ae3deecd0719d3a772df47b37a8d938710edc57d5bfce58b7e415b4c57f623f5e50d456736667180b0a08cf0372f696d65f013cdd7a1a2e4dcbdeeb0bb1584ac7383da048cd518ecd9a3b5e7c896311abe96793041de812366fd6cd +c95ea65ce5be0a626740d2fbbd4df61913a5eb682419831b9709912d2d2176ae51f8d23b685eaa15eaa4fe76859ad148f6a2407b1d4461bda13a8ed337e9646259be2000010b2ccabc5b81151f7c59766898659d80144f36c4f4d2dddfb3b72d9c96b1e59e6767b64e046bd6ea612fe45434b68cc7b71bfe3b1936d2873aae2d +e50160abeb3931d7740cb48a3aa0681453c31eb3983a7cabf94cb581ed81ea7b1cca9c2157d562e63cd30e572e25f56aeb3102ebbf7e9e86e1c70da9d7119e7917536b375527fc6ecb2266c1264df2823355c8a7f3201ee8bfff6a1886b5bbd4b05dc78a7425d6236800732d0b7875b59417f0ef62641fb1a48f9b2f4ea92dfd +e4d5215833bed28784a0ad5dd2869b97ae32d0f6ca5f4333e5df45a4135ae61576a83161d03229ad935812fbee75d4f8b5e9e9444a25332855af2265c9107c24a048caee053ddd72b0d3adf16ee669f8f8cf1ae84a65fb947f0843ccf76ac641ff1ac98464cd36d80b243c2ec15cb8a6fad3e45435e503d4144892ebf74774ad +efe9f954c6f3a607b54fd893c9ca2fda5df4614d830053248900f58fd3ecdcc1ad994c55b1739b476ac8bb6d19aac6cfcf987f2fee4d0187461318e6a10fdf3b5936013bd11c582c49f2ad1d09d1ee9460183f45a22aa42c02bc940be4170fbf8e205c87e8c64943b840562ec7db4ae337b1f0f977fde638d8dcea9dbd9f5cb5 +cc4937af80e67e580d30ee54f7948481213053a49c27dc0dfdbdbc5afe79b50f103492daad1cad29667f5b45f3983311f4a3d769fa7e3493e5fd8f9584a3942191104198de7a77e978671a2af406ba6accf1e8ff883166e29ddb00823cd1d4d6689d287bf7d0d511735609668557beafec9d6546bdc3fb631c474eec3e2f26c5 +f935bebb45a3b09d044ee922c36f6a0066e8668b4717c5f21cbc18a36d311caef071995cebb139b39c5077ff3bd5a527f858c545349bf3f50dc0323bd71835e1685a913f98d314c85ec1312ed0ba310187148ae6988497e508e00c0ef1bc72023c91ec8ed528ddf1ec8edce3ead3e88a05a51bb7ebdc1fac29cf596cbaa49c55 +fc26c66972bc7b013f0b879557e341a1ab9b05fba4a5580e9607358674359856e13a1439756614bd6600202cd18f932f737e839fda6427945750345f7070a1e317d391816d58e1500d96188087174df8970cbf8c7c4359da09a143084871079cfe68f5b6470e9eb6b8508ee4b8baa271fd1993203b0f71d074c3bea1b31cd1dd +cee883ffeb32eae7594cfbc23d3cbd5b37c72da6dd7d3d5bfb286f7dbe460ac3b6043f78cadf14715b656dfa92d421958c8acfb43194078759c058565ca181b32c327b94a9086011b5952ebedc80d0e8ad8e66f84ea9d9677e19c2424e122f3c8d987072322f24c5de5ca61b5abc7f0aeffa26972a7a2d241defbaf7af8ede45 +d01046b4fe46abf9b099573273cf61303301689d00de860f94acaa64838d76f25365bed14039eb287e24ddc34a07c183f6a2a81392fba7d6e1d4e0602932ae13339de5a788900a96d58fa25042c7568fbf8939ab65a5e47320b03ddb72091895056d659677ad22ba1ce6088ad515247327ceb49fc5e223d3e828050c5ee8a155 +d1f9a4c8b9d2d6c6ae0d928fd5c2c6fbc78d056c6804223788fbf1f9990298a9fb0ac57d85dfe1d1590c617212d717b292e2dff15279b973f03eca220b76c5cb1b699b49221e91a88b0619384df979f21ef63a5b72ed20c533388ba23d96b6d852490c5837955aed263f56eb862bdb50cbda07c65417b25864bedb6cc6cc39cd +c477431e31e33ad6357203152d48df7537ee7d4343dde4580bfce608d0b07725edbf7e16d5a6ad8ddd79ebff06905225322e7ecf86cf2e9d6cc141caeccdbe4c24babad2da9eb6f696620f056a42579cb5218c1acaccce5a1b3a76dc4110acd74829a31d373825e31cfec03a58102044845d8997b9ed93bd1e5ea96e081d1015 +c29d8fc580e7ce4685181a7c43c82620a6eadff437952b73b655b0683283561f067583447debd62a16b7e55b8dd6d4d2f61a99165006d04576025129c027f2af7dd2d88d7f1faec453d96bb34ad50c4a1726406f2cd50cd45c6cb670cc97d6a0809b556cee10c08759b9b36344aa8627ffb9c67b159664678ebe6b131e124f75 +d64fc2f28b0bbbe44bd528770722849281433f156ca40cc2722d4bc18df19e48181cc7bb7265a68407c8048e95f0aa88706bba6d9b7bc9e78d54b4ac028a96b99813e514dfc032f436ee7d284ca3d4403ba94ca5a93707a20fbc72d0897d3fffbce43e252a7d3e53ab66993cb2e1fa02120c4897dbe444441a63e2f7bcde3a2d +ce6dc18edb75b0a352738d0292c22d924c813c1a9abddc00cdf4592879b566e30d9e98277f96410b2db488a96262e32fc1d104e776db8a10a931f98524fc7fda19b3867910614835fca01a02241ad34dd02719ff37e980df15c16ad99b1321ca7c375eee6569894fa9a07e9f4d7b0b49de1586d2b2243b2c50981d8d1e76ffc5 +c7a9dca1332f1c903d260c67444f62c9cf1d8b53e283199bb0c2eb84a72dc800557709e0d77cbb5ba76b01176406814073b633bd56af3bfc89b8d499480cef7e7355dc78cc58a8a9f11f8221ca79cbf32a25c75f393cf8dce9d45218b1280595f2f496539b6ba9e3972c08c7b9d0732fe9488754a6f4555cabc331dc634dc0b5 +d652d68072085772edc617f0114a69dfec0cf4bd7f7cd50160d9d8852e3672e013522075e2a0fea5f6c0418cb61941ef1ac6bfe3021d68f710a723fff5f423509f75a4b8094dc4f753c791120148ae4d0ae44bb09d09529cc6d29388a5e90cb5df5a3c23ca6f559c72255f8f1db04eb00fa36143a95b8e43bd71172d9363387d +d34134232eb029ceed44cce495cccd5bb84406f5dc027b2901cb3b57f201a619828bb4dd7331f393430a9e1a2c0ba039c4cd8895d5c1c554ec9b48ec5c1ba98d29fb53cbcea1a479afa55f1fc5f00c1d086441af41a833bef4758b7c65c9dd8c2b34cc1fc63c8d558f72d9736c858cf6e182fd4140036e9b590958f580787abd +d95d11a752f12c2d04e41d0bd2813dbea2272e49cd943c72993e38a88f32448105e1cb3816daae65d7486f69b6f7a6f28eef4a5cd5e4ad28b47246895f5b79ad4aa4a73b0328cbdd1999876ed8908d5192ba68bc0a446919081b64bb91b299c021cf2f9609503fa802d86545a831e39a8c4cc775362f73a3c6d1214ebf7c7bfd +e59903fd4b578592a5d6d706264846e17127732a16af4104235850e06e78f22d900d8d427d30b4d6c74dd4d1f90e0abf1ff0f17cc8cfcc9dbf910f90a0518364634fca337aab25faffabec1a7af9f4154e10434acdb0b5d1ee705c198d0f2bca3ae5b32b15b91883e87ece116d6b0bb1afa564d3846715a8b3eb0521ee3bea5d +e90a1f94595c3a7bd75b90b5b6cb021e32090affb36bc094470886dad266cca4c057e48480d92be364d02961b7c88992dd3010fa94f8189be92241beb96bc3a2b0b97e3b0a88060b0e2f17bf0458b64ba357f89a33c6e5c0f0b0905f9eaa1ea5e5d492e4c54c5c4fcbdcc41d546809acdc26e5bb46b53ed95e548ae261db0f15 +c0bf25c067104f867cc0f8e86553c2d46c9dec62a16c5472fc88261d0c557109575afd749dc3cf060b31fe5c25255db5c5b428b46d23c5972e0e3c1897b97876ce743da8ad0ea9ad00a10d0f6b211ef125cf861b17b9ca21bcfced9aef4392c9728cee93b61a02a6725b3fe92804742231a2589a93bb115299cbe571e8188485 +fce11de71e18c85d152046211cf5d57be81b26abf687d094816d5b1bc16e7f446f7f4bd6164db6c7d0f60ad9ee7fcef172517188f1221cff8231936541baad418aa310896ccb0e76986ac6975bf6b5884234e6bf8c719874f6ff1d7b98665396bea1a58d4e413768285d8d1db16dd496b051f3f9e8015ec280eefb911480cb85 +d71ea7196f642b90f8ea627a8f941655f6472d7fc02f4ec24c55cc79f95d661183121e7324628325bd52f33e2f1bc1a14310d7f49930462feee1ce1dcb787af70374a29a7152eca3bfef0138e11e85394335ef7f8b9ac51a0314fdc795791c9ca0b5958c381148b12af682ea6ff5f731d6b3b83cd0cdad9ca4f9d4829575d94d +d7fdca776293f33e09c416d2c1ba70f00f7b47dc1ca6a2c8ac6ccfc9f1fd1e88344f75316a99be099789591600ae779a4c78d4c12f4a0e97388fbd6ca63f437fe1def8f31671ec27f6f9bceec8405932c15da2fe3ffe66c6dc3bd0aeac146867790adc61bfab3da2a0d358594746debd018d2b7d58dbca16f4851269bc806cb5 +ce72cddb8418186abd586c8fa62dfaf121e804090937c323ddfeb139dfed9e570b26e2e28e69ca08ba5515556959b451092c83fa5648fa769122987313415e14bc664402854134518f131f58db527d163db3cd13062a9af658357801535ed5bdf24f97ff18cc824ec9af6dad9a2d3921faf7d84218d1f513ad262f64dd86cadd +d5066137aa3101b001a99b1ee411dc57a6c5ba33dee3279e85d2c62510306a69c924835e21e6541d5c2fa3f74ea4e908024bd299c5f8e6049720e1cacb935f8adab9b4a3e78f83555ea835574122c2cfc9b1c2d14d0e4e56e021e06614d5e76628982cec22995458e6482f7ced45f757716620d27a53935386ecec12b9bc73d5 +dd62d314161fb6c7fbd6942123a8e321835e19c5b3bd6f067dd713106c83a04903508862541e1687d6147954334f2315595e41d307cc17c47a85fb0016fd987c2b6273aef981780aabdb54e6f8eaf269f97af56b8190fa2ae67af1a3a60eb924f0e00e9f0e5345ee4ffa94d10a1058f5b364234e6838e2832c787b673be173cd +e2b1d31e084c148363d4a41c685f15ca460e1ccabfd25dc20b9c51844c17c281fe72c76d2b6d4a093108d5cc88f0d196d24baf65c90af95a905afa7794d9ba873c1fd79fda78b878ee970c8a7a3f434ca44cd35965415b35ebff7c635a5ac74a4500738c86f52db8a7f7c82c4544766bc726f4f2b2a4c02419430bba3cdaaecd +c227213c7f588e4bbcfd2dbf3ac650c6610f03e4c4313ddaf02791a75ae396a25c62089e11e47326e585ba8c5c8e6aa20ac1dc690f32d600c162ea086f5eccff3029d1bc2f590194b7980379680edcdc6f21505ee678057c36245c400277140541d12346f067bc77354fa2abe8dcc26a2df51f0e9b5d05f604e179529d2d226d +c90dde4f6f6f00220fbe1efa9accd0d33f47c2c7f25844d4a3d58964d1a01eb63cf695b4415864b11d25c69cc231cbe4c1b8e8c47bb49715fce7769b865db47c8242882c528765534521de678c800469937a2857ace52f3bfc74aeecdae1b82588c7a3ce44b656bc274ee80c0926985ef828e5d7083486b3df38359285cb8dfd +c3e5c636529c8c0c7a842c434708663d186b95f8cc1a69eb7d39c7e008cd1e192defd05ce9fac89e93e57e64b99ca4801e83e7fffb7f0d5a46505d4d5522ca84f00e05b2e895706ee703b63b897775f5950c8bc5d7e2557d617b55eba35ec36743a198b4cf239d95b9efe62f4c40a8b3930eeb82d07c186d1c3935d3a2229515 +c8b525c6c1d8ae599b7f7841cb55d4ea2af528a77ee21d840b45d644104eff3cf467ea7bf0f0e0bbdc5a81cc4606907712d1235fe3f33551ebd11f6dd54ec3a42d3b6569224d0579bf587cfdba19a2fdd8bc8e30229e0d11cd7536aac5dec9475fee801900f6462114c0b148bfb210c7a27f9b027f1886abe00674fce8ff761d +f59a9242b778b65b309b619dfd19a877bb25a168bf0c323c54b03e3635eb31d5345030dfa96130b157c10b8c8d78f4733c0610f4130e99279840966b0115b31066f6eab749fb930fb844b50265d36d04873a3ab0197893cc62b6703d61e397c8aa3660dd3cadff9e020621b8b90b441ac368f773f94d2d2d4d35afb9ec12a14d +c9d9ec80523510681280ca314ff905ea6256ca3ac6225e478705a662319ec8365c36282f1db47774d85f00cfb3d66aa68cb60a9af761924829e992f0e5058488bfae552fa0c8d847bb4546dd71502d63db7a2b713122455d47912ac37b89d071b272b99af36df64f66f803d3ef980784b0da7ecfa117f75ef52430896f15c3b5 +e979ad6a662fd41f6dbb5ee0d3a8f7a928ae0f0940dc3982ea5a1168a46f5910ccf0d5606193eafda19ae97c5aff2b903b0e3a89eb3c879ce0e13cdf43a2a177c8104d7fc121bb9406a826900da82ad4352f4899d04af3f57a225c86c20a175a96f941c2c5eab721db904330d6bceb8d6a7a913461e389158708c9b7b83d8ecd +ce2c29c73a1a121454c12d84784165325ab05dca980a0390a3cf0af78409cce3e67ab9df103543babc0e35149a0b8b9c677f9c71d37a59eb27811eeca31ff7d568430a434a1c03720541cb69a21947e378d14711f17efcab348fee297e53f169fd5ab195b4be24bcc8c68a9bc00b273aa10a01dad64117cb7615b5fd88d0df75 +c3ec5ee6e08a212829db60b82d68c90e4dce2c6c419dfd7402a1a4ead2c4d7d864d45e72a2b4f41a09a569e4863b2c78c60d071188d927f608f1b2c623aa23b63d8ecc23e253b3c995c85416dc4ee57ff0a63e2e8884f191feb8f16bbb578b5054182b3edd779a0163a873685b927b7d375495571fafb20f420f8ab95c0d0555 +f389ef5bdad020e5dd82a2e0f9dcd3b40fe30af45ac437a727047a934137920df2cde3dd6097bec3a2cf142955651661fbe04cabc3a19ce2d882d9e17a8c18046f598216e3b975f297819bbeab264676e4ca1acd202eed2eb7bea7812238b33988ad4c980dfb1eebc643b5a7b9312969f4adf746844e9d714b2180e6fb19a69d +cb8caf76e9203122c7f3cfccc7d2924ec29cd53ddc0709ee0902237d445c2cec1ca337b1e1ab7b6cb63cc7f5f11e50ae8ceeae9dfe11299c39390db51e8a4186e69a74d0775bdb38d1c2c5f8024ad1810e298285393293d3118edaac82a5d8914c5dff163610db4bc4c09486eaad877840c3ddd16c9098f107aa7614499ecb95 +ca106a4be4fb9b3c203254f44debc87417b64ab4c774f972f12cd1b3adb1c5c0c760e68cb145fbd02c7b682f451ce467f17a0f07b5394e7db5fe209d972e3fd2cb92b583b6446740fdf38e292d18b4818e824f645f86a20eabe722c0f693984b58be5e29a56a40ff2589ef5713b5ab2d7ac803b72936011dd436cd1db6e2d6ad +c69e4a726bdf444c038ae2f146b35ac11f03127abd71686cdf8da7219ec3cc89fcd1eb4a6c83bc6eca90373c8b998be6a05858e52a370b184b3d1a2f4a280f847eb5c252c351f52151e13a83dd7aaa2eceea9361071b13037cb9197d8f7c4055ce4955df497258815f59f2a436392a4c6f97166882189926575682fa328ac98d +f6a6bf9a76484194e7e94c0624ce91045be44e04c8c25bde647d421e6266694a4fcb384699cb98cab4d5e3498edba6c581e868258af1b628557561fb9c5e5e23cfc9effdf4f46a788587a1422a4817152b28d67c7888c9543673c2d8eb3a5a51ee01ff2021d7e9f9eaace73873bcd189f84704f2bd9ad5c38cae6e188a3e4ca5 +d4aeb7fa7499ce369691bf04ffeb312ee235060d0a7d96e2bf6398985cf0b55bed0b8b34a53346adb47826e71b7ba2490af72f9d92a33054e1ad8e203233e356f5de9874525de52e74960948dc6a2dc8c9d1671b463a167ac5c889d5cdc6fb6a087b9949f3a3a4c15e1828979e9bd566f502bf9d12e4735dd1b51ab9ef7dc97d +e1324d35af63b7c5475c19d4642a776c82b69d3dad74278afd80ae38969d6e010f0f78c7a17dbbc505afdb00f05c0371a8b100acbaf7b69b4cbd46cde9cb11ca43d36ecc1d109aac95a7d6abd11f088787d9d8ada4412fdc06c030339a10e1459939bced6b5b872dfbb08e33f0c427fb94e0245613f0b697f483674af9f46395 +e4e6640df3213f25799c6aa0c723c17cb67d707261f3ef74f95bdee8d2acc59af18233286136a9d86610421205424d27f21aaf838b7ee361176c0c89fea4e1b79ee48ea438944746d527b1adacdea3dfefe2dfc5de313876f211cd3f7e3543a5cfa7f07624e913f65c506af956011b854549b6b956c951a803eeaaf6f08e9bc5 +c3c8a60114d2f5d38e14c79b803d2d7af36e285daa5654da567e6feaabaf74d6165304d53bc465c0c72b3f9f7961a996e77e066f37eab45808ef5fc1bbb87cb252307b88eda73baa461b3ac1cc44c37af966ec17755fa66040bd340f2a77a8158c247638008673fd3cf4f98ed552ebdd52b61932b540db5ba90b939f2dd9a585 +e18fab58b61a8b6b28e6626cb74cbf9b1753e51ee3a8093b9283cefe7dd576f2043192fb59a472bb89454b2712ff6bbfd390dff0ae2ecddbdefb80aabe82500c6e4d35fb99b79c6f17adaaae7ef87443189916566d755e8f2f9726bc7f3b1d8253a98877bb5e20d2e8bffff9445cd8cc56af3425ae0901f98494678adfe82e45 +dee8564ce74a01b800b97ef020ae40870571f9d4a4488f9e11bd5ee1acb37310843f065d3b50a5e695104bd7f7fa7b5ece9c5a930c81bdea0c7a140e7e107b6a31597dac446b1a10a8030c9f2de9eb5552b796635e92ec9d98dbd311bb4d18e641d294fdb9dbe3816657cf9980b9fe4bcdd0c1015bcc2053dd1b267ee26b40e5 +e67803f44dfe83b81f73a9b99ef69b758f15a3d108f7eee2ab55e9222ddb611e65de2862bf867fafbf04af4e33e80c922296baf1039d4f76afe2537f988d17e16013cd22dc33e6f9ac8ba04f6f166c44666d572e2515db1de362dd2d45662f5c87b148faa1bef73aeb27fd75123faa6c0ba4aa27dac96c8c08207deec65d32b5 +cf02e0be2c3370c707986317d450090c72ae8e1d3b2f6601a0b2fd62eb7531bf9b883fca519362541d176388699f2ea0434920f11c08d418701bfd0fcac8cd6b23b123551d37d53333d361c5135761f4edf669939f5e9c932525314e623356c155570a6d253f78d6dc0ba1865d94383239921a3b2bedfde0181581dc5b822a7d +df15be4ca79a4659366701c3d33bb1b31a337733b1c5bf32944f501316101d9aa23b0c5426e90b9ce7abb5a489d24900e4004512e51ac7af4aef6a3a6845cc1d82bc4c905d1afede8a0d20f7f38bbd83e9d63eae6dd7d8d69c480661d802890fffc1dddc682fe03ed0241c130e8b2d00a6f8f5f2c99b27c852d6b652a2e8039d +c8c4b2c92bf417257ca6a493b1dfb3acf2d57e49f97a873ae34727d2977d4fc339d36017b08cfdfa4a6a544088ee3034d96eb9c050857eb914a2aebb5acfdf6632361e9604a52559a831db5013fca0eb9381b6c40e421064e45c534f7a0028a3333066491e7e1b35cf6aa82389e47306c7773b745221141744d723c85ea2cc75 +e2acbe5b77c2da5bf137081388a217e53e6212e847903fc2263f6c099a7ffabe54e8c200be0f8c35685088f32d6f4e7067d36913854c064773b4fd6dfd0aa7b56eae581a5c2ad01f25b155bbc88845c005fdcadda96dfc95b821e15889899d2c6a9c35bf5da5ad1c886dd62c25753853fb6c1453f558f40171c33d73ae410975 +f1e94067ccb4c0d8465a57da3e31f3d0a77070bb82f7253a2cc410b8b53b12df6fce41c457e3cf25a86fead441f3019b0220f9b62740dae8a20620843536b0643ded83f214ac93216c93e747b6a74762798038236778fb0e8fb2dfdc90002995a129ed27eaf8fbbee89bd0ddb60f56d30a5778078d4d61dd36ce12afec488bfd +e3e06839335402cc2a140f9b3da84df94187bfe716eb31e4523ae2cafeefffebf62af5e16a379c3184afba15da90895581eca9c4f87f4d671d72c384121a7999200c986b33cf89692d70d171eab1ae7bec2773f243339ece696183d76f6f7b4038e5bd5bb801f7c4c9feb25f3a492f5073efb161564193abe2be7f59f2e1950d +e6eead8428cce555091e3adf44d15d2e312fd5692cd4078930f4820d2cf47c5a040164967b629b0ba1b2d06e16935671f608e1b0d972d070b9006c0a8eb248094b9afbf778c89cfb1f80606ac5413e2e6597065eb0bb0869ee40fca0cabe995b4f6626e6b89eaaea90f5f48004233e845e291386f3d298b46dd5260f618f0f4d +f1985216060ba19e8e0b77afb6f04ec35a7c7cee04f9df6a306b4da8d76ca5c92cbcc7a728bb22ad5b24e6d1eb1f7305fb8a7b41b747747503d0198c38df69bad6c6aab06d55adc79537c6d8e606620b545864e3394dc5c0d1916346cc01988649d186c48dcd1937d21834eb194ed3ef03f2d114770632f4d37a14516cca2bed +f9c9f2bd2342e3c4dbc5894122166fa39625cf16ffcbc84a71ae8145fd6a66e1dcedeb7e6f264209f0c877393f21862d81d2c759546f0fdf8c41708d5362a4f8b6111a3a7f732af1d187ae1ad693117b0d5dabb08f64f9c256153b160d4a957433a7bd780e9956d7e175f15ecc79c03d3fb0698238426c07337202a32771ec4d +cb2d386ba2cde8f38438f2774b54f75a9be753407727da7eddb882beaa0fcb6225d1095a980ef842d06a3847fe772d745b2e615a9720f608fea86281a9e041c38628b4335ae93b446f19a495878d350f9664e3d4b80b153e67a7d1aaa73728477889de9cbb6de0bb7a9537d7327c49c5a99bea50c96c9d53eeeb576010979d0d +e1045e327e0b645b22b8703682d618317e0b96aaf79a8378e6e670c56c6c0075941a48d4301dbf66968588f76b064fcf34a323846b09ec18be65c744d4b3b86583991b8facf99a247fc84078c53a2a0889b573c247b354ddc50be0ed49291762334bb34f5cda0bcea7ce61087bbd967df9c2790f901e9c135847bdf766e3c0d5 +d17d7c04e0e73f0af70e8a681fe57336d20b1132167808e6799fcad00617671dfe513c63c38a1ac28b390a16649574cbe7090d09aea1e9cbb4542ee30532098c4e0f5243491def547f771a6da3d741aec52e6e346d8018ba95d2ed500f0ce37a723d3f99bd8d1f0abdb72af353569817a815f95054ed2bb94a30b96e2e83cc05 +f521ce6d6aa5622862e18019c1b08b71d313a12bbe15f27f6d68cb14e31dd1eae4f1b5b5c85655e685e1885edf7548bab4f9284804cca5a874ba1750a79765e6d74e6024239431045519a5a5a6f9326c5d2bf59711e52785d17cab0ac6c2bc8858fe335e2bd6e0a54a39fce1668c382a3d8f79a48d5bb0ce7bb448f0f46d265d +d27b0a46e492d31872b6d2670921afb3c11964a113bc425a2116008cda9bdee4f2e1f008a96942089755c1a90a80f62c18b3ddec2f22a38971b63f3d9e23af975a8c51357699f7b11598ea7b877e6f0901215d2e8df364ef1ab0106eb0706df83285bf3015f12ca98688c8d919aba4d630516b1211f8d6d8f71cd99d0c599cc5 +ef72eecba638aa1f75b33a9d91981f780b7f5e39371e020c8e468cf72069891f8bddce8eb86075dd459de98a0cd65af719afaee5553fc68b00f8dbf87e33a620bb767d8740b69f45406bc8c3992525c94b3feff1a7d3cd91be83d24d767ef4836d01c62c74ff946ab61c5e5d0964d63c5c42ea56411364d7fc4a4b3dfbc8e495 +dae2195ea4ffc79e7a0b70fbae8d74cf51cfaefa30d15c0e9a054276037a4504ad5efdb692ff97f0c456c3fc66f21f71563aed455a1555fed1d8f7fdfb14a126fd475e582e187e1a8875e25f7959111afe8f141b0bcf641367f672d024a286766d902d7b48de1e40f4943a07adc20866cf2034d32881ff1756ef2a7470886c45 +c393aa8773a783ff411304ad3000ab42d9dcf488f3c4d6098f49db82b339ac6942288861268dbc76afd12c5f9adf3f61ac685d5ddcd80c8a3b639fa59e6a11c425cb4f90b01070ecf9810e5468e8fb2131c807d72dc96528fe3379995c383c36efe88d7f1bf4706fc64f775c4fa9dbaa738ccad52004540d134b51f912436d35 +fe6c3ab23ca3b1624a56147831d8ce58edc77ae99d6db6f17f889bb19f8b87ec488ecc4d4370a26978a553b5769f5803bf3b14893ef6f779dcd4a2a0a6821ad61e63a4c8a41a58ad3c1c5c62e72f524cb63992cf6100b8e7e8a1f9666d932f4f29724a3645cd20727a615161a08af2689749bcc601f37912971778b7d738c62d +f87d31fbc3fe86c9f4e6f2d6e66044b0b3cb461ec9b00c52747b9b7cbd1bef105ab9cfca392e807db5ff40103aad29641f1d0408eb4df587c8c44ff78729db10fcd76f56a475b3f16bd28a7497a939ae2afb8ac23a1e89ac29836dd5bfef509f23796a4038c899bc0ae2428cc3fc34564f4a6841ae920a50f838000039751d8d +dcdeec99b18f7d837ff301714ac199acb2d8c3e73065562d6635f6644a72693bb098d56865fbcb921eaa8c417719c634b7606cc0628e740f051677c920fe386d546aacf0de236bacc0fe221275566ad92250387c10cffd555d554fedf9ad3f7c4b6cda017a7e0b8040505483cc64285305438da2d50442295e4d535a8f9be7e5 +e786b2c5dc7d1439be99339d198b15b98cfc3f718adcc65bef4fbb86be6dc7bae3dd12734960659f4ae9ad6a45c66e0eaa6efac0edf6c0ba72910e1910e9056031b3f6c58538c0b24d5c325d7c1d50a78479f7c7f3e9cef501171ccf45f455e71e91b6f49f4362d0fbf8d8855da8574d888fe26398160323a45668f8ae62e9e5 +d960b0d7131cc252542f29a686a2e1b9b61bc5ed2da6208a6a13b461b4cd6606505b4d5e640dea6e11afcfc5caaee218e78d2026a5b44bdbf3c8a77f789ea841f52dcf2fb86b0078bc643341856b1d2fe18441247e58d03065f96d10e85620a24adfaab437b5ce2044f900239d868abfd09ea56434986a2a3da0e34fa1f59f85 +e9f06ff9af989968099c7585ee539702a91b9966cc1c0f6fb7c7a20f32f37b12697f6afbf0b27e16187893bd40cc24f53aa4265c2195a0f08ddc17f755c4e3c76a79d1153e6a2d99bb9ced184b4f031e7dffff14ccea6b87a30dba589ea8f65970af537297e40c17a94a4aa4504f03efbdaf05b38e6015a1102235897b4bfcdd +d76a70ac8966f4c7f4c87b96879541d573b2806996fb6a8a2054d8b56fa5a19e318631afa4e83b18aeeba714eb1670d95dee8760b9ed0f5ff17b8bb75540b5011405ae8a2da96d87cfab2626983de17a890e424a25b41401b990127d0cc6b1bd786cb2786fbe8080eb18e98558894c3fe383c7bcfe897bf98dfe95521fc12e7d +f7bd785aafaf4f2bdafbf38235f1e4c730e9ab5dc4fa3cdc9b9ca90b06dd9bf8f382ed58c7b0e34cd51c2cfe7b59345aa10839f3345cdab897ab7f553673e0bbf69ef0577ffa2b758fd2a6decb7a24d9818227aade82801f62c453b95e3087d8992b435e50e01f83a4833967a8b75ce37a760fbb6cae3e99bf89130e1736755d +cf26ba352f678c227c25e5147ea8de4bfc4310d388530d9bd3d0dbe0e9254fea8483ae00f5f0ec81dec0e42f5d1bf948b8519ce593737e318925de8c00c0214247abfcdbbc77df57d750b3a9c160b39ad9260297f7fc67ea2043d190bf0765a4fab4c7e6edfc3f08a26311de00e47b879b338097574e4a377e2387ee9c525085 +c6064c4add843be7a9bcf2582ced8aeb459ba930241002cf0325da5a2d2e1b5921479e50b688b069fb82929469ffedfa5a1180f7a9a6168168a64ef917c909a7f4ede94357203f1113d0c6d2376a5e39d37482511a67ffb65832a4daaed93f86ee9739b42c62368eeec1434d6c42a1fe53c5068bbd3ff89549029410740cc23d +ed15402f355404533c4592b42d93de22ac89ea83acdd01bc6b742ce77761a4743fdf9a069965a7d19e86218f8976580be43e4a1d978e0722668b060f95c0365fd9c84f732ea445436040c8ded47a88ed05172d17b46e92d26ad56afc99d0ae216003413458b2718cc194e559c4f141e593996bd9888d57781b5aa33ff1b44e95 +e83a24b27cc273db2414f5adc3d70f5b9926dedf39b4118298c908a26fdc0817341485b2c847ee98ad34afd0a316d32c03c455474f73261bd3231e08c09de84c051d1efdcfac30ebe0da967755db47b13928966f6d0cf348e24f6831a30942c3b971e74fd33a1c6a0997a4db9eb486aff9da54af901149106b991566d025ed3d +c76da67e64ab8b60d859753dd47d501e96d45772eb72cfe226dfd6a52d026034f4ac2cd8f4113ca8cb31b830eec8037f94e62273653608152de1fb580f247e919aa25dd58540902ecb868f5e25ab6deac6caa4dcd48dc9f35cf1939f1f22369f98e50463fbe9e368dff83cf27847e2ae45433c747e848d995fd26d68c9fa541d +fcc228f8f74fcc29e4d70f6e437d258e34a9db8d1af38d951eca478a66e56a776423b54d636b7537f46e6b1ed1d8e19eb03c865dcdd1090db77c1da5a695569f4f0b85d554ab61c8f65d04627da559d43f5b44962d38bf3ee742b9bcfa2db3e9120223b31617a631b80b1bec2cd7d1837e452d6307c4c1c7340193075fb2b8f5 +e076473f86fbe36192153f0864ea831bf5d4dd8cde7ed5d7ac0dcedc4cd36e10edabaf12ac16e9978c12c60506de560c18e3cc85ef2f5563bcd434f7c16413470f9dadc62f92b5b01328bafd9d7a2e5e11db723a8002eabc1d116593ff975a3076c679e3b56734873d51ef99fff20a2f9bf4f06665e5e84852f7ccd830004785 +cf9e7ba49264fc3070173fce670ff6ff6f11935237a557b68fadce974bfdec70b65822bf865394873da79de16f4e506d244477ec4ccdd1851e2cd2713a0075835fa59acbbee79d2439e4d1656328f339be18605976317882fba6b83dd21fee67c38217132eb115801ef009558886804327c446ba577dc93a92ad71b2888b825d +e8ac898dec76b876eaca1dd3d5b73ecc8a0c06e04c15608b1cf9cd20917d3d72135dcb29e03c59dffce2c2e77d345c8232c32ed8f5ee58d08ce2396d4866b3b09611f9d7e71a3eccdf46e17d84c53edc861575242f6301802fd237359ee6961bc32ae74fe34cf64bc0513cf9635d4f046d229550674dea3dfb7e7dd7a5a4441d +dead27581a5bdfc0ae2111f2bbd3a2c6c0bb996dffa395b4b52b9c30cded05e6a363d314ee9ff024651dffa44aa3ff0beaeb736b1688d9d40fe3e0fef53d4ba6c78446306d6ab6d014af2381df992a526ed354aba7f3fc20e1edbca83012e40566e121801018ee1ef32ee9687ae896b9aa2f3ba935f199c5eeb7953a5eed518d +ff751130a3aa372c26cad01df7083187cf5f6b1604a98f63687846634c3a6676515b839e49454eb344eb894dd4d9b7ac4de7bd8b8de3aaa103b45124e2477e17c0a44bb32f4afeb2522391fce1ba6f89f5596ab338a8cfb1b64848d517b96b5daa4c164ca5275ad8adbef3fba0f0115303162690de9f75aeaef3cb2a831c98cd +dc93f8f672332db4d675cf638d5c3a9b9ab7e28c011802aa30ad61846ee175916a5a9daa13ce7db426209cb409fec83f26f9f4a653a1bfa1c3e4de7318bfd51c182177bcce23e120c601d67f21b948060fb521f1a9cf4a5ff5a260af17dfceca586674084a012c3156e3c59b346707d6511b7f4cb668fad7e517e247750b98e5 +c82bf0ea27c2be5b5b90850621a508016f821cec4da9dabc473fbcbf0cff53e3c13fa3ceea1c851f638ca622d30dcee8519b30e54bfe7f2021dfe08c56a03323453436007a1a0d5058661d0e83ae6e746e27a2db8bc0390eb94a45fb9b3b6ff21f0874805b748eece85a21675b1bf0f87393eb64fb480e776f209dcdc288218d +cb01ab004a455102d265024066c28efbf293b3b0b8c5b97a2adafa84c2f0ee25c567d46dcdc4efae7039917277646f2baaf507ff8bed41e88aedadec96a4cad46b77af2c7ad24bad0cee415ef7fe59c5459fce5334537265db060fa1fef9526d21d4dc4a932c3d27f018b988c51fcc54b3d45757aa1e782e43f46e6630ab7525 +c8fb98ad46a049845c83dccb2c7e31b8b16ea956b9b7f30422ff2f53da87733b60c801751ab394d86449a8270f0df4fd25193d886b8ec7c2f2a4649f2fc39640e8634155ecc8a1a191f2688c1f5adb42843c9d7c73f7379082ba202d30b8ef70a397a35d26e9b5e49d4e3cd87dd6b051a642efcbef58b9b5cd05f349fa724ea5 +e154fd4f5be7bfbeced17aa18baba53f19837f0c7964be61154aa49c6521c9f38d7619877850275b9573cea6e4114fbafd3db7ccf9bd2ee023821042a5e6d8b242f9f62a7bb601d1118573a628bdf8500cec45235d7fec639b5795af057c863f1838fa15eb25ede388c5f7ef53eb9143aac8aca8d19efa3dca4bda97feec5e1d +cdeb910095d2e9345ccc30254efc1fa238ce11958ed30d24617110b02c18136b832293835d0963910d43d8247b9a7b19c49fb479b1717f4ac7b55a2888144aa4fa7360f9847fdacafc98097e7f0b3468eaf0ae8966fb279f1def69640794a221123168a021f85d8d79ea5b1702c4123369d598a4801eaa973dee2741d915e6ad +f8438935ba59e7c95287e5c2ef5d2950e736f74e6fd622f034c28beb3184cd9c0f62cae993bd4c1fcce3ddd5dd0b8eefe9fde9165211685907b55f96833ec32b3398a4377786ffc5b3b1222345b488662802bf89ed7a5b55edc639b9f941292336c51874ca037d33cbe951cfdbbe876f5fc995075cc4ba44e3f6cbdfcc59deed +f37918b706b8ceba15db3e18cde5d888270e3e01c5d5d10b5606f74047587671ed3c5dd0cabaacc3091a82786dc9c17956eaa129196d8b007b4f1540c520fa8c75990c7b63603a56a3200a1ce9379fb536cc2216c87d850085145e230c959f1ded675adcd029fd57e32c762de466b9921470b357ad945d12871bd9a3f25ebcd5 +c5d4d9b311fcc1f9d2d2dbfedc09c347d5d793746335a7f8db899cb6cea019cc66bb50afda64b84c2cf10ea5b72669c598924b1c9735c7ba423bf07c06158372f514e687f5284979231805874905cba2bce044615ebafeb93ceb672b6527c20f48b2917b1593c1788506b75df278881fc77a7c0606093bf65c15156daa594d95 +e63759f9d2559b33c4620d6c598532593b957d232b873ea35125cf190a1fb78f3a959a100d4892ce47b87c84748e1a5b232cdeb3e404fd6cbf19069f2c8de134458854ed4e07d95131af6c441305ec7a70a96058a2882af2a342a626430725a2adb448b7bde4f4adc0bb275e33b44a97d2e9329194f1c815ff57428a1e2cfdbd +fb6dd7d761b3a07b56cee7abf128faabe4ffe20dea8169b2cf74d3dbffb159d3e085cafa59b779433e89eca22895833c491d2409b1403a4d6d437c405848627065dfb0a67ebda9458e474abc9aad869ffef2399abba08185eba0f7ed58822224a981e28b0e7314770357de626418235987b194e8fd6dcfdd03194be656fd60cd +c204ef8a6c39a3f23133957bce1fae1158b587cbbd73797bbbd6e37aa86d747d0c8793ca59b658e9095602aa882f34cdb9a9d290ceaafd9d2116a15f6cceec75b0a150cd215d2e9b993c5ed17fc829be4126afcc9d85851e5d366d18b47fe90edb38e72262dbcac4bb701128c8a2d942b0f110c41d352578c323c1cd7f8ca99d +efdaab310cd92e5a7dd0dd4f5d387921fe5e18b9358d1f8d4178e7522f2aa1936f9bdd5de36ed955530c65f059aff8d177da54b57281266f188309a037533d346788175022f8a4995692294dc2a09b7ae1e79394b8cd9c69033ef8d4d07506fd0dc14967649456b8afbcf480bbbf1c6bb7d2bb0809c88c0f8934fe89b572d9ad +e3f62e0b73cb17ec33426c5a4ba8f4292e03e677f5d1617d938e93e54304f7819a3921ce0df73423902947ea2b4dd162517380dd48ce63f30e8b7df9d7194e75564e72b6809056d6db1bfa62b3dca60f7a017291f8742477efc7eae8c9e7627843726fa4a9fe78dddba02d9814844c6e8c54febd3e2158971fd4779f725f68ed +c3c9197fd49de1a9aaf1ba03302a0849278a6bb8460afaf38952d542cf7afe6f8c1fc40b7240ded7a7ffcb5b23e4a5b1507ff7400db4d19fe433524474e21f9673c88c098f9d033c191809e20617dfd5f92cbcba5c8dcfd6652551945d1eda48f6dcddd16ffc05dbe4e3cdb163de9eac0726b7dd8ca388adbe54858471319325 +dc87423787fb3776d3a38c010062304d7fe6f8e4bc5fe8e66201d9bfd724b601c5184ac3267ddc8cddf3880542fb91ac077cfd2b7a07b49b90a550f8445c1c68c084a77321c0d03d005c0e666a9ef3709c1708e8d5f5f2d82f0ec81a2de2fec456b22ddc5243b2ab19f64a74bba5fa6c2a091258c5916c2c49d2ea7f0a5d084d +d0012d2f905082a4516b64dbadf6a52659fb6065fa31f6678c00941eaff3d1e2423a57d9450fb53dbc956536eb00844326cbba017727d3703cb3d83d7e30510a25a0e3a030e4083c747a9a33cd8d8ad28d2b249ead1286cf1de4015c6022248801fcac6320d2b3aa398b7c4ece75a6a9d9fc19d460c657b9d9aad6bf7501a5d5 +cfa3a9a0b42aee7cbf76b882f2ef49bbb53f2eca837a6660714545c6a5988a819a82b3ff72f96c828f385b34fad06e52fbfc3dcc6e43ee9dafbefbff3f5111fbaf795e0675047a21a64c717b725d027b1df53e680d49e2a0fa68975b8d616790535e6da217b58ee3135043b13f96d9d9f14ceba25c80e698a8b6e79247aefa45 +deccd1e4355492e13408d4a9ef52ede6f736c3bb92c85bf76f264a053308a228a547fcc55b2b18c52615030aa71bc0118f259e8638656f0f912352d44ea4964ee652ad4a277d02a96cc6128655cd3caa276eb2622de7a6dac84bda2300ba6ed68d5883db8f45d88feaf67fd7af568c09dad637d46cca9dd7164913bc21dead4d +f05f8e610335c370350f81e7d3b8681bda7c0e51293550553b028ae6937115061873b575b342c8e0eb16e4a94a7263d405fee689dab1e304651abbf9dcaad9cdc3e0b78a78d8d08ac7bb3bdd54bce8609b4a741d10540e88402f74fd20a931b098f83e08efbf73ea2cd91adf599a06e3181f369cae318a388bad70258959e5fd +f40bd808d8983b526dd32870db7481b0026292b56b3fb937f2ca04a20063470f46f6903dc5c323dde933fc6df412d04d326dd3f2c3c63cabf2796b3a414d953f2e164fb4d7120cfb555ab5499f097ece48afe978a71c8a43deaecd680f181087b380da2186afe81e60470daba27cdda8f72c1e2013743ebb8c7c7836ba0d9a8d +e0a5aa6ff2ed1f0a13e9f226b1d3e1daa1dff2a82fbc24c57f4364ffb58fa6dfc3c146aa2a0c86fbddcfa6255398f4d5309e5116b1e05e044ad396c780312b36c56065218bad573ad88fe687502ff51a6490c10b3afc0db2faed8d282fd91f6245ef83361aee9485da9d7cfc4ef91c9d98d6ae5cf383d8513621290f3dc4d42d +ddecac7bf804349baad7591ee4130b79bc255a87ce3b3af695c7ead8676ddf607832ea50bf24952f57c1e19ace029361d94e6891aed1dc095d784e4147422676e52967436cc963af0914f335a7282cd7b73188996c01113d78ff465b609608faf2d1356ec58044f8b9261cd09cb3b8449805ff34abdd257479dfb7e699b9d485 +ed3e5e09d1a52ce643f484d223b312b47511c0b5fda60bf8081b69a6470f94713661b098f744fd144c1807d7bcb30117813d4d6f506d232c216cd0cf81edb59180ba2bf74ddec8f7fc1bc720059253b36ddb4758fd39986a097cd636c80acd11529b24e9dec6d803e87cf990cd6add1c6d5f16cffd74896811d05d324bff511d +f70b3ad2f71f7561b3afb043fd85a7f703416a0571aaeba91b098d198af0d19675245b53c0b9839b8a3fd4ef37b95d2361809cf72f0d057654ce018c9e9c9222419e636ad7b7b28ab9d0deeecc369869be70a511c15fd40e8e9c35858e04db42e6d08614d2e98754d85206ecce8121305400c3a7eb1be090406449bb100efbb5 +c3cf52f73bb03e184af1ee941c56e4e3e4280b78e2d7b3e9beef82f896fa241d58b5a260b8289708c88ffad26a3e907da16bdea925de7c61de15b0330f717b05693423ed0979130ddc3771d424839262f4135e270b396a899e40ca2f799a7ffee07e65ed428b4a5069987e010754b3455456c28183c46ec789095938d17be055 +c7e0b0844d2d1a7a638dd86b30ad2bd20caad56264c4ebd10566d876097c09785110d4238146ee0a1a219c9247a61b4e39fd342546946af09b9820bcebef102efd8be1b51902acaaa5509869b9ebc5510aa656261313b7ec6facc4eacadef66e758d976ebd613a1b42f0ee54a1fce38fd0096887427f61e007f9386f576b9dcd +d4149cbd1f05bec65413f244f06bcd0ee3405e734d57cd023569e58f8da93fda5dd2cd38c2ad1ce0ba131e9fe0d5c3e898699493276431ace3adeef0d8d74c0197144540adca344003f77a92dbd168e0320adfde66701df5c71bf439413a61693ff137447fde0cdba216e33c78b73e1c6ed6fc813b541041728688edf87dfe95 +dedd0cbbba3cf035e2aaf743b0328e04709b0a27089553039a37c46fc62664f4c3d68115e470517aa546cf3126a2771812ce0c0c2631f435f25966ca88e37eb49faeb9d345618de4403aaf0f7af33c5044e806f423b0eb9180f6ec797dd4374a08d3ef625377787203c5267c8f9014d32b575d124047da743681ad677303334d +fc1208a5ba359665264d35fc6bdc4b23a187ab3fd587a2cab5a3911b708c68dddd57cb150ae32cb3262393e24df810c38329a679bb452ab0eb7a70f7e679285f75b83dcf470390537e02cc0c83cdc6b45c0773589b43b144797ddb6336a8103365d09dbb1b5edc2e5d568079234cf7bec794e952b8f29944ad66e049d2c632f5 +dddfbe3908d0c1ab4ba1c000a2a154671f440d059da2d700a6eb8a46f43c8dd870f352b74e7c96aa4d12e7c7ad5793d3adfbaf242d1b48b32f7f51743f6222ad8f7b7dc663b7e43dbc4a07ad3be5694744e9971d156afaf090053fc24a26df63a664728c0c844b95fd2b46afb22f4d8c6e97b63bb0974bffc3bd146ebddd1fd5 +e0efae147c20338a15d2fb5a5196b402cb9536cdefadde4cc9427f5308a7693768461575c1bceab20e9e28af8f5ba5fd2c981065650f4adf4de6a460c29c395130007d623ca2409a9badd4e3bc00a1188fe3a19005eae44cd36c201126ce72e229b277b220959b76ca71005bb246bab998937818c238e8dd778b0058c28a7af5 +eeda184c6f8397794c654db95158c29a0957b5d273d1d44432e2d212e358592226a00b284670c7182cd64899398bb9f19e4b987ba7ab08fa09d8fb41504bfaca985f1da13792f5b44066c36d0036aa4d48b35ebb6ea6cc173928ab9cfeaf22a37b540b39f7cadbd66e91fccfeb084d2009e3eb3e57885cfd00ffc0ffcc8b2695 +d85227d4f2889dd7f1e39c15060490dd7bc2ccd84f4f22179c74f9240670b68a3a0a2ca2bcaf3ab7512597db68d794f20313ab1966704897b32fd993cc8a656467c66dfb00d50991c01ca979cc35ab7aa0ca463b31829a852f51ece4076fb8fab7d395445897004d3f4d6fe07cb4f9fa7d6bfda3bef6c1d0c4169339da897575 +fa6393cb5800304597bcf745fca14d20d93de2c1f99f3909511dd8b9ba76072e55aca206b562721095499a302f2c77f7ed31dfd4b5011156ffa6e9f96b45bfa1aa698a863d342561b2aace1414171e8280d01e902eea9b2c476465628168592550ba5204609a5b7026d86e85b660739beb085c29f7d9114df91bbd25d59a5175 +e2b832d391a032e7fad9bfd592bd8dd27676e2d64a7f237b75c3d33e6a58adb73cc536681969d88e768ea287064d87de15f93dfafbe1cf0fbf80403793b739ce6aba95b73312b419e31613c2cdbafa65f34393b14ca52ef3ad565e5ab28e4b12bc38199250162992cd29ee558f2fc41f448ffdff6d34e3e259a413b22b7dc405 +f9b8c2242b699145f93f38e0d537eea01e147321aa2780c0017dcf4db6d2b6157964c62c5ee1f16630e3c0c5dfbd2d79fc68153875f2d5231f64537876d7a2d353cd70802c227655d7afd42dd94ccd79dd123f86de8a524c96daacf0365d4bfcb3b255e4598fc119938b151fc7ebd4b6fe6188298ab65853acaedc18f1f580d5 +ceaf59a8150e25e680974300c03087e734c24de34a838035f6af22283da5bcd9c8c76f20a049d26c85efe92928ed12d2c8533d139a867b380697afde7376c8f78593e5b264b224777d525d4d0a62b39d4fee7c4c57d1b133bf181f7c5b3805b727ba0630cf28d48d50e55a05c6735bdd9e90e4b98767aacd8cca543d92239cdd +ff090e80f5837f43f9f393a2cbbb87657185cd70b0b7c7ad681480aa48e8f5d69773316c3d6a9d9af1c8a3ce9aed950484a5a77849076650d573de967a3e9eeff5f70c3230014e39b1b8ac200b8618c201d83893185c07e75befc0ab6791cec4f0523382b1047bd40296642135d9498715f516693c624c0ddfe0bc67506a59fd +ee12ade07eb483a078532f5940b71a9c71b2310d2d49b56df3b7d2394f75d1495be2ccac175a52b9228440dcdfae49759e38b5af2de3563230cb19c06acd0570fa3635548770ded37e0b0f42293e978d1ee0b26b8c105c78e3ae12de17f389eee748aa8acf91814043342df26391c9d9e90dd117198e65ecf9c1d1f5216dc9b5 +f33b2b58ee5e248e076301e4d4b77ab9c9d702924f95f13b486e272da16fc9f57fba60091c79a83169fc8f08082875e12acfde59d10c8ce9944bbe8e8876b31645e074d501c146c651fba3ace691ebc0eb18c57fe7abb0b1efa3b5a497ee7899ef95aadd867f9a988cc28d9d01a163ee0dce6782ae0a45409b6a104da6805865 +c15736b1ca11e133c090d5a065b53cd82e2979f44cb93bba041735f4fd339955f1e9f2a753f7100c8aa7d0df40b908a74a4419999c0f4736021aa171c3bdb5974aea4ba1db3ede0b9ec3ee0eee5789dc24a6ae4bda8c5559b148648b55e3a6e2412970d9c2b0fe767fad196b130fc88ef383f5511000af8b3f74c2cacf165b1d +f16569ecf0eaba1f622c8caf1632b24735afd2684f3a7ac86d8ec679c6b7fe9212ef216d37c210d3ca5849c167ce063d006551d85e2d40fcc12265a14467d8d36a67a9972b7b72ff814f90bdb473f0c8618a22023e80e66d6e0df8e466ea995fef20d4127fc3d690e803e4ff62ac8f88f7b384a7a219fa8b116269152c2f8d25 +e70c3cb7b95c703d9f9e4e5b9dd6260f3a4cd3deecb2b7632652a013158fbf0f5e4b76287410cb20b627737fbf7fa92f85081a13a00a3fc59daca503576d8b449b600f6d9c57dbb7c674309bb1c4af7fcddd90085b68713ec20c766c442a0ef136731101bcc6b1b9633ca344a590ec7b1ca611c6b39c161a4b8b3016ee7c615d +e94d5b4afc86cef446b4a4b0004e08f5b9b94923f907fb015e4959406d03a8acc13a51d825b3930d5166ae0054622aff440eb6f2550d51e7ddcc88dff5dc3a8f8120cf6c4a2f3e01902bd8d86c28df484e5b776d31d149a47a5cc842220a608eb0ffa175eee3eb3280ef12c5b2a05025537abe234717f6e2aa849e675fd4aab5 +f8d4915ed06d22f36675aa84359e1a877b369a6e556371b59690ced0f4d592860a78c49c6bdc8e7ea1a1bd1109dd8878d53bec463b3ea10801181ddd6257daf65aec29f188a706f0039003f436a2086922c7c1de0f251d6749855b0348df64308e0a0ca1fd0b7723dd4f35956662fe0aafd3d420ab19c5487756eedf86cf84ad +f35dc4c3be8a22c7466e1d8e9b44adf3e91601eede366fb9658446491715ea34524d76d8660ddf95f6c512b34d52c3cc4e6bed657cb6e40347d9cc46dc5404cc2e54dd8a370041ff1651e20a8084e45b7b73334ae12c19b5885fc8c626e9937dda3c673365f1c3fd785738c20c5723eb16e604f2a94c4ef966f577262c9e5575 +f265c490a6cd394199083e842612145ae5576ab26719701f92d9e084075d66f4f8eac3a6375abfdfe3197a79d45d30e1a6b05b2f09c5d884d495b1deeaaf3b5fae36541290093078308f0e003b9b271de921051bd13216bc16c996c1d73b3563a4a1e4c71bfb1ec962b10efddbf17c69b77621390a9922d63952cdd4b203a2b5 +f5727c0c1826e112487441c855f1225f8096dd90d13f152a49a737445d03c78115f963f88446f2c7a3335a371ad886150694cc476a0ae0ffc5adcc6368190813938f0744baf6e4def9a211afbcb3cd176166f3ffb23a48a793aaffa08384979d4482435d3126a5ede1665beae8426e0d0c12ad3c0077dbabca8d55ee87b8c5c5 +d793b9f288bd065b38d4e80160a7680af407bea4c05b90f40eaa8736a6a9bf41b2206f663b24205115c953f68cf2be1a2bc7073e17d8ebbf6897c0caa9480192c014f15a9ccf024caa11ef56fc35ca06870d8b86c41baa2fdf6ca00475dc3091d50fa452021e66db6cd58e8002c37b7b43975f23373ec4840ddf8c5afe3882bd +ef2744764125c14bd0109d542b600ff2c445c3496a21409b7ecb9dc0aee0667b564ed41d819537fdbdcd4781121e20c151cdb342facfb9ceb1fa39542b953d5db58ccd7bd5ad36e0252dede437fbe819312c6ebb9745bb0ffdd84f99b34ef0d323b89f12abcbc2e90649ca09372e226ee7009d2600109967ca0b55315062aa9d +cf3cba2e0eec4502ab476834d2ed47764c44d45c750ac23115f49c52ae41ca82f36930c92e424b8daedccbe1cd905068e43ff1dae15093217de2e42eaee6f113ede1e967677bf41b8de614329b9853d7725da58134bcbe055a73827bae503b15f7e1487e453b9252c34006e20127fbfbfe2a4c3d74ce558bfc1734e4728edb7d +c9f921d0852f020ba370b4f2d58dac49176eda353b7db9dcecfd73c1465d8c7a1f4dbede864d6fe320d8e5a2386cbda7ee51d9a0efa099b99b1128cc0ce959a1938fbb85ac4c0306068ccbaea4bac0f7ffb2adecf215237e23e7397b949bfa852c38ef97d40071780b379ef1747ef0be6ac7951f1499ae383c43b86cd34658dd +e49e960ab120ed0d6f3c980febfb5cedc0076a4f3ae7f3b5e2cf9f9ca783a390fd77b674d6c73a9ded0fc3f6dbb1bc91242780ba1f4edcd8098f207aec2ff3bacc6694b6bcb94efef3df81a99c4db1d89a7d3981671d0f2f62a4b1900c0bca3aa690171d04a927183d381e6c67fcd7147660dd43a554aeeeb737f3a5cbf1d635 +f2f9ec42a76b676a5951bf735c3398d8bfde2ef02cf24200b6270745316de9fd949c489fe43f1b4b6212903931e29bfaaf77fc0ec9b0dfc7177dc23fcca35f2a0cdaf82d8fe53c792854322f98c538b968efd86d805ba173e23dac68c2cd0e56796959a5ce5bcd195f89a45ab5e371b0d76b92b348c5e461fab00570740a2355 +fb2cc865e608517979f6f4ee62be241fa5a43f326ceac1cd90e6389bf8046a03bf5bece2b7d3772dc0babcbfeed93c86e5f712350bf429410509a680e4ae335a0893463c423e84cd4ec649bccb376d84b67699f84c8a7471ee4ebf7b5341495be4aade3f16181e21ea1624aa8fa48f266e3f358c5527e89cc2973d9d02b08d85 +eb853c389114d97737af83290fbfa382ac883d68dc89c47e38b5b252a23a61a29a5709aa98333c9f0868f72a3ea5999f4d7a853dd8f3ed18e1acbc0215c97ea23e2154343338d3451d4c5f656eec491f9c2f2c02d69d760be048ab96937bc9a6bd60709066bada1dd197c0c028d87367e0c02fc4a8f19468734a96f3a94eac0d +c70e66e78b8c832a330525acf00fb556c3fb9902e879952ba4f5ef4238248b61d9bd5a15f9346dc23b3cf6b961ffcffd853fc2ae240d50b53b4bcfd7f9c6cfcebbf84714dec1d15d3904b8eb506a8a7f5d5d9313d195ed2655474e1e54d1ae6311721475de884b5118accd509d9f734ffb051f9eed141c22a16a68c96a9fb24d +e9ea13afcfa9ec4b657cade8c373d20c2c13b4e76ac74d00aab43922bce4fd990127ef8e6d65fd099cc55cfc42bd19b9ade8fa71eab087b3231da53ea39ff01b2e8114a74f108757abdc22cb06fcb60a159dfd15ff52d1234f8bde2f8849a78ae24a1091793c69b1ae9807e065c70692cf7278ce739787daef86864762f7bc1d +c886f6a02b642d27c8606a4d933e9ffc1af53449324d5da647a427dfe244ae2b2b2cfadaab181298b07dc90c1fe6a900f1aa078edd86fcfb3b9ca8a6ac2d18248c25afaa1ca9bb3d391b15d277a2733f88df7eaad52d50449f085a1f0ea81948a38a7798193a52619428ad1cec3c7426d922f0dd8dd38234a82346d59e7a4c75 +f648344d2a961d9585105006b9db58df1a1c0bc7bcf2a7d46a5147a92a333ce3de0e8d2faf815738dedff3d6773ffa6134635601f9f4e491a184577b224f377c8580eeb35cbf8ecd1f069d825af966fd84cff6f2332e0fcf3c3f605d3b6fb6cae295c2f55c10d46328f8f8bd29d6489bce95ae1455771b0cc1c807bd74c1bd15 +c79a37d4b999e7348b78d0fa08b7a7dd53ac0b4beaf0c3ac6b001cbef235b42385075e0c81afb247df1d0b733894caa9dab15083c2fecf9d93706d1e060c143a5e1d2c865ea9f55acf25998ac3965dfa5b1ac476a9a6d559866c7e6b31cf78289a276bfae51a0479f899d25b9e08631f17719a5505f418a381def0a772b12c7d +d88b9c146f4e0199fb750c6666498bc1166178fc39169ebb5326f517181830778015833a4d98de263316f84c6af44fe81c6d12369e9f77ffc56f9d5e64f612bdb96e7440f84bc7f473a5f31684a1b32ab72595b58dbedac75f6d1808977ce2428f8a2ea87e3a50aa20748b0fdb93af66ee8489b00de02a62eeb1283154c8040d +e615ba364ffafd0b9d68d3a9de8600f09d08ef321235bbbf4a96aa37f64062b87e8959dbc99a9cc3a43b2829daa490a156e5a347f380b2cc88c2fb01847fb15982903f64c5eb91bc2c632500cc55534469709234c3f75a6a483b122c98f2ef0b43835d68b96284d81161f9dd7ea5f3317da0b8d6d945e8ca1ee2c36fc53b9125 +dd10b88f1b2bffabd48f5372f2087f5042668ca3035dd58325855d8030d3c2889de3d01c4c29309b4af3ee4478de119663358c4cd43bfde822ca3d43d85eb6a9df6702eba7b55321745f8364cf6a6f025bc7cd0efd724f8bc57858035318cba6f0752a04e784ea9b87bbe4c7b6989e66dc214b2038eda8c74866131cccb81955 +d1b0fab56306f78133d466ee8ef5f25e03743ce6f9f0cb3be4daba3b6d38d9d92f3eb55cc4e5f0efbe568da82df2d4a7e2488709ea08e71ad8d6c223d19292d283f77b7d226717363d4472e57c067790c8fb7beaeba402585796df83e7918f88a58698fbc428a2cbac30f09e647b2a36332d006bf1f3cad6e3a3b31bd88a270d +ffea786a2f25d52829f186a0f33580fbc345298a69401f144fddb4706962a2d17281ffd9ece1088b4a0ef43f186a6061785abbf876e79add9354da10ece0764c68a15c60afc504996564a7bff20623197d2e7e92e94d2b1716e15b5fba48f42afa481535f5c861001f7ef481434c7a3f144e5839313c3730f5c8dfd8413abc25 +c423dddf756797d8d1f4c3d65a1870332e1e0f38393ca7d1a1682b68570473ecb473195464fc3d5088e64e04196e83ad79ac888ff490f7ea240d2e56295adf29d069856fd7635069c9964941d2bdc3a171a9e56a67f1722ff9b3dd6493de3aa153db2a8abb97d67f7b8952c4224aa4b2be97616d53b3eee164128caf710ba65d +c9674f220b3a2c58df851f2603bfe4c908e2945bdf89df61d2ba9094e4cce5591c943cf2ab2a40ee664723961a0f353c0bdc5b85088e45d12a7d28336557eaa8224b843882b0dbcc3bdbd766fc2becf05348f2bfc85c4cd472a7bc264bd91ddbeafc8df06e53823d06ef34565439b13d76678ce32684f99ebf039b1aa422af1d +e78177a16356284b786a93ce78a6765e11053d69177d7aacdc500a5eef65cd61a77fc39b07e164684361a378bcd99862ed041a28e9f8ac7da2331907bac701652ac3d1a618d76f6d68781c82df15bb465205c6f4dfb1d7e42f1c2997ea578a37e229a6b8b8318a758ff684d58257ad18e955bcd99872bb3df226674858b68435 +c42eac8e41f303ad0083c1a790c6c79d0aaffc394020cb344fe105a9cc4190170b0fb5fbd81c77edabc17fc7c261a5561f34edcda4688369ec781ef4d486c99af5debded2faf3fa8e96d33999da49bab75e89fadd3cbb8ffbc2fb0f8481370080c35dd6aa0e88aac0d8955c5395b6655cb8bd34417f0686b03dfb332b7b39f95 +fcac3e16eef5fc593642ce3bc0f7dcfa5d3203afdf4d914725d22449eff9c53a56ec94c030cb793c870b78787baec6f8ba18c6e0ab83cc082db8f1d80a5ef4f19f4f775d0581b2c50928aa9bb552c7fbcfc26ca785df3580e53c649690cbbef9d3e9b168ba18ed4ae9144813c09c9212df7a25971e0cf39d16d0ba3c31bea345 +fe925c06ec7371c2e002573af3bc6bdd0db363dfce1fd3e1ab46a53e9a85aca5c41a00a91294bc02b8f672bb001c81f29a04d560dcc1c83a7e379be2e6863a97bf82dbaf58794885d0a5a08344d313a5966a667a7c9149147b90e02e043e8474d8097940d6208da26f7ba17dbb94ed8ea730610a27a6d19db0e852f25d3788d5 +c1552629fa38301034c6cf7b05e27566ba9f370474bd4efeff1448d83d288144dc5a088de234b311c8f9182c4496ebcdc279e299a654a4fbd5ce9d212f4af82a6f4cef9bc22b4e2f05beb2820843d69b1e245da30f501aea1f9c2f498240ca5895939c97bd50593b019e1b05a0d1d5b3acdce7848aa111626851878d2dd3d0cd +e74a8165276844b8154a45f81e0dc7dcfa55d19fe3af14a87ef0c39ae2f952ceb9370a1ccafb78ecaf7278b4470dfa26cea6e901ec0159f3a3d68ab0022c8c2c118511dc681e00fbe2a03cf9431e6ceec99f76799053f1cafac04e94895ecbb46e760d058ec20bd9823ef56d4f47bc67c4fa449c3b795a37f0997e34932c359d +c8326eb60037bf4345b9da19d4a4b0aaa618fce1b1b636d983ec5ca5e7ef9a48295d6fd5d2805d38e311b974f69a5dcca40db2db8dfff01c563bdcf623ce76a3c304dcb8e21df3a5ebd78abdb4883482ef0e99b3ddf998812075be2bbb73e53bc69f93c0c925ef9574c42e660d94375ab0451557934b5d93e9ab5c0160a57b9d +e325d4d545d0c461d1e6cb5c5c88b2fe5a6498a5f4f33c3ebc4251f700840fc0ce8e6b2c5aa4ebfe0d47aa64704656425bdba03ab530ad347e3868af9d32e743c085e48550e64a8846eb6b3e6b57e23c641105153807a22c7b929461c47b98b9980965529fc562c0c40389545d9c93520839165dc9f5df9519812ad49f344c7d +ef6fa5cdd389e45b4a5d2773bcf6c52799445e1a00b80e0af49bf59ff9dcc0b719973775ea3309190f9ed31d59c1e04cc2fc46b5807d51bc4b72e86845de6b39c85c809db14229905bc93c986047f127e7d2ae02184b34b7af72ff1bad7fd0ae398e631e095dfc480592517459b551829f814e989caf154ca40e57084b0b19a5 +f554cd79a676ea46712f4e3d592ad62c12f6f5699ecf30f65046de91c2e47eacdfba5ea7d691802a9a57111070daeefafd93a3efbf7a483f58fbe8bb3e38a20de9c438f227710407c9ea34d6cf304ba18e94f8cd18112bfca6bf7256c02c53446a67a66b5d8ef28f02a52d90304db070aaed34d746906e75aed077ed397f5ea5 +cb4b1fd5fd6ea096c48409dcffe3103a32309bb2ca283475ddfebdfc6d3f21d1a0aaf28c63af15fb94217210d76078e43495bfbdc8ac4a0ca483a4a84fce13c3f24ed69d1225225fa3a39df9327dac09030bdb3191354307db701183acabe5b9f4049a98084711d65b76657bf957ab0e1d51d18debeb386a4a4692ee4b10894d +d729e6b7386227bb2cbda5260a2d6d3c26127d5afb20f93086e44d3a2b180fec7ec80d0e6941b1e32d4a65441504557af88198fed0f8713c7ca89f095e1f3ec17174c6e138367f247ddce75ccb9cff48fb13a62f5feea30dc6dab7d12ed70a5779e4b890200c28171b83ac8a348f4e15027e34a89dddd727846edc880fe8a6bd +d74900ff226b5a70acc71f89a6b928ef282d39e17ca9cd334c4262c44837259ac948b9487ce4be7336b9829b9f48382baec51e5456be045920ed2d32b28b744716617e83003843611e42ae9183cfe86c0b51e514200504dde737f86e70c006b996d2d10ca9c33f733dc29497633364674de14e2256fe2b4658755a48226ee91d +cfeb9020e28f7ac0276b098df6fdd9322178b14087b45b09f0894fe673d6f13b51937a0fb6f4f68ad54bbee4831218ba5923d60ca33ea79c5b4304e773fb91d2026b00a773aca6b9d05de79140afdc7f2f4a160665f9e65398967f2cedc78edbcf8f1c1b9b0a6eb2d9eb43b52e58cb7ab716ec235e78106843cdc5e21b37679d +e280bbcced0e16442aecdb68961f53ef97afdfc030a2606d54e2b5610d3ad6f44165cc2c9500a49b539565f24bbecec5b6bbfc8185c815b00ce24bbe6f61032d30d611ed5b78f8adc5c2c45f82deea75aab6c64514e0cb3602a2b612beff2c102c888df5764e1a7df70794472f86a114ad17107a259535f07d27cf18c9434a75 +f719d169442d84d3858146d5690a18c28e1783be909379827ef36bbbeb72cbd71cd66568f2a015c3b4bdfdd2940fd751af4fc370dc2df02d2e836c9e6c27ddf866b646bc05046aa4a3ec49281d9f03a56897b625d9d884ac02e87bab4cb8e9d875f41895a1dbbd77d77c18e67f55961888aa672edff49c0ba6750d0c97c659c5 +c5188adf4708b4c4e8b5da09939f8241abfbb4735ce569ea05fbb948d34ed13408a68032adc0cc381858a100c202975c05f64d5f240364fc699b4b0d7170dd6b6722f46ca9f464da1eb915176cb62ae58ede202dec980925d9a4c72d55d4a1a617c6b3cef3500ea5695276f3c64610703544fbf32b9cff064b12f959473b5d95 +f848376b6da6b8c4a5a6cd5c091f87aa6861d5f074c4656f1d436fe455e222c778f2f9de22e40cf831e9f2978177ee80e29291ffb6728c7c2317b55e91d9a5ba92e2097a9a18fa975b51feb8c9e720faf8b1595f0c6ec30432660744178ced85ee4caab23727e83f094ece52a12ff9bb473e7c1767ca9d4e5bef3b2774be0895 +de46df8ca7a7d1bdeffd0154a914705de0689253f377ba9d71d0fbb4ef39f37fa76585e83f3f0efe5dbd96d916bc6ce65b5f9e9854740e60e06cbff56b74b00d5c1381c45002372995dd149a0c6e17904b356a6b4991dad55880e146b2fe6d2f5a7923b3ff93d1efd5d832f0bfb3f99d5aaccd7a33159ac6cccfe8d2646033f5 +c05cfce36415e5e276a06631d8bd157702acfed8578334c7a8834404723300d6eff52261007b514cd9d8af7eb18458149a71727b5e6834f019910b098a56be457fef0a1242dfea95d6d9185067e7b1bfce3d398373225669aaf922a8c7f556549f050a0e32ec7d4dd8b60aa9f5ac5136e07e87d33d10b41c51344e7c310ff445 +de846c4c59e11179be5e1532e39df29bf5418a0482318bd8a2e86b85f9f550578ca39d42f4fecb8d449346946d0ef2ea58976a392d5597fa171f2cb0b89339ca30a711c02625232f71caae3e51fc42354307508263028f3008b579180acdb4be0f8871ae4beaacb30776a1889117239af7a523d35477a0a97f1ad09462e9349d +df12c209beed0ea2f3642937597f2552dad5e523e29d3ceefa2a6497562ae2d165fa88a412de4a3b9c60c066c57647dba4b62daaecc6472e8a5494a40cd795008c917af115502cf37bcc20869dcc291de45bb0bf3afd9868949e3419c7f573e88bc04ec813c0efb58b7e8a5993de4ce67a8b75efe0ffb0b019e52e3cae42a27d +cc50771c1648fee05dd7f2b11aeca10f9c98fa92fa549e7d58c9addbde156a9da1479fb99802524f074c666e1c5073896613a3887fb9596e963564f423179d93b8f01e4ea28aa4a0cbfb816cea5093a9514ba234d882587a11005bc9b9f78afefa418ff26c7d9955eb50610f9b334f223a7e3279f879e645a700430897ad4225 +eec6db480d9fa186cbdb38ce166dcb9fbde5378b3b7c02e8cb9b0408cce03f5345a115c1578ba63ada5a63f3a67ccdeff0091f145c20550b6d0681d7b15d85a9127e3609ddacd1fb051958b0f0ec9684cc64d2a489144c6baabf7d54b87208d2a5c90b3103b390be118a3857a6cd8f81bd58ec78874d5376efb3f64639e1af45 +f9f7bf19e35163b79760af714be892589c1ff3a4e640d2a95614e5bc1fb99f9e02780df153bf12b2612c3bac9b8207c69c576011db501a4e667a289f8bf1956f600453ce00b57da6b79d143d69c31bf44ee4bef65a6b0a5c4f054e92c82b2aae00c569be359f40bc67b221edec8a21cf1d5fa2a734dd486c9da49af2e6cc2b85 +c55325a6adf5dbecac33ab7115fbc5502f1af5fa3c39e2785f736003f3f02cd12c172700b8063eb97c7467294ec30a80b523398090f6a6536156e53e9c2f0504fb2a75b2aaa3d989baf6aaf7a9a3f9a80e3ac23b3a05204cec1b1d31f0879e9d3d0a4229fd2af6809d142bc8a9d697b7e04a8f03881c01c46caad405dbcd7625 +e55ec0d6b7f1a14eeb398b7504ed6feda7f2e8f79ef3d1ccc0e193311ff39b73cd7afa873e4e03ee95ad3a8d1f1dc8e1999dd2263bed74c6c46cd061eac4544fc8dd67483a9b15d998128346228151d049077f83a7d950a058c5a6b89953694fa226dd1822c8196ddc75911604330cdc09e5c259ca8b081c14a448e9d1fc47ad +e05c896d23616319b2041fdeea81b8da5077aa68a6a586e0fbc189aecf29c432e46e1e37e515db6e2674837a469bb3f44f7572ffb8abb225291430ca44c7eef9e79a7269209362a7f60c014fa5568a98949809cc93fde3ec99ed034dc7134e0d2e8f28f7ef38ded6a1fae3bcb147de54228dd91f7d88844f31c59882bb435595 +ea2513a18692955aa60455f8fe637f48cfcb64f497063a5cad9f7419a1396c992a9a8e9a55615488b374242caf05a80edf2d871e6c65206a1a27566728af9e4f7581eb1741c0e88622d14a9d3c840ed68cb2146d22995532b820771773b2c416a853946fff2a41136de6205c149e3789e394d75ebaaa9846af47867bff3ac0c5 +dd99490b49bdd7f025fe2ccf451f8b25718d1992339c0a1c62169eb3da2d137db4c8709ea60eca1e590da4d20f737ad582c937ccf364f10c593e9c336859fa403de6f7a5b8de16a41b5d49eafd98a94a8a6b3218ac696e236e46307e20a1929c5bd114516e6b6a6f4466b217dbe03de266e7342bdc3384a34f13116c57d1dc7d +d6366000987a0bc50a837241f9c5bccad07a851d10fd8ae8d9bfec1bdf8acf86ab98b885d16e89827d0ed7522a3e2aaf5fdc2d46e2fe3bde43bd3b8fcfe77de4f4ecf80d6489f12807d57a4ec43deb0307b94ac5e610a29c75810a56b207c2ac8747c5f40b6b112f98c0ad59b6c382fd610818630967bf9d8061cce453086f2d +dbaf7fa3ce31316be0fbfbd2f5439fba81d440719459e61d5cd52d6708e3bdb1c51322f33133b2c6f8addaa143ebf380b46d958723fa9cbd0a470fa1ec34bd5d2417380d5700827259bf7d058306b766d600fcb63eca7519ddea51e7e170521dc2ac5b5df245e5dfc19a3c0b7599cc80d920ee5ff31ffaf3585c7c5791c741c5 +f81d4e3e2ee193d38eb2025316be6b7f65bb6625666079abc6e0c7b7f3258ea7c95c53769112d9f83846dddbfaca860519fb3f17e1d1eda1c88e6df313682f2d8bd6018692865ce0e413c72b1882e315494992173477e759ed05766fdd9769c69f852af3dd385aa9d39cea626b372157ed825bd88df7258a1aaa78a0ffef69f5 +ee3fcccabd80601f017d6b760e12f14cfdc876c8e0190550d32c1f1eb6049f5edd356396314b8914a84f6178f62751e8c31fa5bea13d4f76f79c446bdd45a66a71b310a4ec1c85677288ba0ce73861a4bd40cb96cc896dd839227b2b53a358baa8cf0e70995cd7f66819c00a7f7811502d81f8aaa7a90d0851313f08b63e07a5 +e7bc5d453f823ac26bb071e138eadad94528a689566eab8884aeca68a4128603b771b3fbcdefa6645f3e16163a4b2347b7a63d36aff89e97f5b40894d7cbda43f0dec94040067e88d1c2627245462fdfaa061ced0ac4e9743140c1680b07c107e1493a2800f8abc75e403c4ac5289b59559a9fb25e0051ff614fe3c6314f911d +c6757706c39d581ffb923998c8d11656c261d23f529f1ea3dd5b91482fbc1b5d9d0fafe22b0ae8f7caaac98dfbd4fbad7f31ee044a8d467b590c3166abf56a5e3aea37ca2daf4c1a6815f4a503a7b1b55a3fbcf14f84ffb867cf9f3f546b02009de6d3ccfd76b691e5293240c342550ee13a4141a693071b629fd3770a01371d +cce03e4ce3bdb4c846c1044eacd7a400cea051d22c7eaaa0cb1494e2c35a282b87e0157bb72708a21b88297be011ebe06b2a93c28d1edf61821cb7ad6edd14ea4693821c1fedee39da6bc86cf65c31b949984db16ce23fa23b0d4f9c7d73bef86969173b7ff988854cdcf7a23c9b2e3ae8bab8860d85a5ff004349f3b74b4dcd +f3ce37a66647d5ebd8781e14c54b25e738ab57df1393db7bf1e71b0e5c2cff7e1c9aee1228c2047979a6073326c56e969577f196e58264cc3e95addddc871beb8c7facf010236594f33644576934e024a3194f7c168268b64f7082ee9cb96cdf6bd834899523841ffa8694eaaca9c2dc48a53e02199259c77294e2063664fcdd +dd5df28cf7821595a31fd36906f6802b3b402bbb1ad9870cf92b64acd08c8a2045a4ab03ef7cd607a15d27925b3974ea3a1f453c5d59c744343e7686a62737af11557bdc6862eff77c9341c988696938874064dc0b0e1212e6e460651c05edb595d849a974cdb58c498be6e75a8ee339004f121c4ed81b50e88bb8d7cc87620d +d56377fdba499c3acb75cc81353de33f5f626e85d74b9f72fdc0a265b19a7f4b34295a3aa78073097ad62d38575693cd8bc5c6306286891d201e5958c686ae7776a2b3d7d9352fa536f415a71565ca2a1637ce6c1c6dcd3219852a87e865d5d4e018269a20a10b4391cfe8216d2d8712174e1c6bd2d3a369dd005c913fb1d9ed +c24d97dece5958111a0a0d8538d1c15e282f21681a47f5c66035267a89e57d21287000214b069c5acb3ddf99e9ad4d18c02b675ec1b634394901796507a740c818b8859e6e74bdbcd433a236c7ab511131eba2d27414502507cbba401311e9153a615db6fa904d54d415d99970eaf7aaf3a265efdd2021b560eea01b40c1a0cd +c65535ed408e69a5928bb27267ae53024e6ea2a32867212a914b9447d07fa6b725e4337bee9b7b7ff99e065248848111b08d4bd028533cc9420e54801ae0b8536ff3659f34eb9062f1469d6dbd22596ebf993f89e35cd4366fb3de63886192030c477ccddaa8cfd1944ed3aa2288d61d7f931e42cfe939cde98d42bb92c48865 +ca8d20c285a7f844aaac144079b0e9ee4c92e160aff97d298959514223f85d94658219eb8a0740c7c1b59395ff6e57c0b9688e8a843d9f75974f5fab5dcda5a0ebf44dc271c22f0138f1268de1fabd7088f34f181c20c58479fd5f73b36c36b9c77d1a02c3c999298470dbd1a4edc324e3478220ecc4e11d3cc350fbf20ac525 +cb1f812e799aefea75f7ff591654d77fd43706de79eacd8f4972e4a10d433c74f66fb644c7a53b09ab8611daaec1b8b158995d1162f5cd66468d3627638694f0144500412822685b72e9cc4d0f0310cbdf9422943be40f675255cd36a624dab1635e6257ea937ec0a9c2218b3458b9f807bf604639bd3f2c7ea9df24bf7f9755 +c9041cd6b43bf14ef4fe6cd495f876bf7a3da054e826570bedca2593b715c5e6f02f5ad5703196ab717ddfdd926dc5ae1f4eb49f054b8078da6fcb41c8d828833418c883451e944bc1f62a02e87423aeb84f52a6806d6fcabf42ec19ba3e023c216d57215c3c7d475065320677210d19f1806d08fd6a3502f8e157c8ffc7ec8d +c5961a4910ba30ef276b27337bc4adcf01c74a83189305f7be816887901c6eec46d204e813e3d4fe85e812639403102dc00a8e2d54c858e54d69eb45076ec46b826c9f6d9eb862a5dcf168fc71c84b1ae6a58677fb16eb1b11f37e0844a84b8b0bd06c33ed5b1d61b0855cf220429803872218f736089e7f0627927c01891b5d +dcbeda335b757bf79323762fbb7b849545bb717052586afa644c0788e300c07b3ef64437ee07a0b181c83cbecdb49b91c4020215fd88302a232c8ac46a0fc1c1a2fc8b45b66f8e8ee21135b1f186f1faab41ece733401d92ec4cca066d6a6490d721152b5547bac7cc4f1027ee7c6edfdcd8ed22d04586fd159cff4ce970b245 +cf4169d4412da9494e211cbf5d25ebee47e7e48409e3b9d76b4bc6da0f91836fbd40b4ede0c50d05a63d6184c9495dbb608d3e57117d086d30e35b121acedc64dc75c5f7c760cf9d1a455f171a2fa9864f404f7188604a92301b5fad6d5544de323a74888906b6177dba5a643ac784ee4eefe15d6833aeeee55cc9a0f6d699b5 +ee2113d74d47670f7313c08e615fecd2777ccbf355bc125ab0bc48151947af19b99f2cda5a815d5c44046e74199595989d1ff84205a8a2c976f9e09ff0361a62c27b3575d1a53f3d3c9c2681fd9528a71e22bb630f9ff2b9ca1a77c54ccfd699595d97becb8764cad0383aaaf908368acc311b43980cf902885d0cb13eaff335 +dff036be4f36093f2e20781547235c6b88f82401795e0cb513d74e9618236af81d9daccda3da9c4a5272d2e10e49a776c7baf5f2aa40fa61e1003df52d603a6abc2484eb807588826d5027f5529fe01b6651a71dfb1ed23990c7ad12a6902c71282e9083fd6d33a36919e51b00d10a47582a9dac65b9d192269000c3bbdd6cfd +dc0d7122b50d0b27327606cef7fb6380d0e7f7297f5076c4b4de8cd76ef629745d2bf9bee4e2b1fd8f37a1cb11758e98d2f2e54583de2a3c0c339a8bc8d013d48d5c76297e12e18d03aaa6f242defd98b145f5bc37224c0f94f895132b6791392e7d598e0b39c2f9badf8631f19325fcac3b8f8ddc655914ec249cfd9e0d0e15 +ebb816b31744578bbb9b10c8c0217de1c20b0ef86b5bc6da94f5708d0140618a56699792225b3adf6e9a02b68422ac0b9f8bc5f676944b4bc3382059dcb33b5343e2bcceb340ef9faed9044814c41de3cf862e07a2bf936a6e8176bc8217e24169f9fbaf8c09b321e349f4d2364d9e37096ef8f62e0c0ed640adbff7bf7b0135 +c68591f3244def5bd01eb6f7a257842fff85f59e2243377e0d265f1f1b90e478b563d5085d1982d96ab881793e195a168c468b50c2c16388107761bad1278db7529d207c5a41ce77bf1cd4a5f6b16cc97b5186f821b15311a07ac6ef0df2994372f2f52c3803ccdad0da78ea6e7527502970e09dd1ae7ef8e99b872beb9f8045 +f6b4a1c781d759e5f4a13973e003264c365893c9f181e5410af0820d57ad417039913bcabe466e43e7e4d66d79fc564a627d3bcde846d5ac6658ae09444050515953337a9398bb1ceefea37c72c67f09cc0ce3f1a4091f52834003c3f180ac340819a4fa0e9fbe074f9d0187392ecbcd024f0a90649759ac83fdf7e77593934d +f6e7675f852d318d6b402d410762978cca8588326d5bbf84ac86160e03c426d90ce167b041d255993eadeccf331e7d0cb57fb25878da293cf2b9fead61e9e080f2488d98e96179693f4492cfb4b6df7b24446687e1b880356779fe408b09c07ae59fe64cd4bdb209dd93e37d6064f6ebd7f0b5d3316e77c916e515d608096fbd +d9cc77a38c2e2c6ada42e24580e3a801b85020808778cf7956f90cda127aef2416e931aef46688ec937d9d57401820292f1b7ef0b956a9ffedfb4fc5c04f9e582fb527d7e09d68ae5cfe1d99b47b603302a89fb5b933e18e4f0a1fdbe591974537f59ff2ac171bedc72ae00100bdc4891441c2767fd70cc9917234cb6f7910cd +f29f0718f61441e6a7fa1968060a4624c4e038995140029615e447a2ceaadf4faf0b650a448fdeaf4d2bd8a3467fbfbf1d413dac1706b7d4d345daad253a8e47bda4215bb71898c7b4859dfdf9011f4f14b76be402efcc22a3f65895847aa866ecd65492a53e7b024513878f197e23d16b6753655c7f2f67d1d5a4df6112e87d +c38400b70f59f672e31bafa81bc5da25f9eb83c2892534d863325df19d0cb4d161db7d52e5d96ca6d4884cbdd47f8f95a7f59f087933df4c687cff3c1c09c527e559a50674edad1f798b233b3a9e6bd0f1d2e14933ca7d27910e4cec9022fe6713b6d774f624968cb2f8a146c5aaf48df2a44171e90c98aaffc5b45cdd0f5295 +dbfdf8391f3f46855b0effd42884211a069a720eea40b4045e05b3f7b89350b3baa9349542f5dd48e6c6675799ad85b7ba29c0d7163f9ecc8a5f945e9ab62cbb3c05f018e07bd736b3d245c168ed0e7c565fa8df0dbf75649acc4a078f625cbe24d7d102bfeb66971d97774e5a2a686b7484260e762c2cbe8218412b255cbc4d +ff1044955a4a46fe88719b98b4fbef3610210a11a00f28779409e52a898a2a62d383379e3892ecbd5594fbc4c54d363f2030ac48f0cf8031904c598d494776c399cfeb0be3b0b376519e82981cd88d0dbb656ea1896aafc998a00c33a7e8e619fd64838b70bbde315812ed2084ac77af17a5c5737cbdd211a0559f178cbc796d +dffea7b7cc8dd939086609c14c85234851f74699f5df1de82d8f9052f8d29c4e2f5af05fcf1e0111d0da7d216ce76366684271c14e56a56efa08592f7c0aac245aa1d4a0acf19f921884f50cc320fcc76f1cd094692634e3bd5142cb588e62687705b236927f77e4097724c5936523fdf6633a934efa57ecc0bbb109e4301575 +fc7089d2a8cfc5cce0d344cd5ef8b83274f0e49f7a805299bf8adab3e89a074d9daa62f7ae3b8127b628a11044a511061ab08e7751e9603750dc5a5f46a46735b5901c118a2a839d1232d2cbb429c0a7588847daf657f367560e30412e984028bba9616754eb2f2dbfaa3ee4550ee81898d933eb5c4339c1f126bf9286e7721d +fad5e667ecc443e6caacb6ca76599adaeae84cbcb83d0713eb84f13dc7fdf4f0d208ffe47ea4a1d67375df6a08cf13067fb8bce071c39a5a75e94363964125cdc689e499394d3c7d6f0ca6238668024221f82817d132e981c72075db853262cefa4bbebef031efa99dc6ef5384cf942c6ecf0746825c9ceec4cc699f2d4181e5 +fd1b4d7018b7346ea508723e0a7d3960a8828e7789230e1cc749340be4c93b4f424f598b1acf0d16dc515cd6ffb9739c5d52d6e8e9e570c355a0dafef9dd7d61071b0b4ad1f9189df4284308d74c34eb8dcc2d9b9d8fe66fa96b0b221881dde85a8583145463d45d7e1abf1278da2ea8050db5173deb108d12c70528d9c4745d +d58a4da763089093120a6a32735137d1ada1a5ee779329ee0c3c04036ad92f36ac3d4ece8e47e6f3ed86ffd47e96e36c245141e64bc20a825657fe0eb15ca4dd9456e59f6769fb3545f5cfdab573f9ac9ff9ad1dc55f012589cc6c58025df387e050d52ad8946f9618fbce898b651c8fc155827229a779a6876e92dfb484ca65 +c9086f19fcd869343b4a3c21ddfc79caf54d812e21049ee968aecbdbf22ca5f4e211ac94595b28f9f8a3f7f21d5a7c95bbb92a5baa0906ab3ed98694a9a3e40dba216006180c472e993fb1674cbb02c5dff2e77d2f9d03323bb0addddb2ed040c1c55599377d7e98cb0386cb2f95b8f57182ec2b982cac39f01243bc0ad89565 +f461eb483e0ab79a1a6d670c7613f6b04b6f3c2ab1bf12bf20d108f8c9234e1fdbd4502bf829be59b4ae68bb90fbdd6d5b19c5b84a3a512190adb31df2a4e29b5fe4991ebb00532365ffcbf462be7af9267e06c252ff251093bb6a442e1c88a0936aeedd25d99513be8af23447eecb1dcb06ff24ad868e44f53c790feaf4ffe5 +cf62f209d35a652c0f29f916da23634d32148391c06f3e9691d24758b87fa9dded4cd75928a2c4f3a097a0debfcb6928405e50f4508e16482c30f4a6401e1d32517991d3e13acba0397fd02483705d3500f81cfbfac3c4fe75d755999a9a6587b1ffd34f71a8ee827372616aaba8d7e44532c639c104993815efdf23b86387ed +c355f05e36c6438f1fd6ea0613892c3b71931a3af6738797cfc8fba634d62c69b1a803f4e779e3581b1b834fd9ab5f6424cd08f5bf242f71435944a2c31fa93bfc8ff5baaf40f5e378962fa3c10cdd5a1bc8dc0a0ee910d6d7182f5bef59bdc4ebea9f46108f0c78192b6cb94d8e259be4667c7ff4b8b48f51fdd34600fbd7ad +e9704819bdae06326676d9c36cee870a503273e9e450594b52a16ad584504f09fd19177569de2e3c2889d461257b6e69d323ddc6534a672a999118967bf283b012fdf87d9301c37cbffbf49eee882aac5dd871bc137396e9a184f1f11105cfb6b88fb8665a861f0894ea4270fd93130a382b938c9218143345e3f7fc6d24bd3d +f935d2e497d834233aac83a32151b1f19adeddcfbe5145a882e0faa6a53324dd21c79e5677eadcfe93628a58f70bb18d004fae06197b01293b3d09fdb4b7aa0ffbf7059c62185fc4a30cb957fdc13062583c3ed2f4e4588a7d63056e7c2adf47f6467697c6ce15cb542e92edbe132c1d40fd56aa4de0b107e3ff1582e5c0e475 +d51a8b2cf9408add3bc549a140310d4c8df4d2bc68ec35306ea146b90c2d19197afbf54891a240bdd1057f3d8238578ea1eb2e7c0b54676e4b53549fa074bdbcbe794d9084810ab6e0b4827c739e845912f54839c37031482e5953f04ca7b5c39d28c7d97646508675386085a3b59b011ee2b8aea1f46c052753376fe5e027dd +f7048051b96422dd7f07e4fe6f1b70b95aa981f6136cf00e05f8bd1c90d54e425bfecfdf1c5522ac89b10419f898bc1c0616ee8bc703c0dbe4835ed1f56ae159685535960e386e662ea504c7b9bdb1ae7a0cd46e79eaf6e72e5aa60cb58e1c91bec7258677438198287296625f65c8e77db2c44da948d8a2f523b1e65e1f08e5 +ef81839f7627a4798900fdaef895cd619689724ce0b8b3af8b12711a1b7c4b89cba3957f176d4a74668a7e7eaee137f9fb144c96355e1cc7170c107f513a2a09d3b053c8b1b6fc9134c084e5540b2f698190d43b9a1bbc0d0cb77d3c526a95eb5115281fb66c3e3c569460d701393a91e41a1c27be304c6d4d352864736f791d +d06d19436af8376073fbf7527fc3ef4b54e6f6d7323cb3fb4de5547ca75c7aa5a975665c9eb6408447eeed168c75a94c24bb7ea1047517a2991d204a10fe798d24e57d0d907080935492fe3571aa8a97533d0f836ac2037023d22ff0674954a43f144d5ea0755cd6b5a64a5f9da16966c1a40f757081a59b51ae50371ccbc14d +c91f81ae46dbaa65df1a6a5e5ad6e663575559384d94123b2b25c73b67e9d80a56c1e7e503906b87d04534b8d1e0ee788e45458558b4aebdfcd4054871c00ed3f1913e25c154619b787d62fa362c92242189f3b12ffb89ec239c20fb9852900cb73c5b6aa681165f6692321078aceb4da6d8c6753038a624a65048a60903e72d +e2b88ee5f593780e9536660e1e277047f5f00f2507c30923f76355443a88bd194546598aeba7d8a594c461973c066c9f1b72514ba669678a85b5706637ae059459d99a4d9e1bf1a95988226df3fbd51b0c08bf65ea092870f902d734f2326c1c82b28e32154b7044be05a11d4ff9505e8b22e991bdcd295fed0d7d520ef59aa5 +c51bae95613c3cb9ced85c822f9f63284b4f69cd5806e5f17e0691fa5531d3a83361e042b796f53667517851bf533915ade6100c5e35e571fe62186af6a2f6b111f7b006ba1d2dfbe15177f0df69bf48e59bb15c38c3e5f2adc861b99c3ec18e86cc2ef60765a1bea21c66a4a93f5ca5c824a33e3df049e2294a851e607d98f5 +d44dd9aa118e09e71722661f274ecbe1dea8195148b3be5788e0581a67b10c54fd565b1206442dd2e6ab50d0acd466d4190fa2b435c93e6b3599f38ab8ac197a97c1b6eb85ece86f6ffbbdca079f844b9e7aced5a1362415d7952785bc3d7f7759cc23fe28b1e7004ccc809956462a5291d03ea4db7ad19c160121bf81b6ab95 +ef78419ec73c3b5c017aad711d0cb29aede2c53c6849a804fcfdd1e4e8a32d26e5d024f3f621e0053c03fbe25ef5becb19acefe10688585b80d1504f0084ff606d37b8c630e73f15736dda94e82eac69d73b1fd69ac9c0af1343dee6b43f512d93654a9559331b93ab76767e5437d42e8866b68140b1c7e8a8de2ec92b96d175 +e0b9a8df9507d8acbc32c9782d83d3a67e5e9601c155794bfe29e065a4f3bfd5c923001a143473e1cbd5d9820b73bcc1014bb1a7381ea8402a01734683529b599b19505d9bb7eb270c9b64a57b98f97554ff8380f79653ab19bac8ba59bc057463c497239831384ce93c4da48c0ec1fe94403ee965f3856425a082d5bb9d158d +c88f3d02089a37c3ef9de421da396c22884f1934cccbf6a603eb689c1d6d503cb9af8f153dc2f2b4a57d4e3731fd0ac69bfed081027d0be77f299903ec52846ee784024559b5d13ef76dfa4b365d08a6fecb2fe5621dabd3963a3ff799f1e95466633169ad5c5ea044286baf5fb3a19a2a7147affcdda544c1061da877b978f5 +fe6cb780781593d1e777f946121b55f15059ab69f8c1ded35a09e6c29dc4ed637bf0dd3e11a7c056af7c7fed27e98bd80a5fa2ea7665a82b8381261c7e764f02fd5f605c7b8059161cb1a1e11f7632c90d417b460e32b3ace20a6948168ff52dd64f5c17a412b7fa72674baf8c1e24ea62a8319b3533c4dd04b840083179627d +e14376d91a06128b4239141f7d4b1a16847d475a02660e0304396ca40ad93b994c6d501db2cc180f466437841e76f798ceb5e7523ac41b4237064b2b31245df30dbcb6a076cca82de707e1b45c8892556f9fce262ef24169cdd7539c8ec04f497f5c4b28262a2622a650648ad9d4ae5e1945a443562a8de81518b22c42bb3285 +dd6496c4c984f5305b5d7e114dc2184f75ed627180145f331049bbb0f15e14ad2fa74ba7540206b61ce364bd191230ae6ff2d8c25d378d6ff9e8d08e4972a015b917b43ecd734d6defd9961cfe7ba50b916294c9e0cc686c7a496eb5f44c98c44ab974af71be0aa50f5f47cb902a6cc004289ee1e882f9a71bd63ca3d1160c6d +cbdab7643480f2ce10c7f4f3effa1491d7eaf45109b5e7e188cd5d8cc3d6f17b220d774844d4d130edd0ace88a8758d951187405c4dc67dad60c15eb7a2b7695393459db06832e96bb017623dc617c0b916484542633c5c68c0bb44ebe306d9ab1d7e33fd50e328cea3f256393a822dedc68c8af63388462b1e5b2ad9b72cfed +db561231fa1ec28360b09c579bb5cf6d4a8038139fff0994a34e325ede4ba9e41cc65eb04294490880891eb4a624cdc25777e8ae54cc65642e3e986d25f0d1d68450df0bbf52f2b0ee5da9769b6872b39abbe87dd4ebcf56f16c4f8c59f7b27d49e84676ee28980a68fbac057e0bbf69a5414859aac4eb110e18102e4d356d0d +d13ae235284d459e4228f01ab6c8b9ca0df6eb563c83471c32d272e8bc5708f1dbb853d4a225d019b0d63d244a79bdd9cf83152b5c568a5cb378ae601cd96864cd2fb65a1adf1e0fc8cef28c367f44705ab153b86018287fc73892221877eb2f544da6bd09ad2d9eb5285978a49fb3536b8ad70cb0b5dd96577b4d284ac5c1d5 +ccbbb29d9ee1e4f113219a526ca77d70a7b00fd13f9ae423d287c8fec3fc4d128a3f29110b87933f86849161f57c1b72c9309fdcd52390535eedc3b18e61acb6e904aeb4eda536a74f53fd02c9fa903f2f60532baa57c26755168b02db0c3cd311fea7b833ef308be69e8e4408c23eab0ccd3222231a17b74438a8a937746e15 +ffefc0562538685dbfb1e3184c5c855dd0a04bc340374386134f756fcd52deaf24010cd012ea90306be7c11128326b8e940a441ffe90c81c7096e11431482a8cc42576034034403b8a5691d28538833f17313e610558be716afed80807ee29d20b309106c06edd2e7cb06f905af6fcbcb5ffd41f36acf88170330394db4504bd +e3081868e6bfd443207bc3b51e53884770f6dfc227f5c4ad5aac48192c838b7d53b753365313afe4082f5763d93a9362f362434df93b06490bc0df7ed1a3e69811fd8e2f2daf8093ed7a3c55744a8e2243eff50570aeb50180d9cad962af13522d80e562dd549840102e4af3b654f62b29a8a035d59e04277e3b8204989feabd +dd467258d247ee309beefa0333ad4bfd4acffff39ddbc44e6cff2492c65f2a9cbbeb33b915141d7361e579ca387ef3e55105b39db94a006dc92f55d104b29b2d9cc52342e58ac426c5bea0c600845091de3ec35bff5ff3e181c8c13c763ed8ea37d5abf8b39f4583034cf575000216c48d56338650cb39a4e1806c7dcd5b3d15 +eaa24e93d42d27e4bb64120d554c2ee11a095f0f9ff153b835c94e9a2133812d5ef3a22be995af15f6b12b7288c048a6fba834f4698e77259b5a1cdaa740d550383c2c0be7fdaec742ba71148a08d4934bc8e719edc0fe65c32ebeaec3221f7566b9f8f4dc076a0eff4a35f675852a26e46914c5bc2f1012c8dce0f5f6b71cfd +d1ef6b9fe5c0ce5ea98ee2851163b31dcbe3626f2c61e0343c3518d407aa23e14c1d9fb268c844608dc939434e54a5c20f2604c1ddb5fe4f5e71956a4114021758963fab0f569d82a333be13da8f80abc81693de7fe55bec0c53bb6f2a92f9ec80ca756367aa767b39d5b28fa21a232e9f524a2b08586e3e2630c073710083f5 +d0898926fb912d9a6a570a895aedba60ef0f47192b21b7ec097f40cfa4eb92ddb3af30bb55a8dc66a74a68fcc908f19abcd9951f06bd9ea57d91221135c90bbd191b53c03296b8e5127978ab4a7538b3f1a975448474b2895e24eb45ad71bfb24de5b98cc1858a25d1d41a0ae575b05db820aebbfe82a1b7f362509731780fc5 +c77eec2c2dc7baa150df52db49a9f9d624da0cdfd2f20ae97d27e948321afc76aa9e3453dea6d7decc72c8d67da56f57de1874149066b20726ec74b2dbd109bec0fa05c735bcff108ba3e966f00f6a5bfe868c5b74a7983bb0d3310b80b6d745b7a288a73ee40951329aeb18f1b5054a4148a89c7cb34b0f5693ad7e7f29577d +fc417d0a6a65e7a90ca52a82a7ab97069e9845a248fde683451ae12c6fc24391063da1dfdd12adbf2f6de878a47ac0954c8bd6a13129ba1aecdc535a16feb591ede4a8dcd5b0aa9d40129dbaf29879af5debb7fac908565977889ea4857c27b6fcebd8f0cec0fe5ebf1b0f389205ad4976726aaab02045cecb7f3440231f98ad +d3339da1f1643b18af3aa714ab2fef6e2f229821d1e9100261f4f696b972af044ee21a2c0f3f3f0cb69cfbbe06409057a02e6129a3e6ddadefa12639ca150eb9ddb0a2e4a0b5697e4160de6d885061d71cc5ad6a19c7e002078e8ead1348a3017758cf3cad68d70865ade96f3334d708a34d5931ed105cefe0544e78a4388805 +e9065eb79ee92dc6837b8aa61f7e99541dc966151f34dc0b97506a5666090d58c815490a4170097a4b5158aff94a69eea153561c7e332b387c42f3563ae81591e908b0e2f4449c943e7586bb4aee61c31d9640f440d08fc1bca84f8575e097817984e803705a513ac3c852b702d7675d5782e365eb76995d1ce292194039370d +cb211ab2c89539b83d3b4f8ef0edc2e2a061dff45c19b8e25c9905049323f59e42108359467529f94df66006d2b9409f1add75c7be360e29805570161d3660c96038c49bf576dd84f6f9b12aae23cc8bd268ee48c29b5fd3c13c939308214c759c7afeb89465b1bea95c292a0481cad727acf3020a44cd639740be98cac94515 +d5963124a849989d338e6ee7ffaa24830b21fb0b32e6313e3ea77abe09b62ea4bb29a31c7df9c38fdaa25f5ed00abeceeac3c79b0c33dbf19380a9a88cd6cfd09e5bcccdbdd644ba3e018893800a910f2e99beaa7efdf3358ce600d20be8020dc4168647e9a530ff61673bac47ed53c63b39e9964bc542b797de0a7517edc385 +dda885206c316fb8493450432e705e2f202dd4d5a35f86bf6332df0d50729aa3f86bb71ef78d7af36940a1add9f8679f0b8b67e828755191f76ff74e1b0530a9a2fca1120b32f28c3e73a9e51f9ac3be261c91919d936c66e2da8ef0b23db3c9a6b2c3e1d71fc634319e99e758513396352fbc9c978c3e8746e9dc6e943655fd +f0b6bd708eeb9a153389eb769fb6b140127f876b4116fc7f72dfe4e3137951dbe9190d6f76916dfb8dcbe8c8ed02848386d8d43298bfc1d6d6daf773156bac1cb2463ed1232902643bb090eb604c812de60c1515482bb3217660e11eaab25865f375d9fb7a5257c80bb5019056e233bace203cb691fa15831ae1302901a7e8fd +e6e9ede06461fff5f9fa150353b9604229649e454bc1adba9d30d58fae3f9bbd45b45c4a257e71163c9399a1888741cfed9ff01ab8167381e6f16e6e16cc2aad73f6c8c30d1d0be9eceaff662984aff469d85cbe852086beec64a5c7a2448720b384b79d046f4c41408402621d2a676aed29d24eaa99088d126e947f9e240475 +e99b898150f7a8a2d907745b3cf293f2484957a448f480fa2fa9d1069ac64ee0b34fc1816558093e14d8675e12dc416732f6102ec9cff519315806c2d75bf4870a2aeb20abc04717a8246b3ca56ace5db90fc60b820b72876d92a6a4ee263050d4a529ad29d87f626f5e9c4385ceca30076eab6340e31adb05490e3fd0228ac5 +c2a9ae48163c80e187b63785aee50b61cf8cafa1f41355a404b3bdcdb1468318ed913fb4294b51e7301d92f1970dc01e4320fea142e29cc8c10d083a6b6523829a13a762fc6681ef0622ed11d81108fa814b989eb435dbee63e025f9196761850054ed2148c07387a5f8f20f6623f6531b4cbea6ad00e8398c3460edd9c3b48d +d5b9d5961ae8595d6f90402dd03cd9af9194ccdb9c30db8df9e4e3951108f739a6b095eaf6be0a44e681aae508aea83ce5aa3875f72821a8ce50086c91f08a731de573e52ba0a592b5f8e3d92200aa1b742f1c19533a4c321873ad2c4b5f5f24b2f6d0ae92a09108735c0a8795b476b92a55bb65a86fb142318f63bedd26cfc5 +fc0e95da298f47b4c5c200cb2cf6c8250297a1cce4de3a070884413f6403d2410277f934161acf154e2c2987279f1b8a1db936ab1232dcf00e02926a218388f972f4fd47a2299af22b9e15b9909154c0b615929f13fd058f331bd1d3f61291373027c965cf07ccbe762fa5249855ebbd897dff7c802bd81a7f89e5408b4a0a05 +fbb36d7d5e2598ffbd50b3ab673e91ceb8f99f6eb46a756396bd2189905426d492acc0eff5245cf16231795c3e3e88bb11b2f66c7c5995130f7679072addf04f7cbd9059755ec8c3163ee8390bcc1023aea55b16f85972c178c46600a5bd20c8aabeb7cfdc9731acb2068ab311b1665771d4b19a8d782fde357d9db1e6828095 +cf448a3d3b21c41ba1aee82c0d424c2ff6d5afa1bc2fa3c82483c0b60ca0ff0fb579cf87cde23daf83b1077f1333aa5f51dd14ef5d3c276cda122df9e79e4156e16f97279b054fda035f0292e0098ec65d9ef02f2c6076016969d58e1f9fb671e356a956eaef3b449088b2eb8443df2cdd4fb09874f6614328c41e5ac0b67e05 +d3f9360e3d6ea978fc7d18f492ac4696929fdec822744c17ed262748e6bac0547633d89bb9c4b34bbd385253f170f262a22ec37d0f68fec97a193b23d73a649177864d08504e9f952c5d52d831b0fb7a2444db147c2f1c4d5d359deaa19b5b335b968d713cedc0d02dd7f08537bf3f51e17ccc440336a242195c239f5e558945 +c72746e4ec4cfc545d4cd3642a4775a04fa97b2e502f52783f56f90e7549b82655053833afbee2472fa7fce36161b90b8463c22511122e63f9416e075517e917b475d6c7c66bb61dfb1a82391929213e05127a704dc4355b563ba246819c48bb8ca27cecb6633aa89d8114e8c23cdb470cec30ead9f9e815809e7cd5bdc51f0d +d2e429c50f73236e0eacf38428572263d1e37d3ca438d9a4738660097c26f9fb7f4c5a28cfa789465a5961c8a81eb6ec5ccecafe1022fbb5bb1c7bccd93be66f1058ce0fbe48c50d961744f507cf4236b4cf5aac01159c634d831941072e0ee1509c78bec8a8d8867273a5d59cc772343c9afdb86e6a50a8cb3bf1e65be1f405 +e0c9f261bcee9f861d84c726717f65582c17a3db29d18e1a5e4b3e5cdf7cb850de9e9cd663c2a83a5ea9282e255c6003b77eaa0cffcbf0ea1d01c3b9eb52aa0358cdb52ef08af713e077a7e4afe78323ad1e0e911209d3e9eb23cf1b4f9fba7952f697e057a2ef7905bd6988c7c669c317dfc2e5f960eb1b5c645e1ae9b6bf0d +c62c8b51ad3c9e9acdd33a4f4ea308e6cd444a539af5cda31c40a39769d2bafbb632f60272615ff1d5829849ee087f0351f7ea347fa6984532dc286ff1932871c7d11dfc6050a02633b3782c2a0c2ceb219628f6a638d14d8e438908ddaf6c623bb4c69a4a5cd313cc8d17126bba96e6ec92e501d8e81b836653152b3c3a9ead +eb161956621add40a0cc0ca3f3bc9a60f2b0f6a2274f4038213e3f0d288dcc5066ab3813fb42687196ab29d76f1cbfb0b0be7cb9606207a7732817a49230b60b9de7fb8e77a20815b83f84850dc0586025e12223f82823d6daa6efb34b8213c2c439628e81850110815d225305e84165f1502239ea79544d7dc322e69aedf2a5 +c9b557ebd4b4ded4aaa97bf4db989dcbca6a741d59cca239ad8b0121c35508f33a321a1fbb9123d9ba370ab69c70a4032f34c32af2d6a54d7e312840171ac11cd663b5ad8df1d7bf6f9bf972155509c2beeb1c65a0a7b85a857a66bfcca4a62cdb4156a460e2d6274e8589ad83431f824eb51b3f06522e52f7c2d3e93c541b9d +f17c93e94c7ab2f777f518ed702a02b4b527c8d21226a7941559475beba02d7e8271bd3b79b78ef5256b094610e4345fe43802210d2e9d072201436933fcf3243323b7b708f93ef223f2eee56023307c0206979876772ff1c302d9684def77c5c37beac1ba407f1c92f23df7e380c4b3dad03b333f145293737e48c6e4c29d2d +d677d7f7b547fe2d6e89c506500885bb6000af7b109349faf06cd588a829ff62b5e5f72da766cb26f49c41e40fd743f03b887998ab0dc0d691fc16073066e3a7d3bb562d25a109ef7228849be098bc41a94d1635d61d0c07de82649b597eb53384b4008cbfc981cd2ad32ce65fcae288c6b57e8410ea504e68d169e3a93ad3ad +d2738f6224131eaa57bac0cb592b5888cf9ab32c5ca3ac603f00dcb7523bc766033cca5ed14e1e85acb26e537b1506bd9ede1169b6771cab017e4b65fa473290a2786ddb0c065dd9e13d3fc3274c280f7cb99f143a3e2f3e66d3d259c9100c7852aafe78aa36b33545241a422734d1cf216c45a1316b7f5280656a768e8a5f45 +ec9e5721d7fa361c5555ae29c7c53c91735b9b9cf971dfa5359f7a7684ef737018b70235d9f856766fe15c87829fc13a0cb22afecb906af9a3d098673154121ae0556e17e94b05784ba4c3b7c0adcbbe8c595c4cab7c82c4c8dfa9f79c15123af3d9c0862dd46244ee60cce7861bcbecf41e1b1d39a708135bf042615adf3b5d +e61ca49b6676e6a5a59d07f5fc51b85ece0a7ab69f0a851f71688f3aca1bbd19b4cb9144869f8827dca9cbfffc59e57cd8729bbd313b8c1713598a584bd9d2f4027c6f88e24bea6738d07b5adbf8c0f38785aa33b6666664607acc77f4f3fbd350d0bb51da7d2693f1fb35d0c61c269ee5f50414b1a4441f72d97d429a6f4845 +f1ba2f7d8f063535c811c0b268d314f93340ea7c3d418928cd5016a2e8224ada2b4228959e6abe7c5c3c9e2472a5cf5ec6d21b7449af0bfcb00331dc64c03be20d75f7b29d2d47642792bc9acdf2f1647ad453fb3e63301385dadc8472048fc73a32e7d3fb6283994a322f7ff6cb8a419886c2c0f700ae704f34647a97daa8dd +ebf53f293fae2b16f3d0a34227556cc536f7f83542a627b250a4b6e8e41fe8a7a68fb9beeef8ed233bed0ed4419eb832a6e698d0ff6b7fb95617e2ccce0561a2b0f124b15bea9b59aaf86ec04374d7942bc6a7e0167e5eeb360fd6002fca0dfc7935a8ba069d47b74821777900ed205980e1b8182792dabeb5858e8e2f0c35d5 +c6894fe616cebe6bd407afcfd26e4ff2ce32f253bf530de41ecb645f181806cc3d592a18024d67ddfc1779788b80dea06c7c4c29ce0294a1dcdc76a30f173daa97c55a9f34aa0cb8f5d848a5f2f706cef4eb515397d4eb38d493a3df2b582d7644c3902b780c93f0c5ded25c50fdcaef8358139741af17241ff861f19a5cda05 +c044d81f9543061edcaac544ecff31ceeaccac638f14129d9df54c34891a9c2482766445a37e405493467ed188b570ed1e8450e6750873170f4ee3c9c29a519b0f17bb935cc74fa6cfa583f6891cd935cb8868068b5d76975074ca7d4c6f2cc0156a93e25c5eeb03d5fd6d7858b599f15fd8ab9e6b5dc22ad1a302f96775cdd5 +d9ac0da9f7b0cefde928331c9edd485728a4e4238bcd2c4df7c1ceef777df025a6c94c27ccdd49476ebfdf8dae75603fafb4563ebb956a6b73b381dec4d414d6031d3db06360ee425b7f104e14b993acf1594695500c2ef559bf9f569e3e33232c5333814d1060c696b2c0c58ffff825cd92ad1c29bdd4e0ae4b711639bb6ac5 +f2a92c73a5decd8e1e36eb832029d8828ff031b759d366267cfba24e35f01b45c637d7727322e60c27d55e65871362410c3a783decca9e65e821d5a21cdb8c5d97e13cc11aea383fa8f26b602e3a9d895ac042df6c2bf9d42bbfa6d11959cd9b9ea9d5202a857ae0b3c04b7097428f3291a50e86d07085e24b061972a0fafd45 +cabbe2b78d742f7d8fafcfc03d8d1c5464c5379c6a34514b255add1babd0fbb955b3280f9c9b9ee22a6139fdaccb09f1bef5f64058b9cb24aaa23b4bf88a7f34e3601ebfcd12ebd497a70c673c58b303e960be452f19b397b91e2aac835c7cc395ca2b0484272d9cea2476be8febfde504fc9cc392ae1cb89e464fa226f3eb55 +d9f356c3beb34dd9d2577d5cfc5dd9d7571cf90dfa1302ebb0bcfc194de5185237457e7324033655050e22b976a45fc5d54b5b22c0d50ac7762b6d812c5f32d682624b07e71eaf335c85205606475cf9206fef4c5a65e1c90cf4bfa74a136f592878410aa2784d0b30373067c2bb529ebd24f1c50e941137d244b83d903f586d +f2d4f9085480253f2946c7f41189ad8351cb734f6219905cb6f75537d6b505a49c350b5af52386bc59bb946ae662690fc349e18d31f70084b72d981fe557a70b2ebeebdb16a25d63dd93bfe9ac1c2a919ceb92081d7f47f5db53f9a66e5907c83201e41028544d14b1524516fdfdc1effb6741d79621ec127545ab4113d12b65 +ebdedff63552b39b84b4435c4825a88b523de9c880e30afbac8922f6a296e00ff02296fe3b66583c7848daee7b2fb7acea9144d7a5d3b7d2ca7c5e109584d08426069bf599fe097272e0dbe536caf767f0520b25ea5f678a255d3efff629aca6435a296f7cf6d29bb4c37372701c37ea7a952d483dd0d54c45ce6e7fe61e1d45 +f8932edaad8028dfdc82072d6ad44c32ef59fd388b6573848eef6239700a431e11ecffd167470a2d4d05f2e6522d69009b6883f55baac1b894039b13b98d6b5ea25bdf219c36040964551842c564b8d13ebd4b729d8156636bb7fc0a7965462b5b3c80786ec10827533be07445a6f66e54ade6155bd008f710e8dbe69cdc6365 +d143338d085ed0955e617ac2758b534a1fa75838e0f10bdf4192142c3c47180ccbcda159813a69c9f6db92a9278285315df6b65757d42a2a1555bd4ba3477dc65a9acad854a6fe0b76089ee6d7b76143fa08a67a9e98927fca7212ec94488318b291858f4fac01171bf595626dce76ef81ac84cd58602a43770a71945e41f20d +c7ff71337de97e9bcf2e125b1731a7ff3c139e4e999a9992c4d9b4893f4756d94507a366a6d8d435b5f4b8618a36cefbedecd4fb9d2c2441dc47b085b89686de53d9a1a7e673546e94db1315bc8369e3866965f55200390575a1dbc4e1e8b06d800068254a21d54c6d79bed40e25b625f6a2fe012635c1c7fee7f60e49582015 +f40213d83153dcb0b019d5ced1f4b3e944881f6638c5f325d8ed05a4c8ca5a6ce38815f336610ba4ca4c7337f15ccea090478c97f8db9231ba73af7689434fbc6ec171944dd77908cd0d329a306957079b6401f0f34738791093d71fbbe9f7af7e6945f7a0c8806abc31f6cffce47c9d539f3f4cb9fce3b0ca54d1177285f145 +efb3b6ac061fcd4bb6411ab78ee10cca7c14b5b9dbd07109d1f6e03b5ef11f4726a88f89a5e79e66a11d59e2ebd426b3f21b34e25602adddcdc87753702b1e4b852a565525559281d32a753e363a98723f365c479dc9f1dea8311d723dc016e811e0d8e918b63c3e6c3e17f4c9a765fec6c563fc1ab1ccd5c916273da3a0ba1d +d0b8a638098319ed6f0c0bc2c0e23c36b000059c0883a8fcdfe9357f4541cfa6f06d0355b0092107459a752b040722356d7f95320d7206deeee6dca587a60acc2c64d8b63067ca8ac10d221a6ae7311a083d62d180f9524cfbb7f5b3fe71f0e82bcc29b64d55872eb03b816af1d23a54d6fc4e395764d0dbdad63e494b16b5b5 +ed0752e6c489b4e37b27a1a90073f5958a7eb7dc6be8c5fbabae963784ea0993152cb9b8e0c9555fde1cda14f4e377611e66a7e7f50166a6eb0e37286a08584d931f47eb66dbb234c7e10a5080ad84edf57765b0e2c32eae6c725cd95932e94f1475a1241d1e3f8b5f11c0ad8f399332a0c1f0a54f48f81455920ce94a3d18f5 +f2164f284acd56b67aaf1b0cd0e733709c7ada139cdd348c91eeb742303f96ea21d3f4b423326ba8cd95fd1c36d5f99966e1dfb075fb13ffae9b2a37db11824a6e184980ff63322a336cbe1cedb8c748349dea7673689c4b34be89d065441eed9b010c540826a2079346eb90d80bb219c973718670ff8803afe28feab3019a0d +f9a00b6f7d7638db994b087390ba2b91cca6a3c30682898efd42f300aa20116e0bad9392294b06fee445d694bc8a5d93e4bffd7f4deef45660d39148230b955b0157bf62255e31861f9ee002e78c300a8d93c11f3d3d7bdfeefdc87231c9001fb7cd41387153dcc91f8a1a1663718494d9d6c866af82c0df6b796943ddce0f1d +d77482d83a5059204beacc0c1dea0a87347b5dc9e5dd04148a9339067121e4be8d8f3bd13a0780e6efdcc077be7f1281df3994031ce308a1bac1024144dde9e3587984dc76b919e941abe1b9e6a6c83076389e05b209966b698942f02e83861a30349797354f02d3a7d1fc0797a38e4ca94aab692e9d242f8d11d8e9ceb36e65 +f36609d7add6e2e5af768e8806280de76a92799a44ba2317d16b35d8120480ab7beb4550c706984300fce781bcfaaa7f34f4b8313ba9e350e35136e7dfe060191dffc4f60803f87a23e70e78ad4067a6ca1ceec7605ec4224d9e4056322fa932fc8d86c3eba6904028ff1391918c0628f23425369788e6f15c5237f3e945748d +f76aca3ae95e24732d780671dae23eb76e95320b999bc3087b9fd5b47ed48ac45f276df913fa29ebc0f267c5d6149f5ef3e995252ab855a75ff13294cae22463caad196c29593233bd345c470f48ec0a33855caf026b02c7e9be7585e6a2620ac8071712520903ba6ed56af7633e08de22b65c24260996b905bc7b17305b6315 +ee47144c7cc79048da2e5c7ca377d6bf54373b0235788f6515a4d05d76427797e8ee951f3880d80105a8b9a5e584a09d7a278f9927737c3cb3e65b00d166696d1e299a81d0b63b3d16c2e80ef0769ef0d49219b97d88772147bf19c8e0e2354dfe0753613ffdf82ca10b79a606969a7d10ba6ef12d41cc9ef7a0ab41a60aee05 +c7512a6fc248a703f04a4628a04c5f42925d0b3a0bbd26671ca332a46ed23a23552581ca3b14fb93c50f2b34f27a4ce81b7b32469a30a02044b08e1d8c76d48da3058e821978bce032f5f8cc59d85d30ed8f8cec79c716aaa577e1c0f61afba873d6f8709695f4bbd22241886fd684db8f7371947330c16dc5cf588393011035 +f3965870c5a0eb8f3f06393b00ff005947a4c93ca91234a60e0acd6174533882abc26e4c4660f194a8851b59029bc31ee4e696fa1fa59e7c88ea0a2b91960d4146ad140286b1f3d3727d0f67b29394d00a604e65e4240b9c93c735f665e1cfdc69cd48a2dac10a6c7b5140d8a57ad1841d99ac5631fceaaddd8d6578b4ea8ead +c108f896edb6a310f4b3acf52348f351a213a75eadbee769f484a220fee6d32fa5ef491f9d39d964015a4eb6f741abd14914cf03ef05f039b0eb0361c1dad1836dbaa575b6b9cf69d3d39c15f3fb5e1c5ec54a80e49636096ab75916e269b1a15cfc5cd374a064a3251b5d6cd6de2aa65aed83cf68d4dbe1ab9682e98bad22ed +c47513e74cbb89be9ce2f69b924430b0f84c0bf6b99414e7db74ac0db1d31100d5f06bf3310e11d1feee623c4998d40dddb72481c45eb87ef0e78b71b85aa1bc121613106dd4fc055a456dfc0a708c5c13337214aec0bff64bce84a49751451ae207b393b2299713cb705bbe69b11b3dde3f818d3fbd8293b501d027b2c2cd7d +e7c4a401decffc41ac44a48dbd92dad6142e816439b07a93c64b0b3eb772d278aac72a5583b93be6d23c85c91a22478b7ae1e9544b05747d5003809f17f89593153595cc572f9ed5815302e80c5eca2a360f030ad556787222cacad104d6c20514be1ff92047b6d194c5706c95e1c5719d25fdfa818198879d9192405b546945 +cf1924b43aa3edd753df6dbe5eb4453194ad50937d6072f449185a6f704dff7f70a28e317e0c5e8c8d67164e83df9ef57eb748b38c4a08248d56e7ec06caf4c47ef7bf09d291fe57c9b0093bbbd36c323f383170e4d1430012eadebd912ec07c507f2b91de125d7711869e7311a98f4a3e63c37cbf770fbb8f965c8d8f337135 +f8aafc8ab799051e91d03ebc27f3126b952aefbdeeec0957c60335325694fcab82a7b5ae5807699479851cc4c5c50610fceec008da4a5591a5065379db44cc33e3d8d654442cf7d8867973a5b7a967df6005192305bbbd556ceb2f51bd80115ca86fd299b9c01e51e6f7467f1dd6decda49ae2211fbc8c1764cdac271290316d +dce1b066d202d9d4ec6be321dbe4a0f27be678cbd16d30bdbf55877c70d7ef875fc895611b7c5988e1d2c5311b4885b3a8845186ac8117c17d9302b66fd1de0855058eae06c04a1d6ed84facea5d5cbb88cf5a8e7790aea33619a681858cfed59a7cb1db54f2d865c71556d4b9cdbd8539015f00e264c0bc4e4570eada2444c5 +e9b694930c95ec62abdba4da1bf14713c02cda478d380e6c4cf3e3a046fad0807211fd0c7f1f0511a28c83cdfbc56f48abe9daaf48b10433989ceb489aada52f35d23aa25b8ca027f1fcdc8457afc56b177aeb04bcab67146ebe9b2ab1e8000a8468f112c07db377bffeb6ad81084001819a8e1bf7360126e344105eaa6c22ad +f2b747825bea0eb69671e2ec7894a0445206269d9b04a1fbf7253ef4a69c05a9ed168408962ac47d852c85cf863534d3ae082e2fe90a9daeab63a37dace95513525731ec16f629465b9f7f05449722fa06c66e6c8b8a909344cf2d4036b5bcf044fd7dcbc50eb53cf2a1246bcb564f8286dbd4c6e8ca155152fdd1b1627b71c5 +e1e43a3d617669dff0cd6d191f3c92516435c17e5b3d8ecdcb57182b7c8529d7def527c14f660d63e61bf30b953f187ef06e378b2139ad0f30f4dc6193a3df84eb265f4578dde745ef8fa1f10a3b7b920bfad2a113452a222b8434d4000d53e52d8e00c3a59f9d7e4514ed175101559c636e90a1daa5d7094db4c529a0964145 +f1fdc775e8fc8246bb2da1d922e140782d2f4ffe317226701bcdd3bbe97efb8197f2a4c3b9e59d8d489eec1eef8bb2e39466a9800b5b112d6d8cd8c55eef993a6cc13a8f9fd23ff53cc12de466e24a033674cfcebe7baf90c3c3f1fc366f3c463fccc4c02dd074b1e5f83423e4e26fba1d35d6b90f22f0369e4fa35b4b46fc0d +ff0bc4dabe703216d2597fec46f08827f09d8277baecd636cb7e63c08004849a4baa21ded34362a871db6afed09e0acfc73cb36220bc5ed0489b022e1e2491eafb0f44c96bc7581ea02d841179f4079370b4dc576357723496fe7e0217dc8773fbdd09b562146613fddc8b38ebf33e283b9f3f5bd17bad87b60d34a191b9cd45 +e4d00abe3070d6233ad696bba8c4ec62f9d3b372a799303bdc4bda030d78db01cdba42919778bc3ff5703fb14798ed26c182cd29852d2488a9f72ecb2e95a7241df1f8f3372ee7990460bdab916884e47ba8ebbbd5eb010bd36b267fc0be5075dfaa772853187e02bcaa6664dc3e98cb1744a47b15e6a92352377ffd5a7b277d +dfcc6a945099cc5fc8a3c59d70e46e29acfade7843ca12f82715f5eeb8975a379afce84de3cfc32840391464413667d6c6427ce85d26fd9a737c5aebf4caa6d1419c2f52f661eaa3a05642d575f855a1650924a0b9adf410402a27f2d896907cc674c16c04c36a385b951d885608cda14a16d6905de193f7e9afaace19a26f25 +f756cf4396de4683afb81f0b1be7b72fe5304e807c430bd35dc598846a3c78a0546154c4798a43c15916f1ff06467bc4d108ea1a339f561d728c1a4e0900fb7f22da26729f5f4093ed53e44696955ff94c2211359aab0d7771a327c69ee934f31db90b4bb6c85c805abbde0feb40f6c26f9d14a496589ba1e42bf86dba5ca025 +cd3ec9b8f738386ad4b07a2411e57ecd743b145b6e865b28bb78e053be310aa7d4ec30e375ba5d2be7cc03719984c05ad512d0bd1f1e41237c1205db19f933c4c3f28135a7935b38e8bae7bcc5bb3fc8810e59a1438286b17189b6feb5dccf584b883a42c14127dcfca06e4c7a0249ff4bcd28c78b7a0b4f608de3a4fb13713d +d1c3b913cc9a0dc1c63b5da8442cb55e508a9fb81fa5538bcda09696fe73fddcdeb2b9766a60c6fe13b6b5ba25cd99414a29bcf87bea3225dcb90c9e5c05e45c982c60e36af6630d023809d0c9c68d7ec1d78088722be1dc45204a02be07f7a33f3688aa0efdd486de336a6fe9d40b24ae560ee94329995c67dc43c999eb584d +e6906f19fbe0a7b73a6602c14cb916c205e117d109800b7dd3c3c0947c65a58cb7933544b2d9fe95defc58fc514c3b7b7e661549223c407a66d8293d895ed71224dcc7c9928867c0179719421ccf1432f01481e76c79ae0c5d5b9b5c4c43d9a903df053da80aa6a2297e7727f8d263f18052bbb4691e7bc2679a4ed9b1655485 +ffaaceb134150a10384cf039e8ad64bff0d2f34e26d5f69ff0440b0d27f9964fffbdfe722279ab31e4ec029394f692dcf8701edaba8b577d0ab28e217e60e2ae885ca72b41624ea3c0793eb388e7c1d513d4c0169606a9d030cc397e8dc3cf0e59dabae23c06de3651ebe395159554f0b6b861bb22c7b0bae083936ca238f43d +e1353b6edcca52bb2e5f4856c771853d534cb7be128ad0a62fff102cc1ea0c83f143afecdc865fc72b1294a940ad4cb7a0316c255f87ba7dc848e7a6a81cf339d085d64002815434431a53c2754e59a8b9cbe97f9cb4321cd60ac9fdd828ae9ded8954efb7287dc088212f66037e5fb80de89a60a5c5daecc91c371295a2214d +e2f3e64e2b74da49aa0993acf7fbdc551d092a9bb47b9c9d5243af4bf4ca6c0e0fb7b7cb743acc4199b9829b6eb893818bd6f60c40f88095efbed69aeaa877fd59e9780d68e2ae38e5389334db71722adcb041d4bd31b009d7ea342e2964a31ce795dbb22b62a6aba4e50edd711db57d3573d96f6d40a5a0eebf5f7fc041e665 +f05bc3e8dd7a81fd90afff645e6f99124219ef8358f125b4deee68652446147c2af8a6eaa2527d9a62755f5c663b936792d052ade822b93f2aa328cedebc9fd32b787a5ac88cc439294672ef61fabb0aacb5a8a33073a6bf8665cededa4fdf80364e9a38c4f2c4a348cae5c7e29bee6c49655dc6a705c3881c7e408aa5385725 +c0d865f35d27caebe727aa38ddd5f74bfa94d6a194d92123ce6a2d939bc9208d4cb9222de08b0515bbad5ced083ff087e516d2e6c3b37755311d233c6cd1b4e7273cc800e5704ffdd28edb92b781143f1f3d259a1524a2762e4d6ec13c7918f54766e63f62305b5180d904a7a648ddf18ac448592855575a0ef5797cf47f893d +f032c386d6dfdbdfd28ac2006ace6ab1591adc464269b33f27ac5138ba399226c151e9e59894d48b0e94ef366884457baa930075b179c7bfb61cd1a452b9149e9fb18dc937df8756d55b3c8f4c2b5419cf54854d3df18df3769eb6b7f0926764952ef7f453b460941cde1b868be3ec8cd6aa333b3a8b45bfdfdadc3bdb59b3d5 +da1915e7c7e36ac52d28383cbc60036ef18185dc10eb99a72cd55b819cf5640bc3ed899ae849a9e033634c9ae84e8ee24359c2502e11ffe142748238a2e57fa08c46b43ea52742eac29564b598f32da59ae9fbcdb34fdc30d3e1fbf3b08d0076344c8eca5646bdfd787868b27fc615a93de698497580586f41cee473f9309425 +cd2946b5bc0b7e1289bb27fb6e81afbf0fef4300f5d0317445419bf9aaf4835b9e6d4c6c10ea746406e45942bd697a8147887900bb31d42e18cbf29bba283786526823540d959252f74a3a6ea9d5561f66e9ac0cadc457e46126fd3b99dee5e4252449c4121722b19bd01d7eb6811f705487ed1d4416a274798b05d5fbac5955 +ce5a745d1e79d7b335d7c88377b1c692ab802a6665bde0af07b37d70e982e0c1af32125c8af9c9cfe22e872bb4e3a71ddc49d07ee17db5bf045995f3b6f4d79da9271d51b89bb13f23592cb57ed01eba6c40949a967adb0f8e3606782e9866322f3477043ec8b65d6718bea34aa025e8f08eb0a8468050274a99bd7f05711ad5 +ff5d660485911741bf3cc5f0c060444c1c847a17c2d7b68b40b644d944310ae3a4dbe878131bc6685242ecc37912cfa7cdc6f4611f7b9873ea0e75c59cb7da908d5c1d503a1720f67bbd2a15f3b035b4e89f39383da64eff98f34bce4f3ad3a91556c39986ef56e94cb7442df4f2687fa2dc965011cf0314f9fd0a4e99f8a82d +d92d8e5d030306cbb0b4e7145a8a4e0636282a5d7f29816cc37366e95d5a9f51415539448edbc6903b07f0d81b84d5a8a33c90f7b9e4e7df1e10bffd107dec6d9362dc86513051ed064f8ff64f156903d0a96fde60811447570cecef2a77cfbb9ea1e3e43ea5d703d39fdf0e3720338f7eb201641fd0c63249d97bb34227ad75 +e046388255096cdd05bcbd3153f97722a6a7841b7c49f9c13542713b5909ec25cc2da599b1025e224b26930b5aabd4bd2e554ba3bfebd531c9586f38d317df4b6c6382b67c99bbca920e7592fdde8934cd38c07d3710be51e1ea08766709a2d63a8562a079ba6116265431cebf8cca528fd82fc77f0a2de81f6e8296fe0715b5 +f5ac18e52f951d10be9300cc6f88abb46f237d7c0bdeb4a088e9352d8fcf1dc59c5b54751bf27b2136eb9e7703d70e4287e5cbd5d07848ec3bb116d977938bfbe288d85e98b68cc1c00a15383cd01f6324f0d78b44500bbe68b27b32e19778a165352214eaa2156780deeec3274ece0ddc7f93fe11401bd49e915ccb664a3f55 +ebb41e1f5442da75e42bf4b8d1dbaec10e59b71b126a5825f03707a9608512ea068436c3afa102a4d2044ea7e6a15e70d5726d8f68f9bdc79f09abc5a2df6946088467b35085fb626a1be703441ccf627eb9b3f56e07d932e8c4b1ad3b2ff399de007cbf72017d819c4ec8ba10daeb75ee00c74bdfc11ccab597352a145c88c5 +f695a2a2b05e885de58d21acc17e7d0acec99cc82a40a0e65277cbbb12698e1101b7d0e30828a9cef39a3661e2533399a754cc67f4f927e084d0c6e756f0daa1e53f1656d637a795972a3d9e65b8e76d5a9556bbecf8243b6f5c56858a0b5187248630a6ede79b202ed70111da94a25f8e95abec93672f1650e9f94ad8e8dea5 +fb1965abe9d6b95979793175667a8174540dd9106ca08aa16da6ee91438ddb51ac0be3f88ac43b3ff3b3a8fc72ea00d669ca2424040f995a63c21fdd6806883e36bf88f7cde516afc79cd2ab91a030c9fcdf8f043cdd33e448178a37f5a5c75fa3eecdf93c24b97a75a09b07d8b6cab8d81338e1155a31305e60bc7bac49a9b5 +d08191f8d2eccdf966eea365f8eff23790b197673dfaf8727de20239e5e64acaeda10dbb9d662df04056a6d7dccb35b4eb78187ffd9b7625baaa1cfdcfa7801a19e63f5353407f5628f10f1dec5c8a02b5cd51cdaa3320ece9daa1af982899280d6942fd90388b819c3f3550974814fd336785889f966a751754054d31eca70d +cb6ae70226b42cdceeace435730d84c2e0a62021315a08f8c91746c6eb4ceb5705bc1344ca936d225d6a8adb1fd6f9ce2249a16bfffb77655d314ea4daabc1a90be9641b787847430f03f1f13b2ad301d8007749a0333ec9fbc233d7adc597331614e7789e0665676aa1eb02aa6fa1846e1570332943f304f96f4a9d467e4885 +e91ae993af41925b63076c8d6f97c71f54802d64ebaa90385676474c25c360084080594c4ec8fa2db7436a7b18b55af2c7902c4cadc0474a7fbc2e82783af9bc87eb88792a0a4241c4b99383585225f1ef1c7e46bbb32c8ed90e83cd2395688821610194e6bdb38d6fbf53870ac9c20df991227c598b300eb7955f7a44d6abdd +c865501f45da50c7061abf8421dae04745125764a6fbad33fef595783dd8676bad62c7ef7853e4b75f4b5aab8bab93b43999a425a83f7176199aada5347567b70d514d2c984bab627c8a3db82f29a0ce1f7d70c1b0de249689c7c59e279ab94c5dbe149125f79196660813b7ebcce27044e8429afd6be018c70a49cce6d76d9d +dd8abe13a5ea14ffd8ff45ab9a3a35b90e6bc79c018fd1a1005cfb7c721601ece24f2078b2289a6afd9bc3d2c63a7e5c7623051bc4ddc8bd9aabc48b7b5d2b5ca86fbfa6b8c980b37e7d46bab3734887543934bdb0a75eff82bf238999a51cc1ad0595805dc27068f8d23af344e022a916bd2012fc7038f547f92d97f76112c5 +ec32ef777c9318c52ea24f7dbd1f323cdfbc1fc91f8ef39248190d4944d320ac758fffcd5a7ac3df78761b1a27cc90da44882672496ded73f2998e4884a86ec264a2f368a6adcc210bb28a9ff66358f15c5e531044a71a7515e7c17d00758b289ab786acf07860e0de22deb357fd45d2f1bc289277456cd028da8cac1f0252e5 +e1e11019dfb7beb92187a908847bfa04cc987466e9f709f3fca57d32699d961cd6630a147ddfa228c30e5c5b6e75d9badf16bcc6edb5284edb255c64c6af7f977c4ccc5893a10b621c98a77eef18882a25852b4667dc6cb03a09a2dda391fff31548f4abaa33ea5903ac891d907fd2e2f56706ffe4bf6eee1e01cefe431e956d +fa197e6f7fda770e2db0630a8d13e4ff7154557470fab3723ef0c7af17974bbeabfa5fd67234c92790f3806e65cc938925aea2eaf11a27535053781166b8c5e9b8edb8071eb0dc434a49bd78b4d36557421f0778c122716598ae748051d2496c518c3fe07fa70731bb34186e6c26c1fbf3f927cfe3a6338386aae4f109df3ced +ee1d856fb6c508481be35e01355c6c32f4fd918834444c48f7dde2b60216389ccf46a42e5961cc40e6cda7b5ba2235669c466d6f14950055c84b339ca92693243b9b7e02a3c105cd6a25ac002d3c6182e90661a913ced8678c231e4105163118c2e719c5d5fd8665981d7c842c282944b81bd482cd9c4f1879442994c62d38ed +cd1e3cd21cb756d928c0f53688aac4c03ff7e71ef20dcd4798159fd8af9a105e5424a23c800a70de73cb5b22ba328dd799c85ddbd20c62bcb4b028ee1476dccc6eb26b932c12dd584f8fb6d70f4046a001e72161db28e2fc76cd5317edb4b35f7c878637b7a2f1788395fd5c019a0033c7c9caaeab1700d89deff335f5f65dcd +c6c7888d3182c721e7989d341dffd18da609b2578e48825acf5d541e54475631b5de037f8323981a7d6ed462c6add59fdc2bf323c9d1c502afd881d817b6a9acc6682cd5d1eb0f7d7cc607580703e3bafdedef36b7af7afedad4262cec7a345748b237804f6012f860dd71a25340d5674d1e599a4dd41b990f7d504195f7b0ed +da9a9b87449827f325fcb2c03bb69a87074b2b97434e03efffa97e3198e3e720d806fd0f5767c29340fd2b8d8a7b2a1df39327de79d848d600b78cc4be447c6110cb060281b891b6811ee411a92ac23b1b19b224669ca3512f85b6e69aca9405ebaf5fcda241ca7ffbf7cc1dd8984a2670b696d5d15aa3846d919552208f889d +d2fad86c78c301373441fb2ca6b8dbe2bd3088ae6f9351df1659f2a7491a203aba7ae0cd4c7c9747cffdba58b901680df5eba4a2dc9311f25fd1b48f4464b55b239e5f6fd26bed5a636b73ffb1df7fb7160d065492c6f2f8db8682aaff9d4b5e9969f0817bcd123be3bf3a2e1de81cee2b0fd7bcddc7fe6c652e6e7e9e3ce07d +fdafb1e55ea0bc6bb0bacc5cf0c617b39f41ad7a2c7f760b26e963b03b1cff39a7a00ece74c36d6a964919430b0cd5f2f84c86595fb34f6c3be57ac13a985083f312d61784d966f82c28a68f4f810c2c7b27d492355f88cd9b47baed0bf6fddf5009ce95576c7b6d8a718de2444b7263ac4a1bb627fe66fa797ab27aebc9121d +ce606360ad461a06a31c1995ab7d67944dec0459c06c95b0f736020864d80511bc41e62f6e58274a3a1f11d372e0f39aa23141979028173f1778eb015c84ac3a9ba7aa492bd589b16a2fcd50288d5e34609aa84a0796fc6165a0684e7f12acd722be221b226a83c9565c54eb1df3dac8d987df544fc177b9981e617e3c160d0d +e0f7089df05ba237db3b1c45bc21d66c29ccb507e9daf95001c416827fbb671390c97df7b83788e2569a5c5fbfb6843cc695f239f5a1e95303dc43eb5b6af0f1c68ab9e90bcfd516e6d6b6ee40d4f9ba37b6c048525c80283488d70250c5cf99c971e66bfecf91de978dbfcb7c6ae04cb05ef016202f30860479ec1f90ad4d65 +e8507ab662b3c98ce61e0a121f721a55fd03f7ae5112227da37af43bffc91cd1467276191a561cbaba0e34c1f454f679f3999d41e3670135b1830cf637397c4a5da44e050f872e2f9ddf38796a72fce90591b01f2e2c5fe715617386044a8a971411027d3806d6a095581689065bee15a4fca04958fb5ddae0ebf5a300300b15 +ca0a12b96ffae3b5527115329f674292820ce3d4088c63224afd871f8119479a55dab101651ff8a18c3adf9c72636fe333c4b3ebe674efde597ff92af502146127990e7cf6edd2d26092c39a5c54bae54cbaf1bffc2a4694b50c1d85e5a2f787d5f9440626b3106ebb2e05bde7a68c2898285f37ee3a28e2e1c9b0d3a0c485d5 +e3e88bd18aaf97c07ef653e8fc0a34c84fd1253a3569bbd446dff4824c2603c7f8eb27875c696cbf4468ddbe68f05441ba1ef367a318e65dcbc1933e89386b188e530ecce528eaec744f90bfa84646c9ea20bfed636b3064b3af93c55e78c984e3bb6fc651bfed438ab861acc7122055624ebd4498833c744b33ffef8c00e1dd +f8456e31f6500650a486eb37443fc7952d67a1769c9fe245af454f76ea0b3c5e7a16977abd85af7d224430556114337e54c2edbe2b3c168b6d091ab6969f75eaf010eae3967f346cfef4fe37bdde5b945fa81c545bc60a44ca4cdb881cb1d85d7756f3106c868ef2554bad9fdf621b6ad576dc6502d3d8eb338a6c661e0b4575 +dbd011e8510af2bd21a8e5aebaf1081feb39a43579a91b1287d1855ca69b461bfb39ff789bf9e18d521a29f9c0a98f801022dfe2b48ab47211d1e2eac93507553f05bcf830b9c0cc01a82e23ac09ded672c36287ec331146d15837a5132464cfd9a9850198ef18685376343822e6bd00b0042b0fb9a10c87e2e6ea25ec08574d +c88a0d8e14d4aa0b128e0c7b39ee774e520293f1dab004456f11eb20c812d76568b4255db7f62a4d003bf2cfb2e1e7d5307fdd19a37045aba83e3903cc80783a40c5b1ef779fdbabcc4b317ebe478f89bc5097d4bff5181ce8ab1485f65ed28b740ab329b4b40f3503778531f64608798271f5b7449971aa01082b742ae821c5 +f027a59ef13a773a37c37ebed7c1f185e170ff69456875c8dc725b7294883480d264d53467a7a39a69aeea0096c69f7e13a48be0e18acf2d771937bf856e63e584dac3a764bbe19db2f72f17c15f24d32c069c3103b85bde4c3addf09b97d195c26ca32f1d0db60d1bb3e8ca124bc68cd4b347950b3572f794dcf2ed28827a05 +d0005617806e66cdf95899ad4657a55406d11afc7e9509e6c203f1fb0c1cab50cba705185bdab4f711f9aa6e357f91b20dcc908cbe10180167428ae569a9aca1ea17c66aae42e99076dd72f18eebec4071c1873f323cf6fff816845c25725c19d59a5248d680f85e487ffb0b50400853cf228fd8c5ca39888cc5791cedc9ad25 +d54b2a1f600724faeef022a75a803724389d6c3bfecc6263b54c53705d78e0344e0c6a9fb7d29f707c068adec28c17723225f0fba495f6ecd6b6a379c3472665beaa8152cf3ee69b619428f5e38c874bc9b648a5c00b844288a46c4996108d3ebf3c1f43b8cb327d04a59ea5c108e76117c487a9b82f93cf928738bce6570655 +c8284c1ab062a0d0ff59bcb818c10920ee12ecfc4c628791d690bd5aaa84a9cc172db8a77a6139640968953b11462225d2c980826a017fb1260569859a2938e2df25f5afbd789e8c2edf0acc71e5cb2cf082afdbc5b4a1f8262df03fac03a2b05f67ab51acf7fcbd9e48d07fe439dc14b0d082d44b0b761d76bfb17a7bb9b785 +ebdb28743e4d9393c2dccf2402595e183fc6888fda052ffd2cd7b6198f09be48f1377712f042a63eb1cfedd9580099ca32d4a61d32b5945557ef67d9950d195009a4195167338243c83cf66bb24086655741fa93c99d3ab51b36ad100e02299926a272d56ea1a1e9e3a5b6ffd8e8a64abf5416fb09d409193319abe153409d8d +d65eb3f296ca96032658f0770215e613015268edbf6c29a95809f565029fbf9bc198cd69d0cadfaaea3105914a0fb42e909108417b0c429eebd2dab7833a3a840dd477de79e8e0a293fbb0a852522075dc5136e6c11cbfb0b17d55c0a7efc5bfc72e4b4b388eec6aaa0d0faee88dfd94d5d894f1835646470eff1814bbf139e5 +c05e541401f5c14ebf63bd8b9d8fa9634438e7fb90abd491f818d98000e0ffbd3452f77ebc328e2be0449f41d2c6c5bdde140ff58dfd1574e9720395d703fe10ab0cab8bf3d3ee0bf1f733dc5fd34e0da04ca61dbb977a83e27f09b23686daa5f094adfb78651907dfbd1ce6a6f18c54d1556292e1f7f3e94d76147a89fc0f2d +ee4cdd912fa1bfbb9665235f0a06a7cba1a8419cdd12f8a994adb73f241e5bade29f12a1a1e4e81e5d3beb88a15ac2f38e8f55361355a777706812db82c73b6f332703a9f5a4fab39a3de4160b5db08ec30b7047b88091ed3c94140cb753eeff87303ecac29edd3ef2332a546c0b871cd1ded23f27b33852d5bc05fed9c34855 +e1064d22e161db40a96faa22fe4ba463b16a94d17307cdfce9d9c0b642c5d167f1caf1dd777d8623f8dd0a1e0beded6bd5944a0107b045cc4310e6550db18703179d67377af2736ae9ef32b8e625a31254213d2fb63c815a960f78c18cb678ef77aefc1dd3bec2a7bd3d2e976a76fa7a8a6514d1e5d18e55f3dfc09b9aed64ed +f6bf2537599214db59122973bfcec0e66a65341e7bd9fc04c24c9600956b6fc07477d954d1bca1c854469182c9b31e92f7d78179048cd423ffce6f9cbcc91a38b4af16ea03a8d15d2ff8e3412dd9427f9954ea20fbfdf1fcae506d5d732b14a2e0bee1d0f93668e71cd30ee6da165e77dc8935353063de550c8d72573b66f635 +cd6dfb24defa5b2b18cdef0eda2adf0e861869c77a9ef643e27d9d7a02f001e32fe9927c1068aa0b2ee6de142623b161158a02af1c5d49619453d69556bb8e4dbeb36aefd74cdfbe384924e4afff623e4f5ea764c41d65f2032ea5c15cbedf0beb93f852ad2181b4ba545ca1d87fa9d9fbc84d37ae6cc0270e38fabd4ad160bd +fec9e49cce336962d2617746705dfcdc39ddecc68edc64fd17ce44640aed8577882d1bacdcbd504647ecf987095d0a3dc4f5b8290141ac0ab7442b4bdfb78e80f3540a0b85946bf8496a0ad4bf0d204c3236524e6cc09ca68f23a341b31ae7bad457846b0a8c37d4b9e09f21c1db009dbafae366ac179b4d5885ba3f3486515d +f4cf8eb9f784a44c2acb080ad31a8cc9cbc2896ea56bdfee5796eb213b1c5c06538ae73f6b8f3542fcd35a890a247b4332d99a8d58ccc39c3d663cecf6b29cef3afbffbc58eccc3719e9413ae52631be34af052e851f28e5fba511e0c4dab5261e5efdeaf4c4de17c4cf2531963581e038c878b677a365300a377cc8a36c6e65 +c5aed2f3eaaa7996b9b4d936d94d5e73fe64bdaec3a3b2d569a59c030929d43ba23454e922c76bf3e122a0798f6120f87f35a5557ab785dc2afcec66fcae2bf2972872613b493c5b9628d6151ca970b6f8185025eb4d253be4b638d17600b5aee7661afa0e6f56dead3d2de75c6d4aa0c0e0bda945620b44df8cb41f05370ec5 +c101c7e5ac5a0c57db703d288230e7283bd8c9d46576411b6df847260851335e155e08770d9ef9c2ac27cd575860cb65fe85eaa89eabcecc69c0deb244b7fa824cb09763d151a23135bec7a26fde2107af92b10bd1994c65dd25ed9b433c382443987af8edb72527e1fcff85bc0a94ce6de34549ce57b2c74321ba3975a1c0fd +c8462781628fd6bb2c9f47406393ffdb3de02027d4bc6a4eb28ac3cb21140958cde30e5a6201ddfbb8a1f1d40789f439b295f46fe034e077969f895178324b9f23432053f19c0de921076739c9115b364f2c1ad3267b3281814d99b769b386b60f14a57dd47b4095e56b6f0e838467be9e42d208f5574f231acae151c2ff0cb5 +f37aa90dd2a3793e0089a89a0d26e05c38a73328a39e21bd94512b1d81a3804f180f98080230082cf34eac7a602d78c9e402e77a05eddfc6eae2a8ef68b219d93c21feff16e50cb51376ffdaa51b9a930a9b18528176db4ec0c0fc2a535a0eb2f82eb5a0c5b8928f199458796ca0d7b2541a275895c26048dee1f7ea89d61d15 +e47f1e715bd6328113e2353bfc797cb1a008bc12910747730ba17e7e3978763485a68d8a6ff3d116c89cf81bfbfb00bb8517d382f841089a688d33bfeb8ac83d6269ede3d04f831cd65fc9e176c5295b488cf4571e1269a33dc41b1f079b7c0c5981733d878a5785fb261779f529d09d6ce90e94988ac4e4308aa25734ba08ad +f3b54bb99708acbd6ad66450e54026cf28a3c958eb019bc18f429ce68c06afbd52f63f9769e5897718564aaff8e4771524badcfce709a67c6350b178f436f48bd5dd546f3de5d9cd2b68e0c9ede5e9f474703d1956e7d95af0c8bb8d75568b200acf5f6717193ae70c7646676481af3dc2be3c777e1932915f7d5614d0af1e45 +cc59d09c80ee1962acdbaa0443945b0120ea91d7b95ef6ed0d325df83a8c8efe9b022ca8670af6fca2e9f2f0d0454e6b5430a8233709b2a4611163ea979f14634341446b8be00f4dc7b1b6920db2cfbeb1a75861f3d38b373b03a35ec8270bafec241f226dec940003b52e47489835e46a79360c963363aa862e5a316cbe3bb5 +de3f7519b2e3a2538cd66e3b2ba8f5ee8e67961e89a5560f1e89a8576f425dd8c89ad8a74ff176d76483b75b91ecbde59764e9d477d051374e9276e8bf16e132b66b5f33ad93bdba3d3ce14957db94482bc0a1035be41ebed25002e0df2dea3ba33f0127be27eba5c425d1388f81a6b9d52f7393e51abb202a04a118fc9d6685 +fc107d5a74aae7288ceea89bc44c366784a36461f2af8005d1ff880089fd7190f5986de041a583341d8ff7aefa3da09bd36ed24c2ffecf1d0951ee1b34ebe1cf737e4836daa7a59fee6033372eade4c8b80540e6b3c6c0fd4606f6167fd9d355f6bf650990f5fb64b27b74fec2beb1e0d902c536e08d199dd47734f5b7843935 +e541479fa9d88a4bd4ca0f6b7f1f06c799a8c1e0eeb441ac7b8439f08fa1c6a947bdffa3e31c54ce678bb5ecffd40576fba60e365bcca77d479f9169ab9f2f1ccc8468d53bc7ef6f2a4b533e8c3c73e3ecccb686dcbacd448932f3f7fb68a2505aefea662f4b87f259afe07bca12cf37de5b0103b7df99c5bf85fcd72e2dbccd +da26fb8b8d311836ffd5db5064ab3390a02ec5d4f08b92f18dec3d14a458decf147268a65b5cf85cb809e30e6a69b032c3ed89cd6703d4a7523ffb5c7863762556ac0d23ef72d41b5fc3c701b603771d859712d20bf486808158b76e3c647f9919f2253093ee441d9be8f638c38ada2c7f5e28530141d8bb0fdf2192a023f0dd +e57694952565c980f833b0448ef58ec973a1a6e9f2b497bf0660785ec578ecf9d3011ac2b390639c74ba8ca9ebbef87963ee133babce7aef70d27715620572f857413e02701a1070b63868b483c6ebcf419d66e3aa41fba5c4b2130dbebb8977e20489a485ec415ebbac10b169c0b0f1d0c21a96c52c3a9cbc71ed008126b165 +d6247d1a9e2c712a162f0d894c7079d3a8beb96e25c62866b467164d1172e99b15b583ef5c26af9fccb964d15cc778e5bfeec1ed3e5713aa3c09ac3ab353ad6c57b91705f910e8e0efc2d68cf81d7d604853399f811ffedfc6da6030137b156dcddc026eea0af1f88602072a4c0f119717ad6fa98bcb6b78a31e89e6f2dff105 +c19831e7cffce0a3b135f9fe75c84df6accf140118c762dc8d7d24bbbd58d61b93f9e41d49581e7b27b1322d16cab0ad0f7ab9f6d4885a1d9532e9126e1a4a2c7316388549ad1084335054a19bd1cdf2e7828d529c2fe1fc4d764b31ac490f26047a1564aa419dd99c9144095e35650b47934f29fb49343702838630fd9b2605 +ca44e81c9b8fb87c9da389778ecc62b97f40d4f4a5a0a1c6644e7aafd454eec28d58e5674b03d4135c21feca01a3760d11bb919b63cb6721c94e99cb6b56de6e0a649589b88bb229847006ed55a18b344a4de9b175a2ba3739220e6c107662920307af9e1022c7335a0955ea358df0ada2ad3943680ba2e0852d6e3fc2264b25 +e0ebda026946f37448a80a6d6cddfebc8520f7336c499b18ae59daa4b9122f6591e19f47a8603b4efbda0c07b60f6aef095b4143394fe0055290ed3d3ee10bc224d0df624a735f08e1562efc8cc338d83cbe95d372898c0985e947d1d61a0fdad9ad8ced32ecc8de121e3c52033b6c606540e996241a981387f066209d8951e5 +c429ea9d461c269b00c787861ed26644469f9384884b028063f9b556d9ab8033f12e3df5e2ff8dc0206a2ed4060a9782b1124c7886227d49688bb433a3f7c050130aeae8c0cdde9b6d9cdb3b0d355382039241518bf44bc5320d0be029af33298ddfe35caa2cad8ca4c3938428561a33d67453d3d48d55964bc89ec16019d5d5 +fcc2f150a65b58649c0e7986e99703ba3ded0a3bf24c3a514388580a1c992b141fd974948aaed36ddfdbaacaa97890c55f5edb67cdd0e5c24a0af84d07602143fbdfcb751e6bc881b4899682c0b45ebb668ba9b86c8100fc1a4420190b51e832c42ef628be065efd668b93058d3c50f4187884f49714c99b9b81011165ea64fd +defd2f3aa104b433c09ac4e959331cc538e6857aac1b1d34abc204712d0a3e081eb43219f24c5ef2ac7991377497231fc35a504fd18bf370bff33f9518b1f6ebcf7ba527ddec3c27931a2534311b5af43b615369b2436f157ec9c4924a74388102d7d702efedce8a9e1513731c2eb4a498dd38ef3d48c2671bf0556febd61105 +d24cd7fc935c6649cc1c4016e9b434f99f8adcedcfb14c10d19460559b79604504ae630eba090a8e12dce804a98754dfe97d418f69faef31b36c9b84494a52799fd6f6554bbf00bf09c6ceb31d80c9e898585ba514db0da2d5406242ac6425da82f54b59116ad5a8667bbd36359ef615124aaf49eb013b12c8e73c95ef1754e5 +d8454d83397aeb2f95c2ef3f631b09aab13bee7e2ea80a60b23f9b62a16661ce173a4adfe7cd6add3574eaaa2e3fde776fae2512f896102efda5a5d7963b0cb0d24c64cad43df1a404fb45124985fd979ea12e3b8e04986dc79424aad069912399f9967819336ffddfcf59256d81c31287bc0f826259d7ae518f5b4c90bca635 +e5294125e579b5344bac95b5bacfad8a53033192761a264dbcb51cea719e9c25ca5777d647ae22590a3845bd525e431752718570492bb6a7aea199976a5d5f88389289ff8fa3353e1c28ca733bf509982a478433f0690a564ffb77abd035f3cb0e3c2dd9cfa27d0213bc9a564cd40421bd8553c08d9cdd2c798affe3ca61fa05 +d51665034d9ecae075b5608ca2418916d2ec4005282a8324e438aa40df2c4fbe78d339c45293945203845d3ddd9b5f1829c4b7c9f06932279015db0426e12074fe6922892d1accc23322bc6c559b6238ab81318e19f2b09f8743c84b95a873fe6e8aba102ece14ba72ec41336de899675a01bd97803e9dab17e8f5a039b5e8dd +f186114528ec964fa924e4d9ab4a8968543c4edce6b5804d36379c8598cf0cc805034bf8f9d56263998506670eea8719bb510d5ae42b56906cbbbdae35c1144052c937d208e88d4604ee9649efcc4a6bab36e5610f3dd430838947d2c45147098ca1700c642769e7259025793028677068e41afb9ce631593b93bd17ca7a8575 +f16e8d1a0917b9df4114fac8cf2793b05359e131206f02ac1ff291ba5528232ae94b0f6043184b6adc09628251e0ec605e68a9c1e1ed0f777496fc8903fafbc624206a7de1c8ad6faab3baa383e7baf8200d55a5af19ec1344410857f3c8ac8d2f3ead5c582215344e23dbeeb91c862fd3f2f2483eda9edc4f170175f6e95b1d +c338871392900e2f76e013b9f1da842dc7e7631f8198033fddd6e8eb8badf74b402e5deaf65d397e8adde339503a42b7c7e0f8ecff0e09dbc8d29c58650dd694004576ed197da2838536e3b4343d3ca0cd3b762ffb994343e546c9108cf4f143c602749daa477d46d2d55d3a34d666b0f4fb423c2d485615177fa71329bf22c5 +efb3426bb8d11a7e6fa89fe0cd868e1a58743794ddc94b08e8b187efd3a22080bdc0cde343724416e6a2200be91c1f020c2256aef6d66f661c87958c366574c0778aa225160bf2e5bb4297a3d9a22ebe313532b04fa2240f93568a07b1864632326bbf5bc9032709b9b219fe8ea3922fe113a2b772d9fb033a5163c9fd95c6f5 +ea0dd3767681178a62cf5d7503bcf8079b6498453c06fd462dcea80c475c400b6072887616efdd6892efc06503c78dbc0f1e77bfa67f34f3ccee38c1843423fc5f3be216fbf950a631d90191f9cd3a7fb8d0af712c48534a3651ac75b482e1e5f9b27c9125f08a30480d72a3a275b039c6cda359798d2e75fcab0dabc7378d55 +e7e12f4fac98c66c07c1e5bf04c07154e5f5718c51231e0d551a2995f207cb71ace535541d09024dfc68faa213cc95bc0a1b07bcf1101c623560a1f4ad1ec3722b746f202537267c69641d91500880b9ad9f44d07c991405a1d2fe51f5810d7ee081d4113e14654efdcdfcb5857830448b1af7dddac073c4728c798cf393b315 +ca0a6fc6aeb2c9b9d4298060834ddf9c91f3b4f5287a3dab4d6795955fcda75753baae06d7e73eff76a3a0372b90259d6f805e2a2c3aa6a368e08e8100acb1dc2aaa5126bc91ae8e3f15b8cd638fac50f772832b585b71adc258caa8c496ec6adea9b7b7a0ba2c39dd02df58d83ac79bb0dc80367c818948f056d655aff4bc8d +deb89fecf1aedaddcf0d635550756c7570c088063eb88c84fa9e4e5b5da2c7d2f76fea3959f771cdc05a8e1066267ad88dc348b69d6df1cdccd9538ed5049f63a58d8bfe3e28fbb0978f9aa1a300a4bbe9d07cb83f661b2d5fb2b3eb7be01eed03be859ea4eb5cce32d1fa3919966e1e56d388a9b860a446c4a5810aca2e2075 +fcf39e5ba24ad54f27b50091da98de1bd4799c605431a715acefd96fb8bf1513cfde5f593f22686274fcdaafdbbd97c08f02a8d76064fc3af31041459747de9ef4ca9ce18dc252dc8d61ada1350100ecb0b1f97d769ca8bf5b937d0383e930a4c24b68b7a1742472732efcf927b37245e82fd08162c25178d477b515d997b915 +d790dce9372e7ea1d2caee36f69e987301a1a7b1e52bb53f091a2961af1c324155d5f4be1df673285bef975c141e583cbc91f0404c2fd1d005b00ac8b08c5492b1ccea8976dfd1c571471b50c521869a85263989f9857ccfed16022ff08573c672fd29f37be59dd27e872ef5747f87b0a641bb6a8ab487adc41b900396090d35 +ef8f422500d4490352508c3e9435ae2bf8372edec93d6a850c0ecb2f23996556d3635b4e0bc13544481773cf1ddc2663c36c7b4d340a2fbb3356597eb60765f876f8c002ddedf606cb86756c16db077a5fff5646b117d21e83facfb7bad9366e2532051837a3ae753fe0d494c34835518a8c247ee2d2079259b72c2859b4e4d5 +d429f267e30c0de6826ce7f65f90ea8033c03ecfb5dd9b4b90ca47495ab5bb2752b681b4ffbacd304ecee282c922ccb042a41b462723275ac300c8b0ab2a8bd83c114b4f516497c662e8193a8bf54782998236538f41c0f5e6ab06182aec139a7494e1827c3b78d7bebcb07962bb8d1569ea0873c9c6318d3d1cc2c422b5a935 +eafe2313b84fb9e62849079d30c7574f14bc26f1b8fdd3f3d65a81508bc7b6a08a1d568c1f25246544b939f122b1cd0f87692f0c944774aaa8956cb3df84882f5c38eace27bf1c6a3eeb4ac172c9d226d540e7a77fa24cbe6c4c4c79e38d23038b0d517a17b4262905845c185ccadfc2f642752cd15bc48a4ece8eb5ef080d2d +f470db3a4ab8db8f77a9dee199005f9e8223821083326eb8e9aa412669d10c7e018159e8fd5ababa671c0c3403d7e381ad7786275a17e1ebf478c75f5e945b07d59eb7959e253731b3266dec6193a2240ecdefd82ba1af59829ad0b875724e87a9f166918bedfbfb60e6fbc871c4bf5dd0dba6c3c9d46b7d199569c85b14bb85 +c71f44dadd29e0dea2f5ff3fefc4dab4fe6fff677c82058da2321f5935b35fd5a241ba8760cff8c910732e798f5c1a2a7bfb728412daee252c20f764c818c7d4c07c971f4a60f0c1ba922dbccb419ae96c15e13bd99526a947783b68ac247532087709a6b5c42e58a6f1493b5f418d5d41b0163441cd6d4224cc762767b76c85 +c4c55b788b5ad843666d57cd124ce4c9d3b0ab6f4074fff4bf2f77a293f35693457309f86e6cd9a3351609672a5b7b839ebd4354d565a44911cdca108b95d5f4d51ee45bc5a2facc0579424264ed19365de0a50915fd5f4a8bd8b8cea7df39226f10838e84f39383927b5385c4317016f4946f7ba1c1aea81d86431caf3edb4d +f8b46dd21201e8903bbc4c4fe61b97aa663c3002aeb8434a6fee34b462a781fc99f0a483cbe9d0f20bffded09611706e81e8c2c7070a58c31acf85a65830d9b6b51bf0e807aca1d291143c26b35be69db73ed5589ef2be4d483f97197a5b8ec3131bdef4675ef7be3aee533ba6bc1330cc6d2fcef3dcaba336dfae59a4e1f725 +f03b62129d41a09715c112d11c49642309f96a429af38086aeadf7c9e8d53bf487f48ae8cda37ea64f62132467f4a0ad6cfcfc6ada4f89d4c48f76d5f3f283bfc2076bfc7f757d94bfb152bf06a336f98a4ea411af5aa068dbf8e6ca2ee9d80ba2e48fe71bbe6ae886d3bcdcdfbfd73653ce43f963435181a9cc79e0b27a02e5 +d861169853e13e90282dfdccb3d9aaeb10c2c4e996ba1893937e2aa701a56cc27ae905aa733759f008aa1c0b67f6f2a01ba176ea222b7d496a5769250c343c3e7fd0489b683684efb061f35caca1725e01ecbb4b4d3b1d83320007fc66cbf040ea0a6aca3c5f2a88f35941ba7724c72903597aa621e97d7e09c8965403e1bec5 +d9acf96b15169b736c44d9a12d7e987e2c2e2982f2aaf5a446cb063e5d911b1ae053ff07913ed7c521d30e1f07534d223c8bd2b50344396e839c9479adbd23c196f01c166d7eb2906154b3584ffd365bab7164c193a5691a5047563357026f2b404e17beba02b1b000d9e66562714413ac53d12c84969e89f014175ba27d8d25 +f7b94f600362ca592424d0476ec8b022bab10653d3602273e3ea8ac58325db414f451b82c9722803ef553d0de850c1b8c230accfc51d1b759113e2bdf16d16e8e97b46a25980015dfdb90918dc3f3bd36fbb68b7954edf991bce1c291a564259cf642531175b52be6036fb8e962dc3d8435ee4d0baedfe8451355b3164a3391d +ca0ee7ed1ca5bf9222afed87fcb7c71ce8e73addaab1d3d8b6a22f00d0f4ede8b3f3132210995fa34fd85548740079313837fe8eb8f2fddc65f9de71031af83eb1aa6d8bdccc63a07c932b8c0b1d447bb4278be412e68da81b32ab8c38a9c4d8183fd89c16be206ce26c464065166c3560c27b931fb321ac06322909b5f370c5 +c3e5321f70946d1e3165628be877b8b281555e2911dcd721546811d082ebf023c7a745a119a40047e10715b5214129e9d29fcb2d7743331fcd43b6583696e58cd388892131fce98ec1c5b470492b37ce923a74e27994b147e5ee42ae3827165a4e7e532d2b99d9d24c35af491b49665b89e4ca5039ff9d0e71288b4b8d40bfdd +d3c922f7e1e70137012dd1489d3c76e8d35a4a71a052ecd459a7df03403afd9a530e0b7f23bc500abda5a4ea51016626c17f410d85cc4ac798d1a264c94105b736b7b0c8b5503ec701ed80af25a8b2c341e861f8d0ff8f0a13fd01f0333a80e57e0ed6473edfb0a51b67cdbddcc6a28808b371add5151c5d5ce45426e0f6b5cd +fec3c5a877fccbedab3019571ca33cb89c2a2f15f7188b18e3833da5c015527f01e54d2306086ace38b05e2fd9e5bd8bd4a669850f86853719c05715e885ed8b07d01661e0ef7e59d16119542cf6fcf052d609b81b85a60ec50933251d83aacf39f268e4c3793a467674708068d24267678b550645229efdc6cd54acd7e2c14d +fc22a6303f3e5c1ff1c907aa267144458f742b0c2877cc0ac406826c4cc68fe13758b928f93b1dfb2af2f6d7f72ef79aee62f5976ff3fb36478f5958956463417e74e6d29dd438c1f5f6e20d22a046a662d442ddf89c45ca850ac31e4d90f1b4148ba7e9f509b95bb7b2127f50ee3ae67a637de417cbaad674d9c9ab960d654d +c5e7d7d8bd4eee2149c6c8f40ba59f1cc8231f7ba294e6d5b7b4b23ff8a824cb1d806eadb5d3be8444289b12a4e1622499db5bdc7ce400e60aa21c7c8180caa399b036e388e1b777a272174604714c5944fca044d6085665eefe70ee6bf2cc00c9b388f1cb96983bd3bef01474714c89b24cacbf91a8ffb05b1ae9c4a4188bc5 +d948d43d26093a2ba551b0b2e23666c807e127b9dfbc43544e28d7eb650f537f66f7c0818564c960ad708ec73b7b9bc5bb698f1923a76e16d1694e9ebb4c9bd7930a951feb570fc9ae1ee7c6da44661e3d42d6e61bffdc063db904c70c23f91d690f91a80678996dad357e934ac2d6a7d6465b2f2cb1b426a9f49c58da5527ed +c05c527b0d5fa435fbaeec6762d909b72b29a5a9e78d723e01a27d3e279a69900a4befaf68f5faa93e1ada0807c692090bef24ebb7fa5592e6a6f914d16e928dd319559947609b39857f31e72ddd5d60fee1b05dfe8ba63ca3964bca4f5a4b713ff38117be91e0c99662de63068322e462f6e5c26728edfd1f2ad6fc9b0e7bf5 +cf63801c085a59629b4cc9ff4a84e8c591b78637935e74f2a0153b168af7319393080d5628a9f42e3d7e2c3f81abdbdf0e47c0803144323e40b67fafaf580c44a56361e140d9f2d7dd5101e733b6225dee687cd695899d1f66d5bf6aea44942e31050de4d15c1e575f9704a5e583cd9ff6bc255a1775a5c1fe2929edafacf265 +ee662270fbeeff37339f9531cb3cf368439d8965d87eb53a6db314e5326bcc25a8380ff08900df663bd18d9f955d05743d7b3689120df9bff9e2cf5d2576e5f8755e30daaab8f923d9f7d623342469d84e649c47596d4b58d10022c0d2fe06f1dd27708dddd8d8375e32f8f147e293832ee60528eb4e7b3dd57a715f9327ab7d +ce0a029939cda5b28090cd0a778c5542a424f9c82daa501da76aff59fa8ba8ec2c160dcf87e0a553b29ee948c66da6979d1ecbc7b37c91a89860bcbc543d46587c5b1b992e490d9c2d813d0983e93e396baa79dd4105960c30a75ad3492b873320cb4b3442e8d6355491388b6d59153b4da754a6725065607e6126dfdca4e27d +f2e87e164154e1daf3e72ec783ca1f8a5df3354fcb358ae29a4166ef64129764ad1aa1f33fcd59a3c86bcaab4b5f5b38395761539995a03fbeb491ed66cfaa68876b8ec1a0a079a127e14f82d866eee6f9a8077700c74855044f95a4ba1ee7c2021c7a642c1a2e734f8dd6af7fa47445b8c68a7730976be70388b737b7e374dd +f968866268344e69654fc79a607be13aa6407de096255f75476165aa384c674f795fa02a5ef8f19304025015baef3a7ee33513dc9912d512df4f7beb23140c011ed78a0acd2338c6ccd0eb1db4c1d33d165a168755c55f58462ba874d436d96e841eb0a5d00a5fa230db14462f03749a45a38ecaa9c26caee2965b3b2927aebd +e783c7deda612f1caefa7b7064bf23a0da09d2ae09d76d3841171b772dc8968ba9e53ad2c2887999268cf9760afb9233f8b9037063106453adaa744ffe6fb9509cdae6a1bc15f4f771b6c602d68d1a28be70c8a160d58b9d289d8347c5616936b63c866e7e5d6bf2b0143e0b6581ed2e2b0e564c7ae20425fcb18e4ae46665a5 +d978ffc21493198cfe598fc3d21d66b33b9c2bf692ebad110425c5f048b983aca82ddd6205a96ea5a61f42e1aa062c7b274635dd3d464cc49b63c7b35639af93ffa1c1e0b05b7303abdc7f25934decfebaed85a9575dd1369097211306bd9cf032a6cfdaebac6cd109945b7d56f32a1af5b9863e5a6c69300f87144b92db6b7d +d0422b9bdd9a3edb95cb89d1eea6ca34b686a61a8b6512c8b1fe1189a3cceb7734b8482ae1a4e2e00f2c567dccdf1a6079ba89d0116aa2a70c8b53f0aee2b00fc9e93953bbaa090b4e4ed7a8c41523cb7b05d44c267994e1928eba2422f4befd5ebb730b7e544b0d51ba4052b8724bf16769114a43dcb11b02feea881d99f705 +ebe131fa523c7ed17578e2e5823da2c3b192ac207dcd0d146ebcf735dbd17459132dc47b3d16950c395660957819d219b0d1e0cca9dc6ccda229a51247ab1112cbdcbfcdaff75027336d38fba3ca3b74b9a8cee891ebf6d08b96f33d77730149e52e8c8a38900cbd971cc54987a44ce90986f2e379eb9c6a20969694ae39905d +c3a99a38a01217be7d6eeb8e2eaa56c4a43feac43006201a3f8328de13398f452e051702cce7292789b98f122ec96cb82f38fee6ff5d92bfb420e8ed260ccace5eea797479a20fb214fd20449b012c178480a35d4320046ad3889f1ae3807a33ce322ebc5f6ab1bc8f39e05231db9e61d97f5aea8bc892d32bb98ef692689eed +f5ad28c75028eca2c03a38cdfac27e29cb9c4b99a15571a6c1342a9ed21e954f5c1f442b38324ac13951f883991d85c7fd03821a45dccfbf076c03114638af9fa416a2c71b12973019d164b04993ad85102a9db85f8bee7b86df5ad9fdd9cb89dfb07e60d48c0ffdf68a86760c77dc670a1b3b62d7886e4ea96f2ff88878db9d +cff89d75be266af6afb6b9eea3e0a02825b85cacb908c9a4911be00f3b7c8c5370c05c0846979c7fa9b2b18fcea91703b6be71912927d49250ad9237a59968409ca55cbc08ebc54ef98338c59b8600ff687733e4178dd09b49bc9570c86e405a1f137a888c902d729cbab861ed5f965e1e50c9d8c3b426d62185900429a22125 +eb8936415ee74a4aeb09de49a0d7f4fbdf19223bacacfbaedb5c51c0c8d52f6c061c606060d3f9119a4fdb7b4f17b4a37b624392377ab9f81d769eb45495272fe4bbab7d110c3fe7d86d1ac2d91753df8915a8161d2f7db2378e886dfc0fe44f49c4fa6bd601c572423a37f5f9c3f40ee5c04c8f6cb5452273c0d2cb4040f0dd +f0ce9f411618305ba4a296cd22040713246e2f3ea1f723d2e2643b9fc536a864458b48e388e542c360bf5917aaabfb2cf1ce1e30d275ffee99137dd3317e19a2c534a6113e3d89a6665e55902ac1f119a0ebd3390ede13cd04792c89d349fee7c66f17b30e38f78811c81d907e275167b5c3000e58be7bda033c500a172d889d +f7745942de25894a96defe06117eee661fe59b466be237cc66b0e3854cb6d3b9e97c8b52e216458c6e9bfe9303299ac6294f2d62000a964b128742616a726c89505f1d2f493a68e0ec22b80a03e0e478bddd2760b474072490ef5dffa6fc01410a7fd4cbde2aa34b82172deac580cf3c3ee442a3a096597dcd8eeefc285b0a4d +d32dd28cc369ac18b8a088e49062c13f8a344b83cf354849104450a743b6adcd27ae6c321068f49a0334aef4896d83df1997b865f658486421ad0c5773652236a07a8846ec484eea7830c30884e7c20c35d035bbb4b47a112cfda9ba84db9b3dbb0b0edde6d21f8022f5979b3dc441245e38bc47b817ee3b0be50668f8b45cb5 +e26f4e6b3f3588b49a92dbdcdb3e5056703af21b7cfb521d62eb318105df1d3ffa8001629da6f5d3429dd767016ab9707c009a4de738b74401ad916a4f364b10abaa44471b1f16fca5c12a5bb31b6a54f1431411b2cba27d762a6914a6d13b52c42b3ef5838cbd502852bd490fc9951505f81e8dd6f10270861d091103ad5b85 +d640540fe9113341d00aecb996e15420c007a758094e6e89d05f80c2fdc165bd75475286235f5bdd659227c55bb901c2e55724c2af4baf1b989c71cd46393badb590c58b682f0e7f0ba01359232b21aa0fd2cf1a8c71be06739f1237a7de275ab4fe4463db572bffb219e2fd11b8aae37d00227dd0ed1fc060f1613d98d8821d +dc86ed947a567f74bf4f8c8b2f32654a0efff484437a049772e6817787ddbdbea1ccf4d61ae351773d92078f2e2fc788b6556da2955203b2184b0947449ff778359a1fb91bed9aa62885e5c4e3f7a359185a6816c95be610ff82a3d528cbad1775884501b5d5d200303249deb845a6656d066182cc893f0bab7523a5f8ab8185 +eea1ee526723cd0ecc2e218d71fe39bf912afc094eaf6acb4b3f2d76dc104cc9dc5a59cdf7a18493bd90462eecf9223ebadd096d07ea4f03d680f38afe3d60eb8059ec466294580ea9ccb81dcbe840fbf7b65b8d15d624db9b0b9c84d0c3d2549e3782fb1bef4e264bb3bc2d1664c140b4854f513befc9d48b9c13603539220d +e12113c4525a41a9f3858d26a91cd61061bf7976f1090934eaad962220e176519cd512c103cd4b655c87f83dbcb274be6dfcae131b017ea0c3df253bd048b8498256d2cc862eeb4a2d957d9f3c6c746bb2e7192c116efaf3e3ffde5af9d129ee65f3ae419c6130693113f3be41af7f8e2d6832aeafd2647544eee1e6b8aa816d +dbb906ff42edf909c7c1f3c280b607743049440761021499a03a9992de6ec17bc32458e7d697dba61f747d0ce110a24c710d00c597316f2d079f1ec9de3f6225f3940286f4be61fce0ee9649460ad389423a25922015482a6da7524bc65280bb0de9f72c81e459d40706d318632d4a151be0f8ae78239de005ff723ad4d88abd +c82ae8d45b214bd7cde7be84140b8f80b3180966c4b154c6c7d77b94667a3e710ea5dce00ed4a048a8f541cd61d487d014d97c0da902c2ae55a8e8eaca23bb23dea15c68a9ef146d08de1cf61dc589203acaa42df4e1941cbad3d338b56091117af1c9a23904c3b4b7832482310c0e2f9261b1b703e3324418972d903881e135 +c0a6505471e66553d80b32defa0199a97d57b22af9ce17e2a2bb7eaa9a0ff64e76a5d4e05a982d51b8db2e9941480de002035f3ef566f10bbc035b799c03ec58735e4f590cbdb14388b84328d33c53844b757b4ca44dc3caffd9ba8fd6b134da06e0d4c9d8abc88ed43808d73d2b2fd4c6d4e8cd70f91b22ee246e2ffcecc24d +c130266cd4a28926e2f8764efdc55e59cf3e936c5b244226b728702fdf5be181ef9cdf6b4d2fc7046236a741b7b9f7a7c61133493bf6b8578f990a80e68d693fd07e7330644b13a35bb2041d7a0d83a0ff31ce8382d5e197f22826930e7d1eb99bc2ac85f7036c54288e64bbaf60b2460a58c962b028a4b9dba26a9ea65eb54d +ec0a61da39b2b0af9b1b04600ca6f3cfd5d1ccae3970f641999e2e04081c2edac59ad2e25c9bc2babea9afdaefa585b9bb094945d75c2651085e67350c7f89cbd1618129ffcd7f683a10d2de3bc76420436f23967b29d00c613e61b1950384d361170a8d6207c05fa6015cfbc78d4de334a372a94c2372232d56a14c095078ed +d95927e4875a12ab9e97d72bbe3792ffc100a88abb4e665b3de5531f6da18497956af30795a8219bf05ccab568333528059c85e1e1c43b28811838fbb19e4568a7adcbe7111a260562b7feb89e9d4e8c23cc4781004558618caf3984cf6b1c0f0fd56533713b855781ab6520e30e1c33ad34cd9fb192d302acc396f65ebfd82d +c32682f151c85652f533554a95f4c5d1063c73750be7adc84cf437ab072fb835f42fa4cfb3777827d22ef6ad96d3e5ad2a3022464a9be7d861b0f0815e3f678274bf86c4ec012082b46a49500f92cb7db69df1da1b9fcd2cf4d47ea28d6581adefa9aac478781f74ac7afa1ba709baada5d0a55b84446b849331bd450b9143ad +d639fb704008e9fe6288f4a3c1b4e4bb255af723f5a222eb6b17dc79eeb882254c07634d9995fbb231e6713863185102194217c72011a1aef91db92d101dbd0780f23f8e281156168552e2fd7910c5d31510ae75564709c6ef3ca59f0af02de59d7b6b413b31fb17c41d9e948ccd2d4b1d0ea9ed58cd2cb64eea4ccdfa7a591d +ff34ca1e9b2c4807f0c9d8940074d34ec8902310de7223053d8902f3643800ac916eb801f405c6ca4c3596e3545d0f6b388c4229b19638b82057826bdbeb1c65c7557848ad894b30e336eb812a0ffbf33fbeb42ed744c7199092d45178444093d12343937e3c356bc1eed086a2a6097bc99b1a18e5d3386e6410d451c52ac9d5 +fa3cedabf5c5865d9609117f32c1195a35f1f66144f2d8b7bc45f93d03718b9feb4b3bb324420c274de0adf7538d003d689de88f19e710e0774f6c9d02bf1e3f4ac646c1f2b05a2144f5bfb6bff0b9421338e01911c39d58593a13d5f09488dc0517cc49a94cf0e7efe39069ae5ea553efad66f049a3a1484bd6551a98a2c4e5 +c2a7edbcaa3c4b5539b1667c612d3aa11696347b7574722a520a30336d75f3b51cfa7bd8bcc94f393beb73cc34c27d7619d851f2b8c1738c87e7d502c2dc1fd979e66ee2561a92281f4d100a4a3a89179cde82303385dcc4c9f048f399237545c526e03093a55fd90a35b433ff86c635041674490876a0ee3407e13a4432473d +f46990850241249bbb7cdd181bf26bfed6858152d0a2f3a07e06015d3741fe9079527c881465364f8dbdc7ea9df72e3bc86b1534a1b9a9e9b0db457753cf70ea06a3440bd1459554e247bbe119e789d2e0f4857aff24f410a7194521dac382a81ce89040601cda6ceb76b96267ea9063777b79b32712e1a1ff0f8683745a576d +d585d7c8a09e54f83b1a6ea405693006b875d985f01d9ada9d414a44aed16c2759609706a07e926d3c903ff6abaebbe89855f4784de2975096f249508d4a3c2fe55f85a5d662ee4a48b2c21e9f862056bc6b0a802c5130ac4fedfef94bf402988fa29bf125cb933ae316dfd157bb1b4d56c12ac3f5ce2839e41cd8a80c3db045 +c97bc84c797cc5ce3c2be229b0746bc45fab70f26b59cdd7cf7a5a0b96cb7c1e5e6b97d743ff7acbe33f2356a65f5a885d7b0364474e2c72bc9d21099282bec72d23ff3812c2dab68b7687c8b4a49fc6c714dc935f69cb3444430ae719cb3aa72bc80eefddabded582da7098252dd8baf3b60bc3133c2dcb99e58b6ce9ae77cd +ea3d3fbacc5209a15b9321aea7b43727165f34b45fdc06169ca953dedb9b56a3f71fcee48217c54cabb6813e36957ca4453019496ffd665109ce2096dbb6ef049d7c47bcbf461532069522caf053940df12971d7748caed05e9e654110fb97d199683769a8e2dc7666b9bed501c9bc93b901334fccde993d646f9f2ebaa0b435 +ea860c75b83bb20a0ac27531c1ebfadba1157f1f398d39c0c1bb0d6ab81ef2889a400e89341712ae52a4e0e155dd2e2db41863330b02bf198793872565b515217b33909de3f12de190cddcab5ba8ee70af4f29c86273a62efe6fff135da9240667bf7d2ad03ec6bfa0e691967d56da9bd3c57ff9790ca2e905c86fa142623cbd +eaf4e7490624a6174c26558a38fb59f80fd1e0998f4a6ab68a2b02ff3a81905576bc378f6d3d413926888c4067959b51f0725f854fbad6213f7c99416cc413dfd90c814e11fdcf373864a2197bbffa3d1f30a79f008b90bb566a001943d37dc135eef8d10448218e72e2d95a7c02ae89690a6b1970b7f6edbd47419888210aad +c67db1f755259c3e502629df50c9e52e55a8911d237797c614b8e63908e5d3c1f13f9fbef4a6f54b56d8db3ee99076bb01a34225c31e316ab65de36beb85753384bc8979f19e54692d4312e0a1ea82abfdf1fde7963f05d811600311866becc994a1909603d29d90c6bee021b06952c1ea7b99d3ac4bfd0023bc88698651b415 +ed2741be0f80765dcbaaa2df41b383b3a66241743a2828fb34e2f941ff6c507d6a7dbe8706cb1208afba66b83df09562a52579c54d8889220d44856c78242722f1510181313931892ef29dad2c3e5938ce3e74ea24d3ab3a7275eac299809242a1ac425984e0520f03991342fba98292e672d99a9d0c063904428badb005eb95 +ca82695fa012d5e6f3c664b23458fd5daf95c94b4299f936f8b70b6bd79ef243b1b9f16443f5f8945fb01eb0beee10c2bcca4225e6076ae9f5576168826442d4318afcbe7be329b30f0b4becc72832534fafb6440998f31acb31258673a9d202d5dc5e1ac0e5f282c61eada2b6f592aa2393db06bcfcb8166f3439e01e62545d +fda28487fb44a1cc26896298f6ef49d609087687498713f41befbbf6ce0ddee25a61acce094feb2d291225b00be8fd93ee8ddfce42999f29b149cb00f36eaaa3d379e9802f70c3ff58858acab0ed34bb9050ceb4281832a194831bf82d860e27474fdbb2f7b66eca12307dc685bdaf34b623eafabe885c269bcd6c21a52ef0a5 +d8447ae18777fa8b39f9077b58e056dc22330314c427e6e8d13fc6a3610a2c1286246a724fb99312e7f915d891101b1d8815582165178d55bd4a2c367c24e78ab21b13a8b9cf3ff67541fe69049948b567fb2f2f2bd4e7c711693dc0b6ed7ead617a447fb5b5bc567f53be6bb0fa097eab097d91077fd6ed1f02db4ac96575bd +ef71e9bee6899aabd27cf4ab2db213d0ee49214291d073415a2663e370d01fa741495af57193e52c58ea3883b2a13b79d85c68ed4032a0fe96e5c005df2b0982892f1d6c1c425c04063122ce306bd1a194b7b5a1366c2ec673608c56299f521f0c3493e13bed9df1c462e36f3a0854ce33926d532a3c05af710f5fdb65d36cfd +e5359c379e60c97dcdafe5a336aaa0db6659cb8f806a5195c7e13211b396324e831aacbb8691146ec906a96ba3f66cca0c8ebbdab0af793b24c94081dbc19d8f1ee6625ddd16103bf49804fe41525c839f47270d6f1bb9930f46063b593d48145c1982a40cc78421277ffa9b134997b0a02c8af22a51d0c83333433b3fc36ba5 +ee72e1aa567796208b5d8e66fedd555d5755d9c353a5d1979d0bafe0201ada1abf26e9993e2d1a20fa6a1f374d8ef39f007120099df38d6481584e6c6df24d31637d5a4d5c92f8496335ae00a3c7a5b713ee5ff12d9c4dc48e02c8aff9a308592c8ed3f66c1128481059266c4f852540a1393139f7a2d8d4ba6548505af6f135 +d95444b1c9c3d55cfd17e0348d0b59c73d95a44621d30d3a6fca261a8a1a4fcca9c3e4c218b1a88c2aade830d7e74b558ca56446147f2546fc50a485fdfedc0e258378ec1364edcd99a44d4c203b01db566b20528da4c3ccf4593d388fdd27d6400fc1e05bcafb03ba5b59d0cf3edfc66c172a6d79d50c9f6efd6e32bea5b0e5 +f409c82f06463b8a59238727edc9d64a88747a76a4d8a0950533a63664e5c231c842557172601b388f3566d8ced5ee0dcd34eac2d1fa16ba38efa15734ee29e4e42ac00fcca1a9919b4c7965c7be46a72bb6a3a4539fc9fa9f9fd3732786ade282a4fd6b724c2638406b97e501abc5fef1f34b9a4117b54cbb96212cfb0d154d +ee71503445dc30a2e629e73523d04c9afb0ac5bf8c93dbdb701b6d4f95faf5f619aeca3cf32aa52d7c441043c6d08a46f20ccce3b63348fef6e5c5ea655a25364ec3cb997c57d88448b1eae83f65694df426c4b265680b8035a589f309bd1bdcd77615cf00fd98f989b2c1f68253959ff6e45e53524b49a6b81500c3b1975e7d +e8e6b89a01bb23b1a1e501e5cd91b8d7fd676e3b4c82045b2f3403893a44cd2beb9d1e72ea33ac197edf6838c445aa924e0174a83ef14074be38957f494f7aaef38cbd6b42ba7a3a3e4e570f0c9fbd0b9f0876c12f66dc7f2806341851997d623afdf8e1885126cf64e6614119fd9096da72f8bc765e87cdde95f796e03ac72d +f28d5a3c2f7f68fb0fa309881612184933794a4e09b4a914d0e15962ba28ecdd3cf032cc79ea4edc98f96a4d97f9ebe1a42f858c58db0a88470be1d9a2f15a3899bf7ad8703425ab51d37fc186578bdb269fbe91c83c99a5763e433ae85a91559c177303a06655300952f27438adf16897504c23e334f2ad0b9574714ff525ad +c36710875e7737d6163161d4aa394fc388baccf567a9a694cfff56728f5a5a84818d4b4c2a2c393cc92c4e59972980407221c75bd06253d1ea5883729fca08ed04a37e2fdae1d71c00ce3e5ec49ea516e9a43ed50dc130f9ca82a93c735fcd9b953893ad2a2f5dce201db07f5625fd0c09cf357201cdd071cdb442b99ee65635 +d9fd2b8e5a92de3cac06a9b941af8f6734c834fc586ff9664332272e5df184d6b0dadba9e1d75800bfe679a15caf98a340b68f1ad405cb1b92e1a0b5f176419e72874d162ce0966a24c15bd67d416230b07daec55774e40e130f5b59ad8cd81880a15659e30eb7c23caa315c4ddb1f7fb4d622b9b07900b28cb2c8850a9ead5d +f858fff7b6d38f73bbc3118f1a67e787abc04c759a97dd8ab894f1f4c70cb7deedc9d31f642af1deee7043deb93ffbb3cb2a50cfbc7de9345a66023578f8e168687fb8e54374cbc48b89d41a0c8ddd5136f2ba3119baf0b101d462db1f7d6adcef017b6ce12af71b3d83d36871836309ece1d2dd2316a7a4165a0c4936e4cf2d +c411fd6fae60edfaf2f378edc643faf350c7d5bd8d3b14ccc5024fdb2a720db6da39aeeecd2ce1a2abf2a1b2bde9a9e734f641bc1fbaaecf4eb979cdef4c72a3b735073ec40790e1b264519c00fcab517bb14e65721d5ed8e49af4d45cc272f98f9ddab8126322b0ed77f9dc830b33d3c0d00f7c989f000f5614f7e2b7a2dc7d +cdee1cecd0e500ef3a24913c8b4411fa864510348618d0497a27fd62406aa5e19256fc8c28a5865e2efb24b6e56280ace90eef672e2aa250355e6272218b50ff24351f52a9b6a95f3fd302da49668760566314b82dc4bd36641e52c42622fa3b99b0b6e080d2a6873c666757a809beb30675014a61ad558e02dbbbc931329735 +e43ab2cb35c4b7db2c89d68b1caa3a51e5939c423a7cb3cf907c66cb0dc17799a936cfdc74f6a1aa1ea1ea7b74640105152803015ca083207807ce0b3d27310689a15a59f5124bad92b0d7d38d81868bd8415f2ce12fd6dbdfae8345e30e2d3e481f736a9bbb0348c09c521b5a95595485e5db301d6398ff04a9dab2e17097a5 +d4b4aa3594a5a3764e984f8cf743bb10a9054ae512660fa39595e09b3fdaafdcfb25d814bf548fe219a3c932b85d1d05b6bac54704ae7b8d01b126c09463678752596df2eb1a49681d4eac1620718ffa9e1b383639f6a7e02bae1858ba20782b65b34a2873a7b20790699b219f6814edad9a70e02aa2241944fb548edcd38f7d +c424e212cd8caf79d592aba373255d335bb58dd95175bad04c404df2f49004585dec10629ffb6cdeed6a2b96f00e6a943533c05571b10d0f977f5ad6a63fdfd57822e7c9d83f7579cfcabdc57646dadeeb7e774272a2fa5739f9ee189d05ec7cae84894550e6b62cbf5364a9f125f23e6c66c4d6387179a480c8ca19a61a3405 +e35bbcf0eb6f2fd2bb055cddb9a62008bd11c72fe35f75f2f8dbd4e89549cea92ec4b559721751a938c7b491a79e7341db2f3fc9ad7210199fc1fb43a2d79964a7526a1f22130cb232fd006e85efbc33b03a437fc0806431a5e55fc3b738457c0fb9c2ed38d5a87382573b10202404e71dda4ee520568e860a2c5daeabf94d2d +fad58a9cbffe7e58a6fa508dc7fa68c3d16afb32593c74b324a96953e0a28c855cbd40e0c4edb5b255c3ccd3ebd5dfa03ff16afd76cf910cb513ffcb5103b87728d1d9f46c81f2eb908b4c35321aa78df4b4aebf26aa8ba4d7d9f4dc7817c5835a5cc1f06472467ac0c329952312829ce154002908a0312397d2001b22e55a05 +c2cc23eec815b9500fab0310f2ad42967b1d3a8a2ae30dc2e0b0756f03d15b986dbd05ae733b374e687d4c1ac71afb3062a0802016cf2f861c138aaa082570be4e3b9a3d8c3ea9d432375ce5672eda0d9264588ad178679401b1fbe8d39cc6d60866e7f7e672d96683069ffffae0fd0f7194b73552c2d37144d8da4d8f0347f5 +df830ac28a2ceb86de86c778400c44620fbca0ea597f2be44d138bab5d40d89ce5d802ae39b07186ce350ba22c05fb614af5d5e3e8796623ba2838bf8162bbfb336e159f9236ec9a8164cf7c59a59fe1309a4d740b337a4a0cc9d8a9d961d10e2a3e5d2f077a33edfe89721c2c53b238e6518d2d2104f2ccf3351bf73452ba0d +c390d2db5531bf90d8a92cbd10a58a255ef6c8d726b7c805479a5fcb0a805458da99c0bc9123aec25e4bbb483d7d2ab14c879ef3a278fe446c03235fdbc83c85779297db3ff92ddcc4c5a6f6372e924a5d24f87528f87f1f6cb0515bec718ec8b2f1dd334a48d94ba766664ef0c68b9d4edc80ab067e0b642555cee16492c735 +e9b097742abf5024caac24091c2c5890cb2ae2fd5e9c7128c961d19f4c5fad4bd763dcbf3c646ea3a1ac8d816b673af4d6a488f2e40ec0100d154d6d3fbaffc444bc000255950ed13da1308bdb6a8ee6bb3468564637c3c205f77a3ad718d7e90f02c19b6e497781d8da8053733492dad5d55db1b7b84ce21260c85dfcf4dabd +c29aeecd959ee5a8028ab5bf1543d47d90947a355022ba3c72db9304a49d740e7a3174ed0f56818a8bdbe015f7e11d05ea8b03a8e43688dbe0d7816fe9cc7311c546ae34c29d304fd5442fe8586a89b78994d121b0be22077879c6781c44f124a3be54e2a6fd72482777d63033536b8c01cb069c7ae01f16f3fc76e568cc5bd5 +dcba67abd50cec07b4e84ff593f1e89b7e30b28d1ba0704549a0f1de7e23327a8c997079464f0cc81feb46e3465ec010632b9ad0a085a1bd2a5e8c9778934241c68bfca1c96e476548296c787372e8057e7ecdab83493619ad567019616a53c19818228420509728032d8618a8f35fbd901fbd0a4c294a2ead8968ead89a1195 +d33920afbba20c8705f47c812f31cb7b36c7dd5e88a00ac9f555a6f0dac9026c4dae15c219f2760ccd38c0127671e5a33f00211c81343835d8cd856c93e9ff3a6c5c61730a7f388f6aef7779350ba98316f795b684c60950a18e52855288d2e7575315a5ed17bd837e176b0020e0d47d9c50b668a73c893289d66f79bcaffc5d +dd4b3b5cd4c6c13f1d180f2ced8fd94eed6df632f0d6dc0c3a86b08c971daacdb45d96b2df9f1911c8486a06fdd64535d3fe012c63bfeaee833e2b6a220e00860fe9360c9813f59c13dc1037addc9eda6784ab2435400342364fdbce37baeffbf4fc4d876eb10d8f48a6604d85609eb172939f57584087ca911511371ebb43ed +f445fcfdaafc3b04890f3a9d893bf0bab976d6ea0ab6353496c92a35812577970a6f0169a2f2c88dda1f8db9e5a48f1c8bd97dd8dc271321d4a512fbc1c22f8d433ff4acf7f07913fe3dd61c8085f7fbf5e38c73cc0bcf7143c7e7940bf891e48bb25e85e4ed044e299fc73f8ae5cbb928c350db712af57e3206075c31d64dad +ddd27681c4e17b61668296dc2ec218f7745b0d22d93b15bfd665cd1e2341685d1e46c2aa7272a81d2d6e492c879ae6d5bcfd27bfec68f86991448db24eb323b1a9972403d716074cc0e6381f58a9c414b67b53b86a0d4e572d117e31e831eed78674b63d8b53d8aa689eb629cff0625c90053e7de0554f26325affcff31c2295 +ed16929a515a5c173259fc7119e5b958b26a2c061788e7602bdb74df932bf38eb4f2864e8165d404f4bff6d1f4a4fbd0706b840a71f36fbb97d7da21ea11cd258914e1ad3c91f480154e2dd0588a52d30bad58a63da178c980dac5dcfbf07930cb7032bc86d352634c042978640ba314ea678097b6ff6bcaa5e76b5714dc6c35 +f63fb957a8f763458e2591089ba4c4c729bf996617e81bcd911297228a6b2f4d5f60f605ff5efee7513ceadfbfd2e6548850689fe19dcd9967787beabf44400c90d6ffa22874f1d14fc0132f56be66b3d0c62acec6243fc34a876f16b8f5f71564a58bcdffe30455573ca1aefd3b9fc62886d1ab6456de66100bac61d0c24e35 +fcc63fa090af719d1a27b6345425d6685aec8b3b8dbddc2b02e274205c91c965aa6e17761e448480316712a25b33a36ac31d959492c29268ea3f10547f28e90cc8cb597016f1f126148398e21920ca71960481fce3f6ee4c8064bdba7ef49af62cf3cff00cc9ad475ab84619244475378066ef891155390ca589b75efa544385 +e6da32d30e3934c02eb23cb417025a288538ccce07e0d9bce4de00d9492720692be2a48e82ac9a25144c0ec9b4b141c77648c35884316b311080fb0796cab75cfb93933a2aa9310423a7b2ef94860783aa27f3061246a8c6bb2e2b40b69fac7fb617d24d1cd094abb1a1c7bb46460598e66bb539aa0fa499ebadf05e151716d5 +f7379a553d60d6e51a8f37f2d910d9dd5256655efdc891a708c6cb52033ea4689732905a5bb46cc4d3183e74708578e39b0c8630cc0fdf50e5355aafdf07b478f19584951c251fa72b93be1a3a3b744fc3c911b0847b089eae9dd73ff7050a0fefd4508f549c37790de86c87ef74bb53a29800fc64fbe7d365e7e708dea86a45 +e933766d7ca9db5dc66ff2ddc7f44542daf51232fb9ab413b1d31aaac02dbca7113c4ca874ebf599363ca99572e19a3fa57761cdcc600ac6d2f0e8dd8471ee7fd1b5584f201a480ded805af6dbf651f1bf1f06757a7fafec243c30ef5eb3d94958ad6d6b9687689cd578e383c3f6d8e3fc8981e83a91dbfdb2c4e5292382a42d +ed0f5d59bd470fe130096bd507795c809d261787ce73c77de9480d57985759d4909b19b5e248bfe6c75655beffff3a28aae22c017ca4c20ca4dbc81288542fc5b6acb4df489e7a3895749d21f2a13c0cc7fef538dc806576dbf816b529c3800eed0dca00e3e4cc022dc498f8095c7f62573daa7c00b6a049fe550b1d74a78e05 +d647d958769ca155b5f98f62fb98b28c48bc228f641b7f0a05ffa07c35a7413e29f35fb675b6e2580bc1e37f95eab82aa64317fdaa73924ce0970434002d70d1a08f84099d1b151df7623ca07bfe27b964f2ef9796d1e48c172c812e7c039484c16df56e289fa32f8069ea99a209b23efc383bade2006ae60521ef2f9e7c28b5 +c49923c0119e93216c2684dbbd9385f2c59c9a7e6330a44479a62da7db661ed2a36bc6c22fe5a0d21fca0f8e0f2d91a4c2a5c42f917f8e8b8d3e33ce97342b2081bc65eee931d2659c5d7b997e74bbd005b91d1353a3a4d05bfe6d680053092f27832c22776178bfacae0117fe6d80f42d5385c38e0fdef4e3b8cb33016c4fa5 +e588adcbdf57002875cc72def03f19e6c24059914c3a6055a7d67374a8ba3d339ffa72067c69343ad5dd11001e98054928cc39ce04b543ea737b1735682cb87a9b82963080a79946dbbaffac5271f905b7236c6215a456cdc3c5c40fb8bad8ce33e67c1113cbd9f889fc785888f50bbec30982fbcadbde934d72d44990b12f9d +d36621350931f1a0d1a8b1d215c643ba1b6f63a879789a7f5ee272f570e5cf0ec06c0700fa2521e5d5799e091967c54622534dde1c7dce3fe659569320fcbca304499286c3798bae6b70f64b72e18cb4ef5884de2103661257f61d327667f7f63ef4d63482e9ade53bfaf117662b65e46db4a2f3eb0829ee5363f53d2ff38695 +ce5e2ed6e0b9a7e749f48d031452c9c49b9760dd74e9cbb87a3e6d208cdc657dae6caa5218e0a1e6118080e8d2201d541e9cfd9fd518ea91c88cf18d27e484f0ffd0d9d04713e6a6325df1912ed4ef490d1f8223b884277a45c18d9157f8224f9bcb68d4458b79cf80911990b08b40962f84b5481d08b834ec2b80b81d93f2d5 +dd6ab77fdad44b520ad7796facc9fc57e52a6adf9dd7acaf00834418d871f64ac6f2a44d44578bf2e4c488b57becb233fc3d1367b21227c42f0cfe256ddac7712556a3b15c9185f60093d07835e98d03c82a3b9ad8ed338a2b54d49a6d5320454be8aa1c444cefca7eeff7c3854bef4685e8e56a7a537b7a3cd088953301652d +def3aadd465c6faffd363a9c360eacce5859476883c48dde4c8faac7dfd58a1ebfe2316b8771dd04553b78a11d2b4617797fddb541f1e440b61ac9d1097fe55f27be65736448206d0349d2cd95d8856bcc9b770472e8e8db83d3ef3b38b40b83ffb2eea7c0f7596911fa79b320e2add7c8336a71dc1a75e29d415ba0bcf24815 +ec4150c5b4a666343f6b2a831f368a7e0d0cc7f64473e521f539eece8ce65065fc2b9c1484dc692c7f4999e766e277f6fc6d1beed8c64d807cdc10a4ca6db1ac8141406768fb4cbac076d77d9d0555d95fb60f878996a366b099e59952657823304a566b4009952c37389482e8979a5cb0a6f1cec39bc88442c91bd918187ef5 +d1b84e4ff948e7748adbfab581176b13a98fdfaa6097ca3615da0fddfe3a13134cb4737e440dd211976f4753cb17359ed5a4803e8fe8f485103fb47b82c923e4a923df949aa1339ec6e925bedf405f05863e2628bf3a2f4a301426e97e52cd17d5de113ed7b73425b5187a2442b141a15804a0e782528dbc545dcf90c1bb5edd +ed83c3fe0acb80253190a59051d684d71c8eb39fdc8e8beb75e0a166438e206f9e060dd6de4aa9569a16fd43a6337e34f8b038f19906c4670410262fb3cbcfdda0447e4cbb1c69bc2ede8fe60b0bf40ede40e6e380a5a16e8633adb18f3dc12b2e7c74e6dc088a23cef51792587dcb1ae90474f82f37c653d2abfdbf6350229d +f25cb0386c6f7d01a3aba79f9400e2e621a8174de2ab436650a9354c2ad0261db1ba8ebe694bc45f4ded8184ed410c53c796286de4e0427a026140f2a183891ac0422c1247e01a793f94ec112dd23b5c460eb72356b060b3cc701c8db7a9d5b7cd311048f3f7694a92da2fd07a000e56277ae75cc9a15e71a81224592e959da5 +ee03c438fc7f8908610430d2cd034cc01a6762d94eb940b5ebe7c227091cc64878d41821ac3e42af3149e45b4543e34660716c2cb49af0b6c03be8ff178a242e3b5daf281aa04e2ddacca16d65a2b065a7c61dbe4bd8160d0a4a005679a4b5f0cd3068e1e092304d2a585c4fb2176bae941fb11374d6bc0272a8513865cd457d +ef6568752b0fc3eafcdcf2f8ac2b1a9f774e53a131eeb37d49559cc60d9caa99d8bc777b9ff4e84702eb83165fa03627d4eb09d286ee42565e87da20bca80643fd26258d3a060206faa9fcc3508ec1537f6578209ac359f544529fd87c471635dd9db44d3ecb9f5d82906c171673ac1d8ab5fb3f1bfd7b3cc2c06cae9b7f6685 +ccec545b7d7f1ea51d8774cdf81a6e8794bc3cd87fbe1a8f8251d9741410b37bc39f6af6ba9efda0cd15243eed15cf11f0b4465e02c38acff963f71fa7220c9d52f64c2fbb8fe044bc6cbaa0895ab8bdedc65a89e0441524cbe28ef9ada6c63d0431583506354b9c2a5176779538bf229b74c8f7feaf5eebdf0825835f33aa8d +e4459658218601a13cc808dc9581fe25f3609976c1f3430c813a2b5dafc902d5440809e75b1e01568bda0d15f8de12f368d7c3a32bac5bd3781f08b0beaba41d4f0541576658fee91042aecf2a450aa0a1612010690c291537a9430de782eb56207da17b54c16e1096eb1f9799ec98910c8557a65d4dff8da6201949a9238715 +f62b559ef3edfe69b2ec36c3af8ec3c36a326409f7d8665ab2a9c26cc28c9daf5350e8164e06966a68e33ceb550504945fa967c6af820a49c4abf6076f94c44340972f524fca03156376720f5bd7a795a4f1d022f1d47cf571d2d4d75b677791bb31bde0a3613d289d4dd1a3da76b9450b56efebc996ff1a59eb3d42da77285d +f397abbfca0f6d6ab5c132ebd18a30c83514b2576c2876e20c6cceb43ae8654f786d9ee5d8ad7b968ff447274d8c082425a477836b59bd27db3920ffd45ce0aec781852cb2a4cba54aa57092b407e2558f27a8a4a43551ab0641b4446fb54845c883dcbefed352cc0f7c557f1597f3e360e8b5d27da51bfd38e7454d509bb19d +fa306da03ce1f59bdcf895d340061798b11295da3078bab00636ae87f758873c32fafa2579563cf2d313c0889664d66e10957e14b0d90af7915cf61151043c1ece2190769ec61bfad26fce27a86094606f734586ed7590500f21ef02db9b19ed5b7b9598396d10135febca8c3e0be26ba0f02e2e54bb6b1ce0a7e424c4cd0e15 +de301f25036944a4f6a8fe11d1d6781cc5e054a09d449a8b1443ee4ad50e9ac8e68e244dd8f8ebc9de0f6e3fd2bd5de7099e17c452631f1962ba400eeb79db5d36dde3f91c7d5780b2ea421b31c2ed57f11cf08c29bfdfe7350e3a87ef477e0d178cec6faa7a4c07519f5fb21661a9ba681906dbc2c2658d9d25bd42e436118d +fc1556bfd9852204f19ac3da8d59b2e889d4232f00afae9dd890b47882c130eeced040fb696842e0635ed0530cddb515054dc514765e9b555f9857cf9bb13cbf5e2fcbc6630a0989c4b2fd565d81e7745851bbffa65ae8ba42591a17c3a44e1d8d60cd549558a4e17bf5343613a2aa4bcb0d086588579865e14b696ef5d03dc5 +dc60b41e021104e87cf43ba8e75dd012c1121bd5c3e682b8efa1f1103a12e27d89c520ff95809fe0bd206ef70775c78a2364d01d91f4afc7c8a8d02a671e14e2cb1df0b14852e7c5134a31b4380788f8b0f37423ed853e3e1c1938ad3f7cec40cfabaea1eef28c8d2b01f6bab2a1d584084897801bd249147e6e0c725eec7ee5 +ceee4f09a49f1de80b4d0dcd33eb56e8ea3d85c1a2e5e856323c03253fb91e2b4347857a86aa1d8484a501238f10b7aaf4adc3c39e42dd04495590c814f6af2c6a07155b1a793cdab9067b7e709bc3d748b10a419fe3f530111444a7b8cf3dba2880ff1bf81a607ea367b5a81b52e16b41080ea36b18a0d3590566af84989945 +f6bc6ad26d71863b9c771160344c9aa98f35be8193d2ee0b47d342a9ded78aaa6c654df6aff01aba55fe283df748f12afb40f6556d4370a9a987b98e2b330c9576f76821afb9103f249af8d6544c76b94d0e19b710465b5a8732ebabce87869a115f7cd88e7c01dbbb79c9cc04d266b9c578a645616e8ae3d8fbae1f80667d15 +f295e2280717c5fccba279523dfac2149274ccbf9e7a6ef99e17444c8d4c89c668d0306cf955ed951b7f60b1ed3470643fedfedc01cba6d66c2f32ab122a83314e84e21e1bb1c1af7ba69fc64faff940e0e2a797aa758f8b2b3bf2e882dffbe2bbe2da6680e622d05ff03a4ef09e88cbc3595179f68779062eb7fffc336ef025 +d607837c88f51a8449b0d1ded1caa08df6bbefad69c55cfbdef7c7f60fc0b2b3851019ef5be975f84238082fc0b1a1b67d595a95ac5e412904cba879d5c67a99479d5132189c9cd21c1b3270f293d1be5b3ae383e4ad9c3e72acfcf357631facbab5adec457b11bf6eac2cd2995a28ae5768ed2dad963bbf28b5794eb082d3b5 +f18ec69d1572c4686076d8f7f96629bc04ac315b0ce5fddf4758aace119fb5562e39535b7f20702ca99e686e229bf951e7bbb6d5cd6919f94235e873c3a47ba36829070b5f34e4153d0649f7c4aa7acf14720036b80362ae1b1063222903e4c62f434ec16c852c926268e739ba62238994dcc6f95c440d4826dcae5a5660168d +d2e36d5dc038ebddb59318b497fa416f76d2ca62fb99f8d9568737430a5946069ef251c20d2311598e9d1055f8ba15b018c28ec131a683d4d26056e67a46216dc529ab933ceae21f762538db6ca6db0473ed9dcc8c1ac880ada42fcf52938bd0cf3c528587a5ed61f4478d185c4b0d4edba24d222b207016be16e43105fc96f5 +e4be11d1cb3926427f6b2d26c43ae756ab16ea299a5c6b4c38eaaef00e0642e2c4cacce642f3be830a8fa93f5bee03a7d738b0a4e4181e0778788cc81b6989549b16ba116cd931446f39be17de2d99896832ff4fa1d4e9d70a15c4a4e7afa4b319cf51fda2ba57572b5656a6333597df083aa8278addc3024c7a21690e7fe305 diff --git a/crypto/sha1/sha1.go b/crypto/sha1/sha1.go index e9b711f971e..6999a332d44 100644 --- a/crypto/sha1/sha1.go +++ b/crypto/sha1/sha1.go @@ -9,12 +9,13 @@ package sha1 import ( + "crypto" "errors" "hash" + "internal/byteorder" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140only" ) func init() { @@ -55,14 +56,14 @@ func (d *digest) MarshalBinary() ([]byte, error) { func (d *digest) AppendBinary(b []byte) ([]byte, error) { b = append(b, magic...) - b = byteorder.BeAppendUint32(b, d.h[0]) - b = byteorder.BeAppendUint32(b, d.h[1]) - b = byteorder.BeAppendUint32(b, d.h[2]) - b = byteorder.BeAppendUint32(b, d.h[3]) - b = byteorder.BeAppendUint32(b, d.h[4]) + b = byteorder.BEAppendUint32(b, d.h[0]) + b = byteorder.BEAppendUint32(b, d.h[1]) + b = byteorder.BEAppendUint32(b, d.h[2]) + b = byteorder.BEAppendUint32(b, d.h[3]) + b = byteorder.BEAppendUint32(b, d.h[4]) b = append(b, d.x[:d.nx]...) b = append(b, make([]byte, len(d.x)-d.nx)...) - b = byteorder.BeAppendUint64(b, d.len) + b = byteorder.BEAppendUint64(b, d.len) return b, nil } @@ -86,11 +87,11 @@ func (d *digest) UnmarshalBinary(b []byte) error { } func consumeUint64(b []byte) ([]byte, uint64) { - return b[8:], byteorder.BeUint64(b) + return b[8:], byteorder.BEUint64(b) } func consumeUint32(b []byte) ([]byte, uint32) { - return b[4:], byteorder.BeUint32(b) + return b[4:], byteorder.BEUint32(b) } func (d *digest) Reset() { @@ -103,7 +104,7 @@ func (d *digest) Reset() { d.len = 0 } -// New512_224 returns a new [hash.Hash] computing the SHA1 checksum. The Hash +// New returns a new [hash.Hash] computing the SHA1 checksum. The Hash // also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal // state of the hash. @@ -121,6 +122,9 @@ func (d *digest) Size() int { return Size } func (d *digest) BlockSize() int { return BlockSize } func (d *digest) Write(p []byte) (nn int, err error) { + if fips140only.Enabled { + return 0, errors.New("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") + } boring.Unreachable() nn = len(p) d.len += uint64(nn) @@ -153,6 +157,10 @@ func (d *digest) Sum(in []byte) []byte { } func (d *digest) checkSum() [Size]byte { + if fips140only.Enabled { + panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") + } + len := d.len // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. var tmp [64 + 8]byte // padding + length buffer @@ -167,7 +175,7 @@ func (d *digest) checkSum() [Size]byte { // Length in bits. len <<= 3 padlen := tmp[:t+8] - byteorder.BePutUint64(padlen[t:], len) + byteorder.BEPutUint64(padlen[t:], len) d.Write(padlen) if d.nx != 0 { @@ -176,11 +184,11 @@ func (d *digest) checkSum() [Size]byte { var digest [Size]byte - byteorder.BePutUint32(digest[0:], d.h[0]) - byteorder.BePutUint32(digest[4:], d.h[1]) - byteorder.BePutUint32(digest[8:], d.h[2]) - byteorder.BePutUint32(digest[12:], d.h[3]) - byteorder.BePutUint32(digest[16:], d.h[4]) + byteorder.BEPutUint32(digest[0:], d.h[0]) + byteorder.BEPutUint32(digest[4:], d.h[1]) + byteorder.BEPutUint32(digest[8:], d.h[2]) + byteorder.BEPutUint32(digest[12:], d.h[3]) + byteorder.BEPutUint32(digest[16:], d.h[4]) return digest } @@ -193,6 +201,10 @@ func (d *digest) ConstantTimeSum(in []byte) []byte { } func (d *digest) constSum() [Size]byte { + if fips140only.Enabled { + panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") + } + var length [8]byte l := d.len << 3 for i := uint(0); i < 8; i++ { @@ -258,6 +270,9 @@ func Sum(data []byte) [Size]byte { if boring.Enabled { return boring.SHA1(data) } + if fips140only.Enabled { + panic("crypto/sha1: use of SHA-1 is not allowed in FIPS 140-only mode") + } var d digest d.Reset() d.Write(data) diff --git a/crypto/sha1/sha1_test.go b/crypto/sha1/sha1_test.go index 5e84db2884a..3bc7415b9f8 100644 --- a/crypto/sha1/sha1_test.go +++ b/crypto/sha1/sha1_test.go @@ -233,9 +233,7 @@ func TestLargeHashes(t *testing.T) { } func TestAllocations(t *testing.T) { - if boring.Enabled { - t.Skip("BoringCrypto doesn't allocate the same way as stdlib") - } + cryptotest.SkipTestAllocations(t) in := []byte("hello, world!") out := make([]byte, 0, Size) h := New() diff --git a/crypto/sha1/sha1block_amd64.go b/crypto/sha1/sha1block_amd64.go index f9bd4b8cf12..d1ca5bd7ff9 100644 --- a/crypto/sha1/sha1block_amd64.go +++ b/crypto/sha1/sha1block_amd64.go @@ -14,7 +14,7 @@ func blockAVX2(dig *digest, p []byte) //go:noescape func blockAMD64(dig *digest, p []byte) -var useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI1 && cpu.X86.HasBMI2 +var useAVX2 = cpu.X86.HasAVX && cpu.X86.HasAVX2 && cpu.X86.HasBMI1 && cpu.X86.HasBMI2 func block(dig *digest, p []byte) { if useAVX2 && len(p) >= 256 { diff --git a/crypto/sha256/_asm/go.mod b/crypto/sha256/_asm/go.mod deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/sha256/_asm/go.sum b/crypto/sha256/_asm/go.sum deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/sha256/fallback_test.go b/crypto/sha256/fallback_test.go deleted file mode 100644 index ceef3cc9222..00000000000 --- a/crypto/sha256/fallback_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build s390x && !purego - -package sha256 - -import ( - "fmt" - "io" - "testing" -) - -// Tests the fallback code path in case the optimized asm -// implementation cannot be used. -// See also TestBlockGeneric. -func TestGenericPath(t *testing.T) { - if useAsm == false { - t.Skipf("assembly implementation unavailable") - } - useAsm = false - defer func() { useAsm = true }() - c := New() - in := "ΑΒΓΔΕϜΖΗΘΙΚΛΜΝΞΟΠϺϘΡΣΤΥΦΧΨΩ" - gold := "e93d84ec2b22383123be9f713697fb25" + - "338c86e2f7d8d1ddc2d89d332dd9d76c" - if _, err := io.WriteString(c, in); err != nil { - t.Fatalf("could not write to c: %v", err) - } - out := fmt.Sprintf("%x", c.Sum(nil)) - if out != gold { - t.Fatalf("mismatch: got %s, wanted %s", out, gold) - } -} diff --git a/crypto/sha256/sha256.go b/crypto/sha256/sha256.go index 037bfddd61f..70a8757ded4 100644 --- a/crypto/sha256/sha256.go +++ b/crypto/sha256/sha256.go @@ -7,12 +7,11 @@ package sha256 import ( - "errors" + "crypto" "hash" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha256" ) func init() { @@ -29,119 +28,6 @@ const Size224 = 28 // The blocksize of SHA256 and SHA224 in bytes. const BlockSize = 64 -const ( - chunk = 64 - init0 = 0x6A09E667 - init1 = 0xBB67AE85 - init2 = 0x3C6EF372 - init3 = 0xA54FF53A - init4 = 0x510E527F - init5 = 0x9B05688C - init6 = 0x1F83D9AB - init7 = 0x5BE0CD19 - init0_224 = 0xC1059ED8 - init1_224 = 0x367CD507 - init2_224 = 0x3070DD17 - init3_224 = 0xF70E5939 - init4_224 = 0xFFC00B31 - init5_224 = 0x68581511 - init6_224 = 0x64F98FA7 - init7_224 = 0xBEFA4FA4 -) - -// digest represents the partial evaluation of a checksum. -type digest struct { - h [8]uint32 - x [chunk]byte - nx int - len uint64 - is224 bool // mark if this digest is SHA-224 -} - -const ( - magic224 = "sha\x02" - magic256 = "sha\x03" - marshaledSize = len(magic256) + 8*4 + chunk + 8 -) - -func (d *digest) MarshalBinary() ([]byte, error) { - return d.AppendBinary(make([]byte, 0, marshaledSize)) -} - -func (d *digest) AppendBinary(b []byte) ([]byte, error) { - if d.is224 { - b = append(b, magic224...) - } else { - b = append(b, magic256...) - } - b = byteorder.BeAppendUint32(b, d.h[0]) - b = byteorder.BeAppendUint32(b, d.h[1]) - b = byteorder.BeAppendUint32(b, d.h[2]) - b = byteorder.BeAppendUint32(b, d.h[3]) - b = byteorder.BeAppendUint32(b, d.h[4]) - b = byteorder.BeAppendUint32(b, d.h[5]) - b = byteorder.BeAppendUint32(b, d.h[6]) - b = byteorder.BeAppendUint32(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = append(b, make([]byte, len(d.x)-d.nx)...) - b = byteorder.BeAppendUint64(b, d.len) - return b, nil -} - -func (d *digest) UnmarshalBinary(b []byte) error { - if len(b) < len(magic224) || (d.is224 && string(b[:len(magic224)]) != magic224) || (!d.is224 && string(b[:len(magic256)]) != magic256) { - return errors.New("crypto/sha256: invalid hash state identifier") - } - if len(b) != marshaledSize { - return errors.New("crypto/sha256: invalid hash state size") - } - b = b[len(magic224):] - b, d.h[0] = consumeUint32(b) - b, d.h[1] = consumeUint32(b) - b, d.h[2] = consumeUint32(b) - b, d.h[3] = consumeUint32(b) - b, d.h[4] = consumeUint32(b) - b, d.h[5] = consumeUint32(b) - b, d.h[6] = consumeUint32(b) - b, d.h[7] = consumeUint32(b) - b = b[copy(d.x[:], b):] - b, d.len = consumeUint64(b) - d.nx = int(d.len % chunk) - return nil -} - -func consumeUint64(b []byte) ([]byte, uint64) { - return b[8:], byteorder.BeUint64(b) -} - -func consumeUint32(b []byte) ([]byte, uint32) { - return b[4:], byteorder.BeUint32(b) -} - -func (d *digest) Reset() { - if !d.is224 { - d.h[0] = init0 - d.h[1] = init1 - d.h[2] = init2 - d.h[3] = init3 - d.h[4] = init4 - d.h[5] = init5 - d.h[6] = init6 - d.h[7] = init7 - } else { - d.h[0] = init0_224 - d.h[1] = init1_224 - d.h[2] = init2_224 - d.h[3] = init3_224 - d.h[4] = init4_224 - d.h[5] = init5_224 - d.h[6] = init6_224 - d.h[7] = init7_224 - } - d.nx = 0 - d.len = 0 -} - // New returns a new [hash.Hash] computing the SHA256 checksum. The Hash // also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal @@ -150,9 +36,7 @@ func New() hash.Hash { if boring.Enabled { return boring.NewSHA256() } - d := new(digest) - d.Reset() - return d + return sha256.New() } // New224 returns a new [hash.Hash] computing the SHA224 checksum. The Hash @@ -163,92 +47,7 @@ func New224() hash.Hash { if boring.Enabled { return boring.NewSHA224() } - d := new(digest) - d.is224 = true - d.Reset() - return d -} - -func (d *digest) Size() int { - if !d.is224 { - return Size - } - return Size224 -} - -func (d *digest) BlockSize() int { return BlockSize } - -func (d *digest) Write(p []byte) (nn int, err error) { - boring.Unreachable() - nn = len(p) - d.len += uint64(nn) - if d.nx > 0 { - n := copy(d.x[d.nx:], p) - d.nx += n - if d.nx == chunk { - block(d, d.x[:]) - d.nx = 0 - } - p = p[n:] - } - if len(p) >= chunk { - n := len(p) &^ (chunk - 1) - block(d, p[:n]) - p = p[n:] - } - if len(p) > 0 { - d.nx = copy(d.x[:], p) - } - return -} - -func (d *digest) Sum(in []byte) []byte { - boring.Unreachable() - // Make a copy of d so that caller can keep writing and summing. - d0 := *d - hash := d0.checkSum() - if d0.is224 { - return append(in, hash[:Size224]...) - } - return append(in, hash[:]...) -} - -func (d *digest) checkSum() [Size]byte { - len := d.len - // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. - var tmp [64 + 8]byte // padding + length buffer - tmp[0] = 0x80 - var t uint64 - if len%64 < 56 { - t = 56 - len%64 - } else { - t = 64 + 56 - len%64 - } - - // Length in bits. - len <<= 3 - padlen := tmp[:t+8] - byteorder.BePutUint64(padlen[t+0:], len) - d.Write(padlen) - - if d.nx != 0 { - panic("d.nx != 0") - } - - var digest [Size]byte - - byteorder.BePutUint32(digest[0:], d.h[0]) - byteorder.BePutUint32(digest[4:], d.h[1]) - byteorder.BePutUint32(digest[8:], d.h[2]) - byteorder.BePutUint32(digest[12:], d.h[3]) - byteorder.BePutUint32(digest[16:], d.h[4]) - byteorder.BePutUint32(digest[20:], d.h[5]) - byteorder.BePutUint32(digest[24:], d.h[6]) - if !d.is224 { - byteorder.BePutUint32(digest[28:], d.h[7]) - } - - return digest + return sha256.New224() } // Sum256 returns the SHA256 checksum of the data. @@ -256,10 +55,11 @@ func Sum256(data []byte) [Size]byte { if boring.Enabled { return boring.SHA256(data) } - var d digest - d.Reset() - d.Write(data) - return d.checkSum() + h := New() + h.Write(data) + var sum [Size]byte + h.Sum(sum[:0]) + return sum } // Sum224 returns the SHA224 checksum of the data. @@ -267,11 +67,9 @@ func Sum224(data []byte) [Size224]byte { if boring.Enabled { return boring.SHA224(data) } - var d digest - d.is224 = true - d.Reset() - d.Write(data) - sum := d.checkSum() - ap := (*[Size224]byte)(sum[:]) - return *ap + h := New224() + h.Write(data) + var sum [Size224]byte + h.Sum(sum[:0]) + return sum } diff --git a/crypto/sha256/sha256_test.go b/crypto/sha256/sha256_test.go index d510b83d4cf..2dbc3b241d2 100644 --- a/crypto/sha256/sha256_test.go +++ b/crypto/sha256/sha256_test.go @@ -8,16 +8,13 @@ package sha256 import ( "bytes" + "encoding" "fmt" "hash" "io" "testing" - "crypto/rand" - - "github.com/runZeroInc/excrypto/crypto/internal/boring" "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" - "github.com/runZeroInc/excrypto/encoding" ) type sha256Test struct { @@ -95,8 +92,11 @@ var golden224 = []sha256Test{ } func TestGolden(t *testing.T) { - for i := 0; i < len(golden); i++ { - g := golden[i] + cryptotest.TestAllImplementations(t, "sha256", testGolden) +} + +func testGolden(t *testing.T) { + for _, g := range golden { s := fmt.Sprintf("%x", Sum256([]byte(g.in))) if s != g.out { t.Fatalf("Sum256 function: sha256(%s) = %s want %s", g.in, s, g.out) @@ -117,8 +117,7 @@ func TestGolden(t *testing.T) { c.Reset() } } - for i := 0; i < len(golden224); i++ { - g := golden224[i] + for _, g := range golden224 { s := fmt.Sprintf("%x", Sum224([]byte(g.in))) if s != g.out { t.Fatalf("Sum224 function: sha224(%s) = %s want %s", g.in, s, g.out) @@ -142,6 +141,10 @@ func TestGolden(t *testing.T) { } func TestGoldenMarshal(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha256", testGoldenMarshal) +} + +func testGoldenMarshal(t *testing.T) { tests := []struct { name string newHash func() hash.Hash @@ -230,21 +233,6 @@ func TestBlockSize(t *testing.T) { } } -// Tests that blockGeneric (pure Go) and block (in assembly for some architectures) match. -func TestBlockGeneric(t *testing.T) { - if boring.Enabled { - t.Skip("BoringCrypto doesn't expose digest") - } - gen, asm := New().(*digest), New().(*digest) - buf := make([]byte, BlockSize*20) // arbitrary factor - rand.Read(buf) - blockGeneric(gen, buf) - block(asm, buf) - if *gen != *asm { - t.Error("block and blockGeneric resulted in different states") - } -} - // Tests for unmarshaling hashes that have hashed a large amount of data // The initial hash generation is omitted from the test, because it takes a long time. // The test contains some already-generated states, and their expected sums @@ -309,19 +297,28 @@ func TestLargeHashes(t *testing.T) { } func TestAllocations(t *testing.T) { - if boring.Enabled { - t.Skip("BoringCrypto doesn't allocate the same way as stdlib") - } - in := []byte("hello, world!") - out := make([]byte, 0, Size) - h := New() - n := int(testing.AllocsPerRun(10, func() { - h.Reset() - h.Write(in) - out = h.Sum(out[:0]) - })) - if n > 0 { - t.Errorf("allocs = %d, want 0", n) + cryptotest.SkipTestAllocations(t) + if n := testing.AllocsPerRun(10, func() { + in := []byte("hello, world!") + out := make([]byte, 0, Size) + + { + h := New() + h.Reset() + h.Write(in) + out = h.Sum(out[:0]) + } + { + h := New224() + h.Reset() + h.Write(in) + out = h.Sum(out[:0]) + } + + Sum256(in) + Sum224(in) + }); n > 0 { + t.Errorf("allocs = %v, want 0", n) } } @@ -335,17 +332,22 @@ func TestCgo(t *testing.T) { // The scan (if any) should be limited to the [16]byte. d := new(cgoData) d.Ptr = d + _ = d.Ptr // for unusedwrite check h := New() h.Write(d.Data[:]) h.Sum(nil) } -func TestSHA256Hash(t *testing.T) { +func TestHash(t *testing.T) { t.Run("SHA-224", func(t *testing.T) { - cryptotest.TestHash(t, New224) + cryptotest.TestAllImplementations(t, "sha256", func(t *testing.T) { + cryptotest.TestHash(t, New224) + }) }) t.Run("SHA-256", func(t *testing.T) { - cryptotest.TestHash(t, New) + cryptotest.TestAllImplementations(t, "sha256", func(t *testing.T) { + cryptotest.TestHash(t, New) + }) }) } diff --git a/crypto/sha256/sha256block_amd64.go b/crypto/sha256/sha256block_amd64.go deleted file mode 100644 index 3cf081cff26..00000000000 --- a/crypto/sha256/sha256block_amd64.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package sha256 - -import "github.com/runZeroInc/excrypto/internal/cpu" - -var useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI2 -var useSHA = useAVX2 && cpu.X86.HasSHA diff --git a/crypto/sha256/sha256block_arm64.go b/crypto/sha256/sha256block_arm64.go deleted file mode 100644 index 2288227cac4..00000000000 --- a/crypto/sha256/sha256block_arm64.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2017 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package sha256 - -import "github.com/runZeroInc/excrypto/internal/cpu" - -var k = _K - -//go:noescape -func sha256block(h []uint32, p []byte, k []uint32) - -func block(dig *digest, p []byte) { - if !cpu.ARM64.HasSHA2 { - blockGeneric(dig, p) - } else { - h := dig.h[:] - sha256block(h, p, k) - } -} diff --git a/crypto/sha256/sha256block_s390x.go b/crypto/sha256/sha256block_s390x.go deleted file mode 100644 index 0fbc6a787d6..00000000000 --- a/crypto/sha256/sha256block_s390x.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package sha256 - -import "github.com/runZeroInc/excrypto/internal/cpu" - -var useAsm = cpu.S390X.HasSHA256 diff --git a/crypto/sha3/sha3.go b/crypto/sha3/sha3.go new file mode 100644 index 00000000000..5edd9c4e768 --- /dev/null +++ b/crypto/sha3/sha3.go @@ -0,0 +1,240 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package sha3 implements the SHA-3 hash algorithms and the SHAKE extendable +// output functions defined in FIPS 202. +package sha3 + +import ( + "crypto" + "hash" + _ "unsafe" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha3" +) + +func init() { + crypto.RegisterHash(crypto.SHA3_224, func() hash.Hash { return New224() }) + crypto.RegisterHash(crypto.SHA3_256, func() hash.Hash { return New256() }) + crypto.RegisterHash(crypto.SHA3_384, func() hash.Hash { return New384() }) + crypto.RegisterHash(crypto.SHA3_512, func() hash.Hash { return New512() }) +} + +// Sum224 returns the SHA3-224 hash of data. +func Sum224(data []byte) [28]byte { + var out [28]byte + h := sha3.New224() + h.Write(data) + h.Sum(out[:0]) + return out +} + +// Sum256 returns the SHA3-256 hash of data. +func Sum256(data []byte) [32]byte { + var out [32]byte + h := sha3.New256() + h.Write(data) + h.Sum(out[:0]) + return out +} + +// Sum384 returns the SHA3-384 hash of data. +func Sum384(data []byte) [48]byte { + var out [48]byte + h := sha3.New384() + h.Write(data) + h.Sum(out[:0]) + return out +} + +// Sum512 returns the SHA3-512 hash of data. +func Sum512(data []byte) [64]byte { + var out [64]byte + h := sha3.New512() + h.Write(data) + h.Sum(out[:0]) + return out +} + +// SumSHAKE128 applies the SHAKE128 extendable output function to data and +// returns an output of the given length in bytes. +func SumSHAKE128(data []byte, length int) []byte { + // Outline the allocation for up to 256 bits of output to the caller's stack. + out := make([]byte, 32) + return sumSHAKE128(out, data, length) +} + +func sumSHAKE128(out, data []byte, length int) []byte { + if len(out) < length { + out = make([]byte, length) + } else { + out = out[:length] + } + h := sha3.NewShake128() + h.Write(data) + h.Read(out) + return out +} + +// SumSHAKE256 applies the SHAKE256 extendable output function to data and +// returns an output of the given length in bytes. +func SumSHAKE256(data []byte, length int) []byte { + // Outline the allocation for up to 512 bits of output to the caller's stack. + out := make([]byte, 64) + return sumSHAKE256(out, data, length) +} + +func sumSHAKE256(out, data []byte, length int) []byte { + if len(out) < length { + out = make([]byte, length) + } else { + out = out[:length] + } + h := sha3.NewShake256() + h.Write(data) + h.Read(out) + return out +} + +// SHA3 is an instance of a SHA-3 hash. It implements [hash.Hash]. +type SHA3 struct { + s sha3.Digest +} + +//go:linkname fips140hash_sha3Unwrap crypto/internal/fips140hash.sha3Unwrap +func fips140hash_sha3Unwrap(sha3 *SHA3) *sha3.Digest { + return &sha3.s +} + +// New224 creates a new SHA3-224 hash. +func New224() *SHA3 { + return &SHA3{*sha3.New224()} +} + +// New256 creates a new SHA3-256 hash. +func New256() *SHA3 { + return &SHA3{*sha3.New256()} +} + +// New384 creates a new SHA3-384 hash. +func New384() *SHA3 { + return &SHA3{*sha3.New384()} +} + +// New512 creates a new SHA3-512 hash. +func New512() *SHA3 { + return &SHA3{*sha3.New512()} +} + +// Write absorbs more data into the hash's state. +func (s *SHA3) Write(p []byte) (n int, err error) { + return s.s.Write(p) +} + +// Sum appends the current hash to b and returns the resulting slice. +func (s *SHA3) Sum(b []byte) []byte { + return s.s.Sum(b) +} + +// Reset resets the hash to its initial state. +func (s *SHA3) Reset() { + s.s.Reset() +} + +// Size returns the number of bytes Sum will produce. +func (s *SHA3) Size() int { + return s.s.Size() +} + +// BlockSize returns the hash's rate. +func (s *SHA3) BlockSize() int { + return s.s.BlockSize() +} + +// MarshalBinary implements [encoding.BinaryMarshaler]. +func (s *SHA3) MarshalBinary() ([]byte, error) { + return s.s.MarshalBinary() +} + +// AppendBinary implements [encoding.BinaryAppender]. +func (s *SHA3) AppendBinary(p []byte) ([]byte, error) { + return s.s.AppendBinary(p) +} + +// UnmarshalBinary implements [encoding.BinaryUnmarshaler]. +func (s *SHA3) UnmarshalBinary(data []byte) error { + return s.s.UnmarshalBinary(data) +} + +// SHAKE is an instance of a SHAKE extendable output function. +type SHAKE struct { + s sha3.SHAKE +} + +// NewSHAKE128 creates a new SHAKE128 XOF. +func NewSHAKE128() *SHAKE { + return &SHAKE{*sha3.NewShake128()} +} + +// NewSHAKE256 creates a new SHAKE256 XOF. +func NewSHAKE256() *SHAKE { + return &SHAKE{*sha3.NewShake256()} +} + +// NewCSHAKE128 creates a new cSHAKE128 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewSHAKE128. +func NewCSHAKE128(N, S []byte) *SHAKE { + return &SHAKE{*sha3.NewCShake128(N, S)} +} + +// NewCSHAKE256 creates a new cSHAKE256 XOF. +// +// N is used to define functions based on cSHAKE, it can be empty when plain +// cSHAKE is desired. S is a customization byte string used for domain +// separation. When N and S are both empty, this is equivalent to NewSHAKE256. +func NewCSHAKE256(N, S []byte) *SHAKE { + return &SHAKE{*sha3.NewCShake256(N, S)} +} + +// Write absorbs more data into the XOF's state. +// +// It panics if any output has already been read. +func (s *SHAKE) Write(p []byte) (n int, err error) { + return s.s.Write(p) +} + +// Read squeezes more output from the XOF. +// +// Any call to Write after a call to Read will panic. +func (s *SHAKE) Read(p []byte) (n int, err error) { + return s.s.Read(p) +} + +// Reset resets the XOF to its initial state. +func (s *SHAKE) Reset() { + s.s.Reset() +} + +// BlockSize returns the rate of the XOF. +func (s *SHAKE) BlockSize() int { + return s.s.BlockSize() +} + +// MarshalBinary implements [encoding.BinaryMarshaler]. +func (s *SHAKE) MarshalBinary() ([]byte, error) { + return s.s.MarshalBinary() +} + +// AppendBinary implements [encoding.BinaryAppender]. +func (s *SHAKE) AppendBinary(p []byte) ([]byte, error) { + return s.s.AppendBinary(p) +} + +// UnmarshalBinary implements [encoding.BinaryUnmarshaler]. +func (s *SHAKE) UnmarshalBinary(data []byte) error { + return s.s.UnmarshalBinary(data) +} diff --git a/crypto/sha3/sha3_test.go b/crypto/sha3/sha3_test.go new file mode 100644 index 00000000000..1fec73fb864 --- /dev/null +++ b/crypto/sha3/sha3_test.go @@ -0,0 +1,502 @@ +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sha3_test + +import ( + "bytes" + . "crypto/sha3" + "encoding/hex" + "io" + "math/rand" + "strings" + "testing" + + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +const testString = "brekeccakkeccak koax koax" + +// testDigests contains functions returning hash.Hash instances +// with output-length equal to the KAT length for SHA-3, Keccak +// and SHAKE instances. +var testDigests = map[string]func() *SHA3{ + "SHA3-224": New224, + "SHA3-256": New256, + "SHA3-384": New384, + "SHA3-512": New512, +} + +// testShakes contains functions that return *sha3.SHAKE instances for +// with output-length equal to the KAT length. +var testShakes = map[string]struct { + constructor func(N []byte, S []byte) *SHAKE + defAlgoName string + defCustomStr string +}{ + // NewCSHAKE without customization produces same result as SHAKE + "SHAKE128": {NewCSHAKE128, "", ""}, + "SHAKE256": {NewCSHAKE256, "", ""}, + "cSHAKE128": {NewCSHAKE128, "CSHAKE128", "CustomString"}, + "cSHAKE256": {NewCSHAKE256, "CSHAKE256", "CustomString"}, +} + +// decodeHex converts a hex-encoded string into a raw byte string. +func decodeHex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +// TestUnalignedWrite tests that writing data in an arbitrary pattern with +// small input buffers. +func TestUnalignedWrite(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", testUnalignedWrite) +} + +func testUnalignedWrite(t *testing.T) { + buf := sequentialBytes(0x10000) + for alg, df := range testDigests { + d := df() + d.Reset() + d.Write(buf) + want := d.Sum(nil) + d.Reset() + for i := 0; i < len(buf); { + // Cycle through offsets which make a 137 byte sequence. + // Because 137 is prime this sequence should exercise all corner cases. + offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} + for _, j := range offsets { + if v := len(buf) - i; v < j { + j = v + } + d.Write(buf[i : i+j]) + i += j + } + } + got := d.Sum(nil) + if !bytes.Equal(got, want) { + t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want) + } + } + + // Same for SHAKE + for alg, df := range testShakes { + want := make([]byte, 16) + got := make([]byte, 16) + d := df.constructor([]byte(df.defAlgoName), []byte(df.defCustomStr)) + + d.Reset() + d.Write(buf) + d.Read(want) + d.Reset() + for i := 0; i < len(buf); { + // Cycle through offsets which make a 137 byte sequence. + // Because 137 is prime this sequence should exercise all corner cases. + offsets := [17]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1} + for _, j := range offsets { + if v := len(buf) - i; v < j { + j = v + } + d.Write(buf[i : i+j]) + i += j + } + } + d.Read(got) + if !bytes.Equal(got, want) { + t.Errorf("Unaligned writes, alg=%s\ngot %q, want %q", alg, got, want) + } + } +} + +// TestAppend checks that appending works when reallocation is necessary. +func TestAppend(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", testAppend) +} + +func testAppend(t *testing.T) { + d := New224() + + for capacity := 2; capacity <= 66; capacity += 64 { + // The first time around the loop, Sum will have to reallocate. + // The second time, it will not. + buf := make([]byte, 2, capacity) + d.Reset() + d.Write([]byte{0xcc}) + buf = d.Sum(buf) + expected := "0000DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" + if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { + t.Errorf("got %s, want %s", got, expected) + } + } +} + +// TestAppendNoRealloc tests that appending works when no reallocation is necessary. +func TestAppendNoRealloc(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", testAppendNoRealloc) +} + +func testAppendNoRealloc(t *testing.T) { + buf := make([]byte, 1, 200) + d := New224() + d.Write([]byte{0xcc}) + buf = d.Sum(buf) + expected := "00DF70ADC49B2E76EEE3A6931B93FA41841C3AF2CDF5B32A18B5478C39" + if got := strings.ToUpper(hex.EncodeToString(buf)); got != expected { + t.Errorf("got %s, want %s", got, expected) + } +} + +// TestSqueezing checks that squeezing the full output a single time produces +// the same output as repeatedly squeezing the instance. +func TestSqueezing(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", testSqueezing) +} + +func testSqueezing(t *testing.T) { + for algo, v := range testShakes { + d0 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d0.Write([]byte(testString)) + ref := make([]byte, 32) + d0.Read(ref) + + d1 := v.constructor([]byte(v.defAlgoName), []byte(v.defCustomStr)) + d1.Write([]byte(testString)) + var multiple []byte + for range ref { + d1.Read(make([]byte, 0)) + one := make([]byte, 1) + d1.Read(one) + multiple = append(multiple, one...) + } + if !bytes.Equal(ref, multiple) { + t.Errorf("%s: squeezing %d bytes one at a time failed", algo, len(ref)) + } + } +} + +// sequentialBytes produces a buffer of size consecutive bytes 0x00, 0x01, ..., used for testing. +// +// The alignment of each slice is intentionally randomized to detect alignment +// issues in the implementation. See https://golang.org/issue/37644. +// Ideally, the compiler should fuzz the alignment itself. +// (See https://golang.org/issue/35128.) +func sequentialBytes(size int) []byte { + alignmentOffset := rand.Intn(8) + result := make([]byte, size+alignmentOffset)[alignmentOffset:] + for i := range result { + result[i] = byte(i) + } + return result +} + +func TestReset(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", testReset) +} + +func testReset(t *testing.T) { + out1 := make([]byte, 32) + out2 := make([]byte, 32) + + for _, v := range testShakes { + // Calculate hash for the first time + c := v.constructor(nil, []byte{0x99, 0x98}) + c.Write(sequentialBytes(0x100)) + c.Read(out1) + + // Calculate hash again + c.Reset() + c.Write(sequentialBytes(0x100)) + c.Read(out2) + + if !bytes.Equal(out1, out2) { + t.Error("\nExpected:\n", out1, "\ngot:\n", out2) + } + } +} + +var sinkSHA3 byte + +func TestAllocations(t *testing.T) { + cryptotest.SkipTestAllocations(t) + t.Run("New", func(t *testing.T) { + if allocs := testing.AllocsPerRun(10, func() { + h := New256() + b := []byte("ABC") + h.Write(b) + out := make([]byte, 0, 32) + out = h.Sum(out) + sinkSHA3 ^= out[0] + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1f", allocs) + } + }) + t.Run("NewSHAKE", func(t *testing.T) { + if allocs := testing.AllocsPerRun(10, func() { + h := NewSHAKE128() + b := []byte("ABC") + h.Write(b) + out := make([]byte, 32) + h.Read(out) + sinkSHA3 ^= out[0] + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1f", allocs) + } + }) + t.Run("Sum", func(t *testing.T) { + if allocs := testing.AllocsPerRun(10, func() { + b := []byte("ABC") + out := Sum256(b) + sinkSHA3 ^= out[0] + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1f", allocs) + } + }) + t.Run("SumSHAKE", func(t *testing.T) { + if allocs := testing.AllocsPerRun(10, func() { + b := []byte("ABC") + out := SumSHAKE128(b, 10) + sinkSHA3 ^= out[0] + }); allocs > 0 { + t.Errorf("expected zero allocations, got %0.1f", allocs) + } + }) +} + +func TestCSHAKEAccumulated(t *testing.T) { + // Generated with pycryptodome@3.20.0 + // + // from Crypto.Hash import cSHAKE128 + // rng = cSHAKE128.new() + // acc = cSHAKE128.new() + // for n in range(200): + // N = rng.read(n) + // for s in range(200): + // S = rng.read(s) + // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N) + // c.update(rng.read(100)) + // acc.update(c.read(200)) + // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N) + // c.update(rng.read(168)) + // acc.update(c.read(200)) + // c = cSHAKE128.cSHAKE_XOF(data=None, custom=S, capacity=256, function=N) + // c.update(rng.read(200)) + // acc.update(c.read(200)) + // print(acc.read(32).hex()) + // + // and with @noble/hashes@v1.5.0 + // + // import { bytesToHex } from "@noble/hashes/utils"; + // import { cshake128 } from "@noble/hashes/sha3-addons"; + // const rng = cshake128.create(); + // const acc = cshake128.create(); + // for (let n = 0; n < 200; n++) { + // const N = rng.xof(n); + // for (let s = 0; s < 200; s++) { + // const S = rng.xof(s); + // let c = cshake128.create({ NISTfn: N, personalization: S }); + // c.update(rng.xof(100)); + // acc.update(c.xof(200)); + // c = cshake128.create({ NISTfn: N, personalization: S }); + // c.update(rng.xof(168)); + // acc.update(c.xof(200)); + // c = cshake128.create({ NISTfn: N, personalization: S }); + // c.update(rng.xof(200)); + // acc.update(c.xof(200)); + // } + // } + // console.log(bytesToHex(acc.xof(32))); + // + cryptotest.TestAllImplementations(t, "sha3", func(t *testing.T) { + t.Run("cSHAKE128", func(t *testing.T) { + testCSHAKEAccumulated(t, NewCSHAKE128, (1600-256)/8, + "bb14f8657c6ec5403d0b0e2ef3d3393497e9d3b1a9a9e8e6c81dbaa5fd809252") + }) + t.Run("cSHAKE256", func(t *testing.T) { + testCSHAKEAccumulated(t, NewCSHAKE256, (1600-512)/8, + "0baaf9250c6e25f0c14ea5c7f9bfde54c8a922c8276437db28f3895bdf6eeeef") + }) + }) +} + +func testCSHAKEAccumulated(t *testing.T, newCSHAKE func(N, S []byte) *SHAKE, rate int64, exp string) { + rnd := newCSHAKE(nil, nil) + acc := newCSHAKE(nil, nil) + for n := 0; n < 200; n++ { + N := make([]byte, n) + rnd.Read(N) + for s := 0; s < 200; s++ { + S := make([]byte, s) + rnd.Read(S) + + c := newCSHAKE(N, S) + io.CopyN(c, rnd, 100 /* < rate */) + io.CopyN(acc, c, 200) + + c.Reset() + io.CopyN(c, rnd, rate) + io.CopyN(acc, c, 200) + + c.Reset() + io.CopyN(c, rnd, 200 /* > rate */) + io.CopyN(acc, c, 200) + } + } + out := make([]byte, 32) + acc.Read(out) + if got := hex.EncodeToString(out); got != exp { + t.Errorf("got %s, want %s", got, exp) + } +} + +func TestCSHAKELargeS(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", testCSHAKELargeS) +} + +func testCSHAKELargeS(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + // See https://go.dev/issue/66232. + const s = (1<<32)/8 + 1000 // s * 8 > 2^32 + S := make([]byte, s) + rnd := NewSHAKE128() + rnd.Read(S) + c := NewCSHAKE128(nil, S) + io.CopyN(c, rnd, 1000) + out := make([]byte, 32) + c.Read(out) + + // Generated with pycryptodome@3.20.0 + // + // from Crypto.Hash import cSHAKE128 + // rng = cSHAKE128.new() + // S = rng.read(536871912) + // c = cSHAKE128.new(custom=S) + // c.update(rng.read(1000)) + // print(c.read(32).hex()) + // + exp := "2cb9f237767e98f2614b8779cf096a52da9b3a849280bbddec820771ae529cf0" + if got := hex.EncodeToString(out); got != exp { + t.Errorf("got %s, want %s", got, exp) + } +} + +func TestMarshalUnmarshal(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha3", func(t *testing.T) { + t.Run("SHA3-224", func(t *testing.T) { testMarshalUnmarshal(t, New224()) }) + t.Run("SHA3-256", func(t *testing.T) { testMarshalUnmarshal(t, New256()) }) + t.Run("SHA3-384", func(t *testing.T) { testMarshalUnmarshal(t, New384()) }) + t.Run("SHA3-512", func(t *testing.T) { testMarshalUnmarshal(t, New512()) }) + t.Run("SHAKE128", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewSHAKE128()) }) + t.Run("SHAKE256", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewSHAKE256()) }) + t.Run("cSHAKE128", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewCSHAKE128([]byte("N"), []byte("S"))) }) + t.Run("cSHAKE256", func(t *testing.T) { testMarshalUnmarshalSHAKE(t, NewCSHAKE256([]byte("N"), []byte("S"))) }) + }) +} + +// TODO(filippo): move this to crypto/internal/cryptotest. +func testMarshalUnmarshal(t *testing.T, h *SHA3) { + buf := make([]byte, 200) + rand.Read(buf) + n := rand.Intn(200) + h.Write(buf) + want := h.Sum(nil) + h.Reset() + h.Write(buf[:n]) + b, err := h.MarshalBinary() + if err != nil { + t.Errorf("MarshalBinary: %v", err) + } + h.Write(bytes.Repeat([]byte{0}, 200)) + if err := h.UnmarshalBinary(b); err != nil { + t.Errorf("UnmarshalBinary: %v", err) + } + h.Write(buf[n:]) + got := h.Sum(nil) + if !bytes.Equal(got, want) { + t.Errorf("got %x, want %x", got, want) + } +} + +// TODO(filippo): move this to crypto/internal/cryptotest. +func testMarshalUnmarshalSHAKE(t *testing.T, h *SHAKE) { + buf := make([]byte, 200) + rand.Read(buf) + n := rand.Intn(200) + h.Write(buf) + want := make([]byte, 32) + h.Read(want) + h.Reset() + h.Write(buf[:n]) + b, err := h.MarshalBinary() + if err != nil { + t.Errorf("MarshalBinary: %v", err) + } + h.Write(bytes.Repeat([]byte{0}, 200)) + if err := h.UnmarshalBinary(b); err != nil { + t.Errorf("UnmarshalBinary: %v", err) + } + h.Write(buf[n:]) + got := make([]byte, 32) + h.Read(got) + if !bytes.Equal(got, want) { + t.Errorf("got %x, want %x", got, want) + } +} + +// benchmarkHash tests the speed to hash num buffers of buflen each. +func benchmarkHash(b *testing.B, h fips140.Hash, size, num int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + b.SetBytes(int64(size * num)) + b.StartTimer() + + var state []byte + for i := 0; i < b.N; i++ { + for j := 0; j < num; j++ { + h.Write(data) + } + state = h.Sum(state[:0]) + } + b.StopTimer() + h.Reset() +} + +// benchmarkShake is specialized to the Shake instances, which don't +// require a copy on reading output. +func benchmarkShake(b *testing.B, h *SHAKE, size, num int) { + b.StopTimer() + h.Reset() + data := sequentialBytes(size) + d := make([]byte, 32) + + b.SetBytes(int64(size * num)) + b.StartTimer() + + for i := 0; i < b.N; i++ { + h.Reset() + for j := 0; j < num; j++ { + h.Write(data) + } + h.Read(d) + } +} + +func BenchmarkSha3_512_MTU(b *testing.B) { benchmarkHash(b, New512(), 1350, 1) } +func BenchmarkSha3_384_MTU(b *testing.B) { benchmarkHash(b, New384(), 1350, 1) } +func BenchmarkSha3_256_MTU(b *testing.B) { benchmarkHash(b, New256(), 1350, 1) } +func BenchmarkSha3_224_MTU(b *testing.B) { benchmarkHash(b, New224(), 1350, 1) } + +func BenchmarkShake128_MTU(b *testing.B) { benchmarkShake(b, NewSHAKE128(), 1350, 1) } +func BenchmarkShake256_MTU(b *testing.B) { benchmarkShake(b, NewSHAKE256(), 1350, 1) } +func BenchmarkShake256_16x(b *testing.B) { benchmarkShake(b, NewSHAKE256(), 16, 1024) } +func BenchmarkShake256_1MiB(b *testing.B) { benchmarkShake(b, NewSHAKE256(), 1024, 1024) } + +func BenchmarkSha3_512_1MiB(b *testing.B) { benchmarkHash(b, New512(), 1024, 1024) } diff --git a/crypto/sha512/_asm/go.mod b/crypto/sha512/_asm/go.mod deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/sha512/_asm/go.sum b/crypto/sha512/_asm/go.sum deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/sha512/fallback_test.go b/crypto/sha512/fallback_test.go deleted file mode 100644 index b55a4a56fa9..00000000000 --- a/crypto/sha512/fallback_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build s390x && !purego - -package sha512 - -import ( - "fmt" - "io" - "testing" -) - -// Tests the fallback code path in case the optimized asm -// implementation cannot be used. -// See also TestBlockGeneric. -func TestGenericPath(t *testing.T) { - if !useAsm { - t.Skipf("assembly implementation unavailable") - } - useAsm = false - defer func() { useAsm = true }() - c := New() - in := "ΑΒΓΔΕϜΖΗΘΙΚΛΜΝΞΟΠϺϘΡΣΤΥΦΧΨΩ" - gold := "6922e319366d677f34c504af31bfcb29" + - "e531c125ecd08679362bffbd6b6ebfb9" + - "0dcc27dfc1f3d3b16a16c0763cf43b91" + - "40bbf9bbb7233724e9a0c6655b185d76" - if _, err := io.WriteString(c, in); err != nil { - t.Fatalf("could not write to c: %v", err) - } - out := fmt.Sprintf("%x", c.Sum(nil)) - if out != gold { - t.Fatalf("mismatch: got %s, wanted %s", out, gold) - } -} diff --git a/crypto/sha512/sha512.go b/crypto/sha512/sha512.go index 9721b953b90..544a3a19e72 100644 --- a/crypto/sha512/sha512.go +++ b/crypto/sha512/sha512.go @@ -11,12 +11,11 @@ package sha512 import ( - "errors" + "crypto" "hash" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/internal/boring" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/sha512" ) func init() { @@ -44,167 +43,6 @@ const ( BlockSize = 128 ) -const ( - chunk = 128 - init0 = 0x6a09e667f3bcc908 - init1 = 0xbb67ae8584caa73b - init2 = 0x3c6ef372fe94f82b - init3 = 0xa54ff53a5f1d36f1 - init4 = 0x510e527fade682d1 - init5 = 0x9b05688c2b3e6c1f - init6 = 0x1f83d9abfb41bd6b - init7 = 0x5be0cd19137e2179 - init0_224 = 0x8c3d37c819544da2 - init1_224 = 0x73e1996689dcd4d6 - init2_224 = 0x1dfab7ae32ff9c82 - init3_224 = 0x679dd514582f9fcf - init4_224 = 0x0f6d2b697bd44da8 - init5_224 = 0x77e36f7304c48942 - init6_224 = 0x3f9d85a86a1d36c8 - init7_224 = 0x1112e6ad91d692a1 - init0_256 = 0x22312194fc2bf72c - init1_256 = 0x9f555fa3c84c64c2 - init2_256 = 0x2393b86b6f53b151 - init3_256 = 0x963877195940eabd - init4_256 = 0x96283ee2a88effe3 - init5_256 = 0xbe5e1e2553863992 - init6_256 = 0x2b0199fc2c85b8aa - init7_256 = 0x0eb72ddc81c52ca2 - init0_384 = 0xcbbb9d5dc1059ed8 - init1_384 = 0x629a292a367cd507 - init2_384 = 0x9159015a3070dd17 - init3_384 = 0x152fecd8f70e5939 - init4_384 = 0x67332667ffc00b31 - init5_384 = 0x8eb44a8768581511 - init6_384 = 0xdb0c2e0d64f98fa7 - init7_384 = 0x47b5481dbefa4fa4 -) - -// digest represents the partial evaluation of a checksum. -type digest struct { - h [8]uint64 - x [chunk]byte - nx int - len uint64 - function crypto.Hash -} - -func (d *digest) Reset() { - switch d.function { - case crypto.SHA384: - d.h[0] = init0_384 - d.h[1] = init1_384 - d.h[2] = init2_384 - d.h[3] = init3_384 - d.h[4] = init4_384 - d.h[5] = init5_384 - d.h[6] = init6_384 - d.h[7] = init7_384 - case crypto.SHA512_224: - d.h[0] = init0_224 - d.h[1] = init1_224 - d.h[2] = init2_224 - d.h[3] = init3_224 - d.h[4] = init4_224 - d.h[5] = init5_224 - d.h[6] = init6_224 - d.h[7] = init7_224 - case crypto.SHA512_256: - d.h[0] = init0_256 - d.h[1] = init1_256 - d.h[2] = init2_256 - d.h[3] = init3_256 - d.h[4] = init4_256 - d.h[5] = init5_256 - d.h[6] = init6_256 - d.h[7] = init7_256 - default: - d.h[0] = init0 - d.h[1] = init1 - d.h[2] = init2 - d.h[3] = init3 - d.h[4] = init4 - d.h[5] = init5 - d.h[6] = init6 - d.h[7] = init7 - } - d.nx = 0 - d.len = 0 -} - -const ( - magic384 = "sha\x04" - magic512_224 = "sha\x05" - magic512_256 = "sha\x06" - magic512 = "sha\x07" - marshaledSize = len(magic512) + 8*8 + chunk + 8 -) - -func (d *digest) MarshalBinary() ([]byte, error) { - return d.AppendBinary(make([]byte, 0, marshaledSize)) -} - -func (d *digest) AppendBinary(b []byte) ([]byte, error) { - switch d.function { - case crypto.SHA384: - b = append(b, magic384...) - case crypto.SHA512_224: - b = append(b, magic512_224...) - case crypto.SHA512_256: - b = append(b, magic512_256...) - case crypto.SHA512: - b = append(b, magic512...) - default: - return nil, errors.New("crypto/sha512: invalid hash function") - } - b = byteorder.BeAppendUint64(b, d.h[0]) - b = byteorder.BeAppendUint64(b, d.h[1]) - b = byteorder.BeAppendUint64(b, d.h[2]) - b = byteorder.BeAppendUint64(b, d.h[3]) - b = byteorder.BeAppendUint64(b, d.h[4]) - b = byteorder.BeAppendUint64(b, d.h[5]) - b = byteorder.BeAppendUint64(b, d.h[6]) - b = byteorder.BeAppendUint64(b, d.h[7]) - b = append(b, d.x[:d.nx]...) - b = append(b, make([]byte, len(d.x)-d.nx)...) - b = byteorder.BeAppendUint64(b, d.len) - return b, nil -} - -func (d *digest) UnmarshalBinary(b []byte) error { - if len(b) < len(magic512) { - return errors.New("crypto/sha512: invalid hash state identifier") - } - switch { - case d.function == crypto.SHA384 && string(b[:len(magic384)]) == magic384: - case d.function == crypto.SHA512_224 && string(b[:len(magic512_224)]) == magic512_224: - case d.function == crypto.SHA512_256 && string(b[:len(magic512_256)]) == magic512_256: - case d.function == crypto.SHA512 && string(b[:len(magic512)]) == magic512: - default: - return errors.New("crypto/sha512: invalid hash state identifier") - } - if len(b) != marshaledSize { - return errors.New("crypto/sha512: invalid hash state size") - } - b = b[len(magic512):] - b, d.h[0] = consumeUint64(b) - b, d.h[1] = consumeUint64(b) - b, d.h[2] = consumeUint64(b) - b, d.h[3] = consumeUint64(b) - b, d.h[4] = consumeUint64(b) - b, d.h[5] = consumeUint64(b) - b, d.h[6] = consumeUint64(b) - b, d.h[7] = consumeUint64(b) - b = b[copy(d.x[:], b):] - b, d.len = consumeUint64(b) - d.nx = int(d.len % chunk) - return nil -} - -func consumeUint64(b []byte) ([]byte, uint64) { - return b[8:], byteorder.BeUint64(b) -} - // New returns a new [hash.Hash] computing the SHA-512 checksum. The Hash // also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal @@ -213,9 +51,7 @@ func New() hash.Hash { if boring.Enabled { return boring.NewSHA512() } - d := &digest{function: crypto.SHA512} - d.Reset() - return d + return sha512.New() } // New512_224 returns a new [hash.Hash] computing the SHA-512/224 checksum. The Hash @@ -223,9 +59,7 @@ func New() hash.Hash { // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal // state of the hash. func New512_224() hash.Hash { - d := &digest{function: crypto.SHA512_224} - d.Reset() - return d + return sha512.New512_224() } // New512_256 returns a new [hash.Hash] computing the SHA-512/256 checksum. The Hash @@ -233,123 +67,18 @@ func New512_224() hash.Hash { // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal // state of the hash. func New512_256() hash.Hash { - d := &digest{function: crypto.SHA512_256} - d.Reset() - return d + return sha512.New512_256() } // New384 returns a new [hash.Hash] computing the SHA-384 checksum. The Hash -// also implements [encoding.BinaryMarshaler], [encoding.AppendBinary] and +// also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and // [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal // state of the hash. func New384() hash.Hash { if boring.Enabled { return boring.NewSHA384() } - d := &digest{function: crypto.SHA384} - d.Reset() - return d -} - -func (d *digest) Size() int { - switch d.function { - case crypto.SHA512_224: - return Size224 - case crypto.SHA512_256: - return Size256 - case crypto.SHA384: - return Size384 - default: - return Size - } -} - -func (d *digest) BlockSize() int { return BlockSize } - -func (d *digest) Write(p []byte) (nn int, err error) { - if d.function != crypto.SHA512_224 && d.function != crypto.SHA512_256 { - boring.Unreachable() - } - nn = len(p) - d.len += uint64(nn) - if d.nx > 0 { - n := copy(d.x[d.nx:], p) - d.nx += n - if d.nx == chunk { - block(d, d.x[:]) - d.nx = 0 - } - p = p[n:] - } - if len(p) >= chunk { - n := len(p) &^ (chunk - 1) - block(d, p[:n]) - p = p[n:] - } - if len(p) > 0 { - d.nx = copy(d.x[:], p) - } - return -} - -func (d *digest) Sum(in []byte) []byte { - if d.function != crypto.SHA512_224 && d.function != crypto.SHA512_256 { - boring.Unreachable() - } - // Make a copy of d so that caller can keep writing and summing. - d0 := new(digest) - *d0 = *d - hash := d0.checkSum() - switch d0.function { - case crypto.SHA384: - return append(in, hash[:Size384]...) - case crypto.SHA512_224: - return append(in, hash[:Size224]...) - case crypto.SHA512_256: - return append(in, hash[:Size256]...) - default: - return append(in, hash[:]...) - } -} - -func (d *digest) checkSum() [Size]byte { - // Padding. Add a 1 bit and 0 bits until 112 bytes mod 128. - len := d.len - var tmp [128 + 16]byte // padding + length buffer - tmp[0] = 0x80 - var t uint64 - if len%128 < 112 { - t = 112 - len%128 - } else { - t = 128 + 112 - len%128 - } - - // Length in bits. - len <<= 3 - padlen := tmp[:t+16] - // Upper 64 bits are always zero, because len variable has type uint64, - // and tmp is already zeroed at that index, so we can skip updating it. - // byteorder.BePutUint64(padlen[t+0:], 0) - byteorder.BePutUint64(padlen[t+8:], len) - d.Write(padlen) - - if d.nx != 0 { - panic("d.nx != 0") - } - - var digest [Size]byte - byteorder.BePutUint64(digest[0:], d.h[0]) - byteorder.BePutUint64(digest[8:], d.h[1]) - byteorder.BePutUint64(digest[16:], d.h[2]) - byteorder.BePutUint64(digest[24:], d.h[3]) - byteorder.BePutUint64(digest[32:], d.h[4]) - byteorder.BePutUint64(digest[40:], d.h[5]) - if d.function != crypto.SHA384 { - byteorder.BePutUint64(digest[48:], d.h[6]) - byteorder.BePutUint64(digest[56:], d.h[7]) - } - - return digest + return sha512.New384() } // Sum512 returns the SHA512 checksum of the data. @@ -357,10 +86,11 @@ func Sum512(data []byte) [Size]byte { if boring.Enabled { return boring.SHA512(data) } - d := digest{function: crypto.SHA512} - d.Reset() - d.Write(data) - return d.checkSum() + h := New() + h.Write(data) + var sum [Size]byte + h.Sum(sum[:0]) + return sum } // Sum384 returns the SHA384 checksum of the data. @@ -368,30 +98,27 @@ func Sum384(data []byte) [Size384]byte { if boring.Enabled { return boring.SHA384(data) } - d := digest{function: crypto.SHA384} - d.Reset() - d.Write(data) - sum := d.checkSum() - ap := (*[Size384]byte)(sum[:]) - return *ap + h := New384() + h.Write(data) + var sum [Size384]byte + h.Sum(sum[:0]) + return sum } // Sum512_224 returns the Sum512/224 checksum of the data. func Sum512_224(data []byte) [Size224]byte { - d := digest{function: crypto.SHA512_224} - d.Reset() - d.Write(data) - sum := d.checkSum() - ap := (*[Size224]byte)(sum[:]) - return *ap + h := New512_224() + h.Write(data) + var sum [Size224]byte + h.Sum(sum[:0]) + return sum } // Sum512_256 returns the Sum512/256 checksum of the data. func Sum512_256(data []byte) [Size256]byte { - d := digest{function: crypto.SHA512_256} - d.Reset() - d.Write(data) - sum := d.checkSum() - ap := (*[Size256]byte)(sum[:]) - return *ap + h := New512_256() + h.Write(data) + var sum [Size256]byte + h.Sum(sum[:0]) + return sum } diff --git a/crypto/sha512/sha512_test.go b/crypto/sha512/sha512_test.go index 7773d344926..9404e17fdc0 100644 --- a/crypto/sha512/sha512_test.go +++ b/crypto/sha512/sha512_test.go @@ -8,17 +8,14 @@ package sha512 import ( "bytes" + "encoding" "encoding/hex" "fmt" "hash" "io" "testing" - "crypto/rand" - - "github.com/runZeroInc/excrypto/crypto/internal/boring" "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" - "github.com/runZeroInc/excrypto/encoding" ) type sha512Test struct { @@ -682,6 +679,12 @@ func testHash(t *testing.T, name, in, outHex string, oneShotResult []byte, diges } func TestGolden(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) { + testGolden(t) + }) +} + +func testGolden(t *testing.T) { tests := []struct { name string oneShotHash func(in []byte) []byte @@ -722,6 +725,12 @@ func TestGolden(t *testing.T) { } func TestGoldenMarshal(t *testing.T) { + cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) { + testGoldenMarshal(t) + }) +} + +func testGoldenMarshal(t *testing.T) { tests := []struct { name string newHash func() hash.Hash @@ -836,21 +845,6 @@ func TestBlockSize(t *testing.T) { } } -// Tests that blockGeneric (pure Go) and block (in assembly for some architectures) match. -func TestBlockGeneric(t *testing.T) { - if boring.Enabled { - t.Skip("BoringCrypto doesn't expose digest") - } - gen, asm := New().(*digest), New().(*digest) - buf := make([]byte, BlockSize*20) // arbitrary factor - rand.Read(buf) - blockGeneric(gen, buf) - block(asm, buf) - if *gen != *asm { - t.Error("block and blockGeneric resulted in different states") - } -} - // Tests for unmarshaling hashes that have hashed a large amount of data // The initial hash generation is omitted from the test, because it takes a long time. // The test contains some already-generated states, and their expected sums @@ -908,34 +902,65 @@ func TestLargeHashes(t *testing.T) { } func TestAllocations(t *testing.T) { - if boring.Enabled { - t.Skip("BoringCrypto doesn't allocate the same way as stdlib") - } - in := []byte("hello, world!") - out := make([]byte, 0, Size) - h := New() - n := int(testing.AllocsPerRun(10, func() { - h.Reset() - h.Write(in) - out = h.Sum(out[:0]) - })) - if n > 0 { - t.Errorf("allocs = %d, want 0", n) + cryptotest.SkipTestAllocations(t) + if n := testing.AllocsPerRun(10, func() { + in := []byte("hello, world!") + out := make([]byte, 0, Size) + + { + h := New() + h.Reset() + h.Write(in) + out = h.Sum(out[:0]) + } + { + h := New512_224() + h.Reset() + h.Write(in) + out = h.Sum(out[:0]) + } + { + h := New512_256() + h.Reset() + h.Write(in) + out = h.Sum(out[:0]) + } + { + h := New384() + h.Reset() + h.Write(in) + out = h.Sum(out[:0]) + } + + Sum512(in) + Sum384(in) + Sum512_224(in) + Sum512_256(in) + }); n > 0 { + t.Errorf("allocs = %v, want 0", n) } } -func TestSHA512Hash(t *testing.T) { +func TestHash(t *testing.T) { t.Run("SHA-384", func(t *testing.T) { - cryptotest.TestHash(t, New384) + cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) { + cryptotest.TestHash(t, New384) + }) }) t.Run("SHA-512/224", func(t *testing.T) { - cryptotest.TestHash(t, New512_224) + cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) { + cryptotest.TestHash(t, New512_224) + }) }) t.Run("SHA-512/256", func(t *testing.T) { - cryptotest.TestHash(t, New512_256) + cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) { + cryptotest.TestHash(t, New512_256) + }) }) t.Run("SHA-512", func(t *testing.T) { - cryptotest.TestHash(t, New) + cryptotest.TestAllImplementations(t, "sha512", func(t *testing.T) { + cryptotest.TestHash(t, New) + }) }) } diff --git a/crypto/sha512/sha512block_amd64.go b/crypto/sha512/sha512block_amd64.go deleted file mode 100644 index ee100ec52f2..00000000000 --- a/crypto/sha512/sha512block_amd64.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package sha512 - -import "github.com/runZeroInc/excrypto/internal/cpu" - -//go:noescape -func blockAVX2(dig *digest, p []byte) - -//go:noescape -func blockAMD64(dig *digest, p []byte) - -var useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI1 && cpu.X86.HasBMI2 - -func block(dig *digest, p []byte) { - if useAVX2 { - blockAVX2(dig, p) - } else { - blockAMD64(dig, p) - } -} diff --git a/crypto/sha512/sha512block_arm64.go b/crypto/sha512/sha512block_arm64.go deleted file mode 100644 index eea57e73f40..00000000000 --- a/crypto/sha512/sha512block_arm64.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package sha512 - -import "github.com/runZeroInc/excrypto/internal/cpu" - -func block(dig *digest, p []byte) { - if cpu.ARM64.HasSHA512 { - blockAsm(dig, p) - return - } - blockGeneric(dig, p) -} - -//go:noescape -func blockAsm(dig *digest, p []byte) diff --git a/crypto/sha512/sha512block_s390x.go b/crypto/sha512/sha512block_s390x.go deleted file mode 100644 index be2de5e1ec5..00000000000 --- a/crypto/sha512/sha512block_s390x.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package sha512 - -import "github.com/runZeroInc/excrypto/internal/cpu" - -var useAsm = cpu.S390X.HasSHA512 diff --git a/crypto/ssl3/tls/handshake_client.go b/crypto/ssl3/tls/handshake_client.go index ea492fc8323..8ec080f94eb 100644 --- a/crypto/ssl3/tls/handshake_client.go +++ b/crypto/ssl3/tls/handshake_client.go @@ -6,7 +6,6 @@ package tls import ( "bytes" - "encoding/asn1" "encoding/binary" "errors" "fmt" @@ -16,6 +15,8 @@ import ( "strconv" "time" + "github.com/runZeroInc/excrypto/encoding/asn1" + "github.com/runZeroInc/excrypto/crypto/dsa" "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/rsa" diff --git a/crypto/ssl3/zcrypto_schemas/__init__.py b/crypto/ssl3/zcrypto_schemas/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/crypto/subtle/constant_time.go b/crypto/subtle/constant_time.go index 4e0527f9d5a..8885e8048bf 100644 --- a/crypto/subtle/constant_time.go +++ b/crypto/subtle/constant_time.go @@ -6,57 +6,41 @@ // code but require careful thought to use correctly. package subtle +import "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + // ConstantTimeCompare returns 1 if the two slices, x and y, have equal contents // and 0 otherwise. The time taken is a function of the length of the slices and // is independent of the contents. If the lengths of x and y do not match it // returns 0 immediately. func ConstantTimeCompare(x, y []byte) int { - if len(x) != len(y) { - return 0 - } - - var v byte - - for i := 0; i < len(x); i++ { - v |= x[i] ^ y[i] - } - - return ConstantTimeByteEq(v, 0) + return subtle.ConstantTimeCompare(x, y) } // ConstantTimeSelect returns x if v == 1 and y if v == 0. // Its behavior is undefined if v takes any other value. -func ConstantTimeSelect(v, x, y int) int { return ^(v-1)&x | (v-1)&y } +func ConstantTimeSelect(v, x, y int) int { + return subtle.ConstantTimeSelect(v, x, y) +} // ConstantTimeByteEq returns 1 if x == y and 0 otherwise. func ConstantTimeByteEq(x, y uint8) int { - return int((uint32(x^y) - 1) >> 31) + return subtle.ConstantTimeByteEq(x, y) } // ConstantTimeEq returns 1 if x == y and 0 otherwise. func ConstantTimeEq(x, y int32) int { - return int((uint64(uint32(x^y)) - 1) >> 63) + return subtle.ConstantTimeEq(x, y) } // ConstantTimeCopy copies the contents of y into x (a slice of equal length) // if v == 1. If v == 0, x is left unchanged. Its behavior is undefined if v // takes any other value. func ConstantTimeCopy(v int, x, y []byte) { - if len(x) != len(y) { - panic("subtle: slices have different lengths") - } - - xmask := byte(v - 1) - ymask := byte(^(v - 1)) - for i := 0; i < len(x); i++ { - x[i] = x[i]&xmask | y[i]&ymask - } + subtle.ConstantTimeCopy(v, x, y) } // ConstantTimeLessOrEq returns 1 if x <= y and 0 otherwise. // Its behavior is undefined if x or y are negative or > 2**31 - 1. func ConstantTimeLessOrEq(x, y int) int { - x32 := int32(x) - y32 := int32(y) - return int(((x32 - y32 - 1) >> 31) & 1) + return subtle.ConstantTimeLessOrEq(x, y) } diff --git a/crypto/subtle/dit.go b/crypto/subtle/dit.go new file mode 100644 index 00000000000..c23df971f0b --- /dev/null +++ b/crypto/subtle/dit.go @@ -0,0 +1,50 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package subtle + +import ( + "internal/runtime/sys" + "runtime" +) + +// WithDataIndependentTiming enables architecture specific features which ensure +// that the timing of specific instructions is independent of their inputs +// before executing f. On f returning it disables these features. +// +// WithDataIndependentTiming should only be used when f is written to make use +// of constant-time operations. WithDataIndependentTiming does not make +// variable-time code constant-time. +// +// WithDataIndependentTiming may lock the current goroutine to the OS thread for +// the duration of f. Calls to WithDataIndependentTiming may be nested. +// +// On Arm64 processors with FEAT_DIT, WithDataIndependentTiming enables +// PSTATE.DIT. See https://developer.arm.com/documentation/ka005181/1-0/?lang=en. +// +// Currently, on all other architectures WithDataIndependentTiming executes f immediately +// with no other side-effects. +// +//go:noinline +func WithDataIndependentTiming(f func()) { + if !sys.DITSupported { + f() + return + } + + runtime.LockOSThread() + defer runtime.UnlockOSThread() + + alreadyEnabled := sys.EnableDIT() + + // disableDIT is called in a deferred function so that if f panics we will + // still disable DIT, in case the panic is recovered further up the stack. + defer func() { + if !alreadyEnabled { + sys.DisableDIT() + } + }() + + f() +} diff --git a/crypto/subtle/dit_test.go b/crypto/subtle/dit_test.go new file mode 100644 index 00000000000..29779683b57 --- /dev/null +++ b/crypto/subtle/dit_test.go @@ -0,0 +1,65 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package subtle + +import ( + "internal/cpu" + "internal/runtime/sys" + "testing" +) + +func TestWithDataIndependentTiming(t *testing.T) { + if !cpu.ARM64.HasDIT { + t.Skip("CPU does not support DIT") + } + + ditAlreadyEnabled := sys.DITEnabled() + + WithDataIndependentTiming(func() { + if !sys.DITEnabled() { + t.Fatal("dit not enabled within WithDataIndependentTiming closure") + } + + WithDataIndependentTiming(func() { + if !sys.DITEnabled() { + t.Fatal("dit not enabled within nested WithDataIndependentTiming closure") + } + }) + + if !sys.DITEnabled() { + t.Fatal("dit not enabled after return from nested WithDataIndependentTiming closure") + } + }) + + if !ditAlreadyEnabled && sys.DITEnabled() { + t.Fatal("dit not unset after returning from WithDataIndependentTiming closure") + } +} + +func TestDITPanic(t *testing.T) { + if !cpu.ARM64.HasDIT { + t.Skip("CPU does not support DIT") + } + + ditAlreadyEnabled := sys.DITEnabled() + + defer func() { + e := recover() + if e == nil { + t.Fatal("didn't panic") + } + if !ditAlreadyEnabled && sys.DITEnabled() { + t.Error("DIT still enabled after panic inside of WithDataIndependentTiming closure") + } + }() + + WithDataIndependentTiming(func() { + if !sys.DITEnabled() { + t.Fatal("dit not enabled within WithDataIndependentTiming closure") + } + + panic("bad") + }) +} diff --git a/crypto/subtle/xor.go b/crypto/subtle/xor.go index 158dbcede90..f8979e863a4 100644 --- a/crypto/subtle/xor.go +++ b/crypto/subtle/xor.go @@ -4,18 +4,16 @@ package subtle +import "github.com/runZeroInc/excrypto/crypto/internal/fips140/subtle" + // XORBytes sets dst[i] = x[i] ^ y[i] for all i < n = min(len(x), len(y)), // returning n, the number of bytes written to dst. +// // If dst does not have length at least n, // XORBytes panics without writing anything to dst. +// +// dst and x or y may overlap exactly or not at all, +// otherwise XORBytes may panic. func XORBytes(dst, x, y []byte) int { - n := min(len(x), len(y)) - if n == 0 { - return 0 - } - if n > len(dst) { - panic("subtle.XORBytes: dst too short") - } - xorBytes(&dst[0], &x[0], &y[0], n) // arch-specific - return n + return subtle.XORBytes(dst, x, y) } diff --git a/crypto/subtle/xor_amd64.go b/crypto/subtle/xor_amd64.go deleted file mode 100644 index 3bb2f08b7c9..00000000000 --- a/crypto/subtle/xor_amd64.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package subtle - -//go:noescape -func xorBytes(dst, a, b *byte, n int) diff --git a/crypto/subtle/xor_arm64.go b/crypto/subtle/xor_arm64.go deleted file mode 100644 index 65bab4c6574..00000000000 --- a/crypto/subtle/xor_arm64.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2020 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !purego - -package subtle - -//go:noescape -func xorBytes(dst, a, b *byte, n int) diff --git a/crypto/subtle/xor_linux_test.go b/crypto/subtle/xor_linux_test.go new file mode 100644 index 00000000000..eee6e468712 --- /dev/null +++ b/crypto/subtle/xor_linux_test.go @@ -0,0 +1,47 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package subtle_test + +import ( + "syscall" + "testing" + + "github.com/runZeroInc/excrypto/crypto/subtle" +) + +// dangerousSlice returns a slice which is immediately +// preceded and followed by a faulting page. +// Copied from the bytes package tests. +func dangerousSlice(t *testing.T) []byte { + pagesize := syscall.Getpagesize() + b, err := syscall.Mmap(0, 0, 3*pagesize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANONYMOUS|syscall.MAP_PRIVATE) + if err != nil { + t.Fatalf("mmap failed %s", err) + } + err = syscall.Mprotect(b[:pagesize], syscall.PROT_NONE) + if err != nil { + t.Fatalf("mprotect low failed %s\n", err) + } + err = syscall.Mprotect(b[2*pagesize:], syscall.PROT_NONE) + if err != nil { + t.Fatalf("mprotect high failed %s\n", err) + } + return b[pagesize : 2*pagesize] +} + +func TestXORBytesBoundary(t *testing.T) { + safe := make([]byte, syscall.Getpagesize()*2) + spicy := dangerousSlice(t) + for i := 1; i <= syscall.Getpagesize(); i++ { + start := spicy[:i] + end := spicy[len(spicy)-i:] + subtle.XORBytes(end, safe, safe[:i]) + subtle.XORBytes(start, safe, safe[:i]) + subtle.XORBytes(safe, start, safe) + subtle.XORBytes(safe, end, safe) + subtle.XORBytes(safe, safe, start) + subtle.XORBytes(safe, safe, end) + } +} diff --git a/crypto/subtle/xor_test.go b/crypto/subtle/xor_test.go index 65dbe7c953f..7d780b9aab4 100644 --- a/crypto/subtle/xor_test.go +++ b/crypto/subtle/xor_test.go @@ -6,13 +6,11 @@ package subtle_test import ( "bytes" + . "crypto/subtle" "fmt" - "io" "testing" "crypto/rand" - - . "github.com/runZeroInc/excrypto/crypto/subtle" ) func TestXORBytes(t *testing.T) { @@ -23,32 +21,50 @@ func TestXORBytes(t *testing.T) { for alignP := 0; alignP < 8; alignP++ { for alignQ := 0; alignQ < 8; alignQ++ { for alignD := 0; alignD < 8; alignD++ { - p := make([]byte, alignP+n, alignP+n+10)[alignP:] - q := make([]byte, alignQ+n, alignQ+n+10)[alignQ:] + p := make([]byte, alignP+n, alignP+n+100)[alignP:] + q := make([]byte, alignQ+n, alignQ+n+100)[alignQ:] if n&1 != 0 { p = p[:n] } else { q = q[:n] } - if _, err := io.ReadFull(rand.Reader, p); err != nil { - t.Fatal(err) - } - if _, err := io.ReadFull(rand.Reader, q); err != nil { - t.Fatal(err) + rand.Read(p) + rand.Read(q) + + d := make([]byte, alignD+n+100) + rand.Read(d) + + want := bytes.Clone(d) + for i := range n { + want[alignD+i] = p[i] ^ q[i] } - d := make([]byte, alignD+n, alignD+n+10) - for i := range d { - d[i] = 0xdd + if nn := XORBytes(d[alignD:], p, q); !bytes.Equal(d, want) { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d:\n\tp = %x\n\tq = %x\n\td = %x\n\twant %x\n", n, alignP, alignQ, alignD, p, q, d, want) + } else if nn != n { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %d, want %d", n, alignP, alignQ, alignD, nn, n) } - want := make([]byte, len(d), cap(d)) - copy(want[:cap(want)], d[:cap(d)]) - for i := 0; i < n; i++ { - want[alignD+i] = p[i] ^ q[i] + p1 := bytes.Clone(p) + if nn := XORBytes(p, p, q); !bytes.Equal(p, want[alignD:alignD+n]) { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d:\n\tp = %x\n\tq = %x\n\td = %x\n\twant %x\n", n, alignP, alignQ, alignD, p, q, d, want) + } else if nn != n { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %d, want %d", n, alignP, alignQ, alignD, nn, n) + } + if nn := XORBytes(q, p1, q); !bytes.Equal(q, want[alignD:alignD+n]) { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d:\n\tp = %x\n\tq = %x\n\td = %x\n\twant %x\n", n, alignP, alignQ, alignD, p, q, d, want) + } else if nn != n { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %d, want %d", n, alignP, alignQ, alignD, nn, n) } - if XORBytes(d[alignD:], p, q); !bytes.Equal(d, want) { - t.Fatalf("n=%d alignP=%d alignQ=%d alignD=%d:\n\tp = %x\n\tq = %x\n\td = %x\n\twant %x\n", n, alignP, alignQ, alignD, p, q, d, want) + if nn := XORBytes(p, p, p); !bytes.Equal(p, make([]byte, n)) { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %x, want %x", n, alignP, alignQ, alignD, p, make([]byte, n)) + } else if nn != n { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %d, want %d", n, alignP, alignQ, alignD, nn, n) + } + if nn := XORBytes(p1, q, q); !bytes.Equal(p1, make([]byte, n)) { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %x, want %x", n, alignP, alignQ, alignD, p1, make([]byte, n)) + } else if nn != n { + t.Errorf("n=%d alignP=%d alignQ=%d alignD=%d: got %d, want %d", n, alignP, alignQ, alignD, nn, n) } } } @@ -63,13 +79,21 @@ func TestXorBytesPanic(t *testing.T) { mustPanic(t, "subtle.XORBytes: dst too short", func() { XORBytes(make([]byte, 1), make([]byte, 2), make([]byte, 3)) }) + mustPanic(t, "subtle.XORBytes: invalid overlap", func() { + x := make([]byte, 3) + XORBytes(x, x[1:], make([]byte, 2)) + }) + mustPanic(t, "subtle.XORBytes: invalid overlap", func() { + x := make([]byte, 3) + XORBytes(x, make([]byte, 2), x[1:]) + }) } func BenchmarkXORBytes(b *testing.B) { dst := make([]byte, 1<<15) data0 := make([]byte, 1<<15) data1 := make([]byte, 1<<15) - sizes := []int64{1 << 3, 1 << 7, 1 << 11, 1 << 15} + sizes := []int64{1 << 3, 1 << 7, 1 << 11, 1 << 13, 1 << 15} for _, size := range sizes { b.Run(fmt.Sprintf("%dBytes", size), func(b *testing.B) { s0 := data0[:size] @@ -82,9 +106,30 @@ func BenchmarkXORBytes(b *testing.B) { } } +func BenchmarkXORBytesAlignment(b *testing.B) { + dst := make([]byte, 8+1<<11) + data0 := make([]byte, 8+1<<11) + data1 := make([]byte, 8+1<<11) + sizes := []int64{1 << 3, 1 << 7, 1 << 11} + for _, size := range sizes { + for offset := int64(0); offset < 8; offset++ { + b.Run(fmt.Sprintf("%dBytes%dOffset", size, offset), func(b *testing.B) { + d := dst[offset : offset+size] + s0 := data0[offset : offset+size] + s1 := data1[offset : offset+size] + b.SetBytes(int64(size)) + for i := 0; i < b.N; i++ { + XORBytes(d, s0, s1) + } + }) + } + } +} + func mustPanic(t *testing.T, expected string, f func()) { t.Helper() defer func() { + t.Helper() switch msg := recover().(type) { case nil: t.Errorf("expected panic(%q), but did not panic", expected) diff --git a/crypto/tls/auth.go b/crypto/tls/auth.go index f4778e9f3a4..1d460d21c9c 100644 --- a/crypto/tls/auth.go +++ b/crypto/tls/auth.go @@ -6,16 +6,17 @@ package tls import ( "bytes" + "crypto" "errors" "fmt" "hash" "io" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/ed25519" "github.com/runZeroInc/excrypto/crypto/elliptic" "github.com/runZeroInc/excrypto/crypto/rsa" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" ) // verifyHandshakeSignature verifies a signature against pre-hashed @@ -244,7 +245,7 @@ func selectSignatureScheme(vers uint16, c *Certificate, peerAlgs []SignatureSche // Pick signature scheme in the peer's preference order, as our // preference order is not configurable. for _, preferredAlg := range peerAlgs { - if needFIPS() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { + if fips140tls.Required() && !isSupportedSignatureAlgorithm(preferredAlg, defaultSupportedSignatureAlgorithmsFIPS) { continue } if isSupportedSignatureAlgorithm(preferredAlg, supportedAlgs) { diff --git a/crypto/tls/auth_test.go b/crypto/tls/auth_test.go index fb07971aa4b..97dacb828ef 100644 --- a/crypto/tls/auth_test.go +++ b/crypto/tls/auth_test.go @@ -5,8 +5,10 @@ package tls import ( - "github.com/runZeroInc/excrypto/crypto" + "crypto" "testing" + + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" ) func TestSignatureSelection(t *testing.T) { @@ -57,6 +59,11 @@ func TestSignatureSelection(t *testing.T) { } for testNo, test := range tests { + if fips140tls.Required() && (test.expectedHash == crypto.SHA1 || test.expectedSigAlg == Ed25519) { + t.Logf("skipping test[%d] - not compatible with TLS FIPS mode", testNo) + continue + } + sigAlg, err := selectSignatureScheme(test.tlsVersion, test.cert, test.peerSigAlgs) if err != nil { t.Errorf("test[%d]: unexpected selectSignatureScheme error: %v", testNo, err) diff --git a/crypto/tls/bogo_config.json b/crypto/tls/bogo_config.json index 2363dd5d659..6472512158c 100644 --- a/crypto/tls/bogo_config.json +++ b/crypto/tls/bogo_config.json @@ -12,7 +12,7 @@ "TLS-ECH-Client-Reject-ResumeInnerSession-TLS12": "We won't attempt to negotiate 1.2 if ECH is enabled (we could possibly test this if we had the ability to indicate not to send ECH on resumption?)", - "TLS-ECH-Client-Reject-EarlyDataRejected": "We don't support switiching out ECH configs with this level of granularity", + "TLS-ECH-Client-Reject-EarlyDataRejected": "Go does not support early (0-RTT) data", "TLS-ECH-Client-NoNPN": "We don't support NPN", @@ -30,8 +30,19 @@ "TLS-ECH-Client-NoSupportedConfigs": "We don't support fallback to cleartext when there are no valid ECH configs", "TLS-ECH-Client-SkipInvalidPublicName": "We don't support fallback to cleartext when there are no valid ECH configs", + "TLS-ECH-Server-EarlyData": "Go does not support early (0-RTT) data", + "TLS-ECH-Server-EarlyDataRejected": "Go does not support early (0-RTT) data", - "*ECH-Server*": "no ECH server support", + "MLKEMKeyShareIncludedSecond": "BoGo wants us to order the key shares based on its preference, but we don't support that", + "MLKEMKeyShareIncludedThird": "BoGo wants us to order the key shares based on its preference, but we don't support that", + "PostQuantumNotEnabledByDefaultInClients": "We do enable it by default!", + "*-Kyber-TLS13": "We don't support Kyber, only ML-KEM (BoGo bug ignoring AllCurves?)", + + "SendEmptySessionTicket-TLS13": "https://github.com/golang/go/issues/70513", + + "*-SignDefault-*": "TODO, partially it encodes BoringSSL defaults, partially we might be missing some implicit behavior of a missing flag", + + "V2ClientHello-*": "We don't support SSLv2", "SendV2ClientHello*": "We don't support SSLv2", "*QUIC*": "No QUIC support", "Compliance-fips*": "No FIPS", @@ -229,5 +240,15 @@ "EarlyData-UnexpectedHandshake-Server-TLS13": "TODO: first pass, this should be fixed", "EarlyData-CipherMismatch-Client-TLS13": "TODO: first pass, this should be fixed", "Resume-Server-UnofferedCipher-TLS13": "TODO: first pass, this should be fixed" + }, + "AllCurves": [ + 23, + 24, + 25, + 29, + 4588 + ], + "ErrorMap": { + ":ECH_REJECTED:": "tls: server rejected ECH" } } diff --git a/crypto/tls/bogo_shim_test.go b/crypto/tls/bogo_shim_test.go index 4352cf38a8e..1c4886c1353 100644 --- a/crypto/tls/bogo_shim_test.go +++ b/crypto/tls/bogo_shim_test.go @@ -1,3 +1,7 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package tls import ( @@ -7,14 +11,12 @@ import ( "encoding/pem" "flag" "fmt" - "github.com/runZeroInc/excrypto/crypto/x509" - "github.com/runZeroInc/excrypto/internal/byteorder" - "github.com/runZeroInc/excrypto/internal/testenv" + "internal/byteorder" + "internal/testenv" "io" "log" "net" "os" - "os/exec" "path/filepath" "runtime" "slices" @@ -22,6 +24,9 @@ import ( "strings" "testing" + "github.com/runZeroInc/excrypto/crypto/internal/cryptotest" + "github.com/runZeroInc/excrypto/crypto/x509" + "github.com/runZeroInc/excrypto/x/crypto/cryptobyte" ) @@ -72,6 +77,9 @@ var ( onResumeExpectECHAccepted = flag.Bool("on-resume-expect-ech-accept", false, "") _ = flag.Bool("on-resume-expect-no-ech-name-override", false, "") expectedServerName = flag.String("expect-server-name", "", "") + echServerConfig = flagStringSlice("ech-server-config", "") + echServerKey = flagStringSlice("ech-server-key", "") + echServerRetryConfig = flagStringSlice("ech-is-retry-config", "") expectSessionMiss = flag.Bool("expect-session-miss", false, "") @@ -101,12 +109,12 @@ func flagStringSlice(name, usage string) *stringSlice { return f } -func (saf stringSlice) String() string { - return strings.Join(saf, ",") +func (saf *stringSlice) String() string { + return strings.Join(*saf, ",") } -func (saf stringSlice) Set(s string) error { - saf = append(saf, s) +func (saf *stringSlice) Set(s string) error { + *saf = append(*saf, s) return nil } @@ -244,6 +252,29 @@ func bogoShim() { } } + if len(*echServerConfig) != 0 { + if len(*echServerConfig) != len(*echServerKey) || len(*echServerConfig) != len(*echServerRetryConfig) { + log.Fatal("-ech-server-config, -ech-server-key, and -ech-is-retry-config mismatch") + } + + for i, c := range *echServerConfig { + configBytes, err := base64.StdEncoding.DecodeString(c) + if err != nil { + log.Fatalf("parse ech-server-config err: %s", err) + } + privBytes, err := base64.StdEncoding.DecodeString((*echServerKey)[i]) + if err != nil { + log.Fatalf("parse ech-server-key err: %s", err) + } + + cfg.EncryptedClientHelloKeys = append(cfg.EncryptedClientHelloKeys, EncryptedClientHelloKey{ + Config: configBytes, + PrivateKey: privBytes, + SendAsRetry: (*echServerRetryConfig)[i] == "1", + }) + } + } + for i := 0; i < *resumeCount+1; i++ { if i > 0 && (*onResumeECHConfigListB64 != "") { echConfigList, err := base64.StdEncoding.DecodeString(*onResumeECHConfigListB64) @@ -261,7 +292,7 @@ func bogoShim() { // Write the shim ID we were passed as a little endian uint64 shimIDBytes := make([]byte, 8) - byteorder.LePutUint64(shimIDBytes, *shimID) + byteorder.LEPutUint64(shimIDBytes, *shimID) if _, err := conn.Write(shimIDBytes); err != nil { log.Fatalf("failed to write shim id: %s", err) } @@ -370,17 +401,13 @@ func bogoShim() { } func TestBogoSuite(t *testing.T) { - testenv.SkipIfShortAndSlow(t) - testenv.MustHaveExternalNetwork(t) - testenv.MustHaveGoRun(t) - testenv.MustHaveExec(t) - if testing.Short() { t.Skip("skipping in short mode") } if testenv.Builder() != "" && runtime.GOOS == "windows" { t.Skip("#66913: windows network connections are flakey on builders") } + skipFIPS(t) // In order to make Go test caching work as expected, we stat the // bogo_config.json file, so that the Go testing hooks know that it is @@ -394,18 +421,8 @@ func TestBogoSuite(t *testing.T) { if *bogoLocalDir != "" { bogoDir = *bogoLocalDir } else { - const boringsslModVer = "v0.0.0-20240523173554-273a920f84e8" - output, err := exec.Command("go", "mod", "download", "-json", "boringssl.googlesource.com/boringssl.git@"+boringsslModVer).CombinedOutput() - if err != nil { - t.Fatalf("failed to download boringssl: %s", err) - } - var j struct { - Dir string - } - if err := json.Unmarshal(output, &j); err != nil { - t.Fatalf("failed to parse 'go mod download' output: %s", err) - } - bogoDir = j.Dir + const boringsslModVer = "v0.0.0-20241120195446-5cce3fbd23e1" + bogoDir = cryptotest.FetchModule(t, "boringssl.googlesource.com/boringssl.git", boringsslModVer) } cwd, err := os.Getwd() @@ -429,11 +446,7 @@ func TestBogoSuite(t *testing.T) { args = append(args, fmt.Sprintf("-test=%s", *bogoFilter)) } - goCmd, err := testenv.GoTool() - if err != nil { - t.Fatal(err) - } - cmd := exec.Command(goCmd, args...) + cmd := testenv.Command(t, testenv.GoToolPath(t), args...) out := &strings.Builder{} cmd.Stderr = out cmd.Dir = filepath.Join(bogoDir, "ssl/test/runner") @@ -461,8 +474,8 @@ func TestBogoSuite(t *testing.T) { // are present in the output. They are only checked if -bogo-filter // was not passed. assertResults := map[string]string{ - "CurveTest-Client-Kyber-TLS13": "PASS", - "CurveTest-Server-Kyber-TLS13": "PASS", + "CurveTest-Client-MLKEM-TLS13": "PASS", + "CurveTest-Server-MLKEM-TLS13": "PASS", } for name, result := range results.Tests { diff --git a/crypto/tls/cipher_suites.go b/crypto/tls/cipher_suites.go index e80933319f1..6908a5a1f41 100644 --- a/crypto/tls/cipher_suites.go +++ b/crypto/tls/cipher_suites.go @@ -5,38 +5,40 @@ package tls import ( + "crypto" + fipsaes "crypto/internal/fips140/aes" "fmt" "hash" + "internal/cpu" "runtime" _ "unsafe" // for linkname - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/aes" "github.com/runZeroInc/excrypto/crypto/cipher" "github.com/runZeroInc/excrypto/crypto/des" "github.com/runZeroInc/excrypto/crypto/hmac" "github.com/runZeroInc/excrypto/crypto/internal/boring" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/aes/gcm" "github.com/runZeroInc/excrypto/crypto/rc4" "github.com/runZeroInc/excrypto/crypto/sha1" "github.com/runZeroInc/excrypto/crypto/sha256" - "github.com/runZeroInc/excrypto/internal/cpu" - "github.com/runZeroInc/excrypto/x/crypto/chacha20poly1305" + "golang.org/x/crypto/chacha20poly1305" ) // CipherSuite is a TLS cipher suite. Note that most functions in this package // accept and expose cipher suite IDs instead of this type. type CipherSuite struct { - ID uint16 `json:"id"` - Name string `json:"name"` + ID uint16 + Name string // Supported versions is the list of TLS protocol versions that can // negotiate this cipher suite. - SupportedVersions []uint16 `json:"supported_versions"` + SupportedVersions []uint16 // Insecure is true if the cipher suite has known security issues // due to its primitives, design, or implementation. - Insecure bool `json:"insecure"` + Insecure bool } var ( @@ -234,7 +236,7 @@ var cipherSuitesTLS13 = []*cipherSuiteTLS13{ // TODO: replace with a map. // - Anything else comes before CBC_SHA256 // // SHA-256 variants of the CBC ciphersuites don't implement any Lucky13 -// countermeasures. See http://www.isg.rhul.ac.uk/tls/Lucky13.html and +// countermeasures. See https://www.isg.rhul.ac.uk/tls/Lucky13.html and // https://www.imperialviolet.org/2013/02/04/luckythirteen.html. // // - Anything else comes before 3DES @@ -366,15 +368,13 @@ var tdesCiphers = map[uint16]bool{ } var ( - hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + // Keep in sync with crypto/internal/fips140/aes/gcm.supportsAESGCM. + hasGCMAsmAMD64 = cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ && cpu.X86.HasSSE41 && cpu.X86.HasSSSE3 hasGCMAsmARM64 = cpu.ARM64.HasAES && cpu.ARM64.HasPMULL - // Keep in sync with crypto/aes/cipher_s390x.go. - hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && - (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) + hasGCMAsmS390X = cpu.S390X.HasAES && cpu.S390X.HasAESCTR && cpu.S390X.HasGHASH + hasGCMAsmPPC64 = runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" - hasAESGCMHardwareSupport = runtime.GOARCH == "amd64" && hasGCMAsmAMD64 || - runtime.GOARCH == "arm64" && hasGCMAsmARM64 || - runtime.GOARCH == "s390x" && hasGCMAsmS390X + hasAESGCMHardwareSupport = hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X || hasGCMAsmPPC64 ) var aesgcmCiphers = map[uint16]bool{ @@ -524,7 +524,7 @@ func aeadAESGCM(key, noncePrefix []byte) aead { aead, err = boring.NewGCMTLS(aes) } else { boring.Unreachable() - aead, err = cipher.NewGCM(aes) + aead, err = gcm.NewGCMForTLS12(aes.(*fipsaes.Block)) } if err != nil { panic(err) @@ -558,7 +558,7 @@ func aeadAESGCMTLS13(key, nonceMask []byte) aead { aead, err = boring.NewGCMTLS13(aes) } else { boring.Unreachable() - aead, err = cipher.NewGCM(aes) + aead, err = gcm.NewGCMForTLS13(aes.(*fipsaes.Block)) } if err != nil { panic(err) diff --git a/crypto/tls/common.go b/crypto/tls/common.go index 659d5a424fb..34322dd213b 100644 --- a/crypto/tls/common.go +++ b/crypto/tls/common.go @@ -8,11 +8,12 @@ import ( "bytes" "container/list" "context" - "crypto/rand" + "crypto" "encoding/hex" "encoding/json" "errors" "fmt" + "internal/godebug" "io" "math/big" "net" @@ -20,18 +21,16 @@ import ( "strings" "sync" "time" + _ "unsafe" // for linkname - rtls "crypto/tls" - - "github.com/runZeroInc/excrypto/crypto" - "github.com/runZeroInc/excrypto/internal/godebug" + "crypto/rand" "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/ed25519" "github.com/runZeroInc/excrypto/crypto/elliptic" "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/crypto/sha512" - + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" "github.com/runZeroInc/excrypto/crypto/x509" ) @@ -178,17 +177,21 @@ const ( type CurveID uint16 const ( - CurveP256 CurveID = 23 - CurveP384 CurveID = 24 - CurveP521 CurveID = 25 - X25519 CurveID = 29 - - // Experimental codepoint for X25519Kyber768Draft00, specified in - // draft-tls-westerbaan-xyber768d00-03. Not exported, as support might be - // removed in the future. - x25519Kyber768Draft00 CurveID = 0x6399 // X25519Kyber768Draft00 + CurveP256 CurveID = 23 + CurveP384 CurveID = 24 + CurveP521 CurveID = 25 + X25519 CurveID = 29 + X25519MLKEM768 CurveID = 4588 ) +func isTLS13OnlyKeyExchange(curve CurveID) bool { + return curve == X25519MLKEM768 +} + +func isPQKeyExchange(curve CurveID) bool { + return curve == X25519MLKEM768 +} + // TLS 1.3 Key Share. See RFC 8446, Section 4.2.8. type keyShare struct { group CurveID @@ -696,9 +699,12 @@ type ClientHelloInfo struct { // client is using SNI (see RFC 4366, Section 3.1). ServerName string - // SupportedCurves lists the elliptic curves supported by the client. - // SupportedCurves is set only if the Supported Elliptic Curves - // Extension is being used (see RFC 4492, Section 5.1.1). + // SupportedCurves lists the key exchange mechanisms supported by the + // client. It was renamed to "supported groups" in TLS 1.3, see RFC 8446, + // Section 4.2.7 and [CurveID]. + // + // SupportedCurves may be nil in TLS 1.2 and lower if the Supported Elliptic + // Curves Extension is not being used (see RFC 4492, Section 5.1.1). SupportedCurves []CurveID // SupportedPoints lists the point formats supported by the client. @@ -726,7 +732,7 @@ type ClientHelloInfo struct { SupportedVersions []uint16 // Extensions lists the IDs of the extensions presented by the client - // in the client hello. + // in the ClientHello. Extensions []uint16 // Conn is the underlying net.Conn for the connection. Do not read @@ -1280,14 +1286,15 @@ type Config struct { // which is currently TLS 1.3. MaxVersion uint16 - // CurvePreferences contains the elliptic curves that will be used in - // an ECDHE handshake, in preference order. If empty, the default will - // be used. The client will use the first preference as the type for - // its key share in TLS 1.3. This may change in the future. + // CurvePreferences contains a set of supported key exchange mechanisms. + // The name refers to elliptic curves for legacy reasons, see [CurveID]. + // The order of the list is ignored, and key exchange mechanisms are chosen + // from this list using an internal preference order. If empty, the default + // will be used. // - // From Go 1.23, the default includes the X25519Kyber768Draft00 hybrid + // From Go 1.24, the default includes the [X25519MLKEM768] hybrid // post-quantum key exchange. To disable it, set CurvePreferences explicitly - // or use the GODEBUG=tlskyber=0 environment variable. + // or use the GODEBUG=tlsmlkem=0 environment variable. CurvePreferences []CurveID // DynamicRecordSizingDisabled disables adaptive sizing of TLS records. @@ -1377,8 +1384,10 @@ type Config struct { // EncryptedClientHelloConfigList is a serialized ECHConfigList. If // provided, clients will attempt to connect to servers using Encrypted - // Client Hello (ECH) using one of the provided ECHConfigs. Servers - // currently ignore this field. + // Client Hello (ECH) using one of the provided ECHConfigs. + // + // Servers do not use this field. In order to configure ECH for servers, see + // the EncryptedClientHelloKeys field. // // If the list contains no valid ECH configs, the handshake will fail // and return an error. @@ -1387,7 +1396,7 @@ type Config struct { // be VersionTLS13. // // When EncryptedClientHelloConfigList is set, the handshake will only - // succeed if ECH is sucessfully negotiated. If the server rejects ECH, + // succeed if ECH is successfully negotiated. If the server rejects ECH, // an ECHRejectionError error will be returned, which may contain a new // ECHConfigList that the server suggests using. // @@ -1396,9 +1405,11 @@ type Config struct { EncryptedClientHelloConfigList []byte // EncryptedClientHelloRejectionVerify, if not nil, is called when ECH is - // rejected, in order to verify the ECH provider certificate in the outer - // Client Hello. If it returns a non-nil error, the handshake is aborted and - // that error results. + // rejected by the remote server, in order to verify the ECH provider + // certificate in the outer ClientHello. If it returns a non-nil error, the + // handshake is aborted and that error results. + // + // On the server side this field is not used. // // Unlike VerifyPeerCertificate and VerifyConnection, normal certificate // verification will not be performed before calling @@ -1410,6 +1421,20 @@ type Config struct { // when ECH is rejected, even if set, and InsecureSkipVerify is ignored. EncryptedClientHelloRejectionVerify func(ConnectionState) error + // EncryptedClientHelloKeys are the ECH keys to use when a client + // attempts ECH. + // + // If EncryptedClientHelloKeys is set, MinVersion, if set, must be + // VersionTLS13. + // + // If a client attempts ECH, but it is rejected by the server, the server + // will send a list of configs to retry based on the set of + // EncryptedClientHelloKeys which have the SendAsRetry field set. + // + // On the client side, this field is ignored. In order to configure ECH for + // clients, see the EncryptedClientHelloConfigList field. + EncryptedClientHelloKeys []EncryptedClientHelloKey + // mutex protects sessionTicketKeys and autoSessionTicketKeys. mutex sync.RWMutex // sessionTicketKeys contains zero or more ticket keys. If set, it means @@ -1423,6 +1448,24 @@ type Config struct { autoSessionTicketKeys []ticketKey } +// EncryptedClientHelloKey holds a private key that is associated +// with a specific ECH config known to a client. +type EncryptedClientHelloKey struct { + // Config should be a marshalled ECHConfig associated with PrivateKey. This + // must match the config provided to clients byte-for-byte. The config + // should only specify the DHKEM(X25519, HKDF-SHA256) KEM ID (0x0020), the + // HKDF-SHA256 KDF ID (0x0001), and a subset of the following AEAD IDs: + // AES-128-GCM (0x0000), AES-256-GCM (0x0001), ChaCha20Poly1305 (0x0002). + Config []byte + // PrivateKey should be a marshalled private key. Currently, we expect + // this to be the output of [ecdh.PrivateKey.Bytes]. + PrivateKey []byte + // SendAsRetry indicates if Config should be sent as part of the list of + // retry configs when ECH is requested by the client but rejected by the + // server. + SendAsRetry bool +} + const ( // ticketKeyLifetime is how long a ticket key remains valid and can be used to // resume a client connection. @@ -1492,18 +1535,14 @@ func (c *Config) Clone() *Config { MinVersion: c.MinVersion, MaxVersion: c.MaxVersion, CurvePreferences: c.CurvePreferences, + DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, + Renegotiation: c.Renegotiation, + KeyLogWriter: c.KeyLogWriter, EncryptedClientHelloConfigList: c.EncryptedClientHelloConfigList, EncryptedClientHelloRejectionVerify: c.EncryptedClientHelloRejectionVerify, + EncryptedClientHelloKeys: c.EncryptedClientHelloKeys, sessionTicketKeys: c.sessionTicketKeys, autoSessionTicketKeys: c.autoSessionTicketKeys, - ExplicitCurvePreferences: c.ExplicitCurvePreferences, - ClientFingerprintConfiguration: c.ClientFingerprintConfiguration, - CertsOnly: c.CertsOnly, - GetCertificate: c.GetCertificate, - DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled, - VerifyPeerCertificate: c.VerifyPeerCertificate, - KeyLogWriter: c.KeyLogWriter, - Renegotiation: c.Renegotiation, } } @@ -1651,12 +1690,12 @@ func (c *Config) time() time.Time { func (c *Config) cipherSuites() []uint16 { if c.CipherSuites == nil { - if needFIPS() { + if fips140tls.Required() { return defaultCipherSuitesFIPS } return defaultCipherSuites() } - if needFIPS() { + if fips140tls.Required() { cipherSuites := slices.Clone(c.CipherSuites) return slices.DeleteFunc(cipherSuites, func(id uint16) bool { return !slices.Contains(defaultCipherSuitesFIPS, id) @@ -1682,7 +1721,7 @@ var tls10server = godebug.New("tls10server") func (c *Config) supportedVersions(isClient bool) []uint16 { versions := make([]uint16, 0, len(supportedVersions)) for _, v := range supportedVersions { - if needFIPS() && !slices.Contains(defaultSupportedVersionsFIPS, v) { + if fips140tls.Required() && !slices.Contains(defaultSupportedVersionsFIPS, v) { continue } if (c == nil || c.MinVersion == 0) && v < VersionTLS12 { @@ -1728,23 +1767,19 @@ func supportedVersionsFromMax(maxVersion uint16) []uint16 { func (c *Config) curvePreferences(version uint16) []CurveID { var curvePreferences []CurveID - if c != nil && len(c.CurvePreferences) != 0 { - curvePreferences = slices.Clone(c.CurvePreferences) - if needFIPS() { - return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { - return !slices.Contains(defaultCurvePreferencesFIPS, c) - }) - } - } else if needFIPS() { + if fips140tls.Required() { curvePreferences = slices.Clone(defaultCurvePreferencesFIPS) } else { curvePreferences = defaultCurvePreferences() } - if version < VersionTLS13 { - return slices.DeleteFunc(curvePreferences, func(c CurveID) bool { - return c == x25519Kyber768Draft00 + if c != nil && len(c.CurvePreferences) != 0 { + curvePreferences = slices.DeleteFunc(curvePreferences, func(x CurveID) bool { + return !slices.Contains(c.CurvePreferences, x) }) } + if version < VersionTLS13 { + curvePreferences = slices.DeleteFunc(curvePreferences, isTLS13OnlyKeyExchange) + } return curvePreferences } @@ -2269,7 +2304,7 @@ func unexpectedMessageError(wanted, got any) error { // supportedSignatureAlgorithms returns the supported signature algorithms. func supportedSignatureAlgorithms() []SignatureScheme { - if !needFIPS() { + if !fips140tls.Required() { return defaultSupportedSignatureAlgorithms } return defaultSupportedSignatureAlgorithmsFIPS @@ -2315,3 +2350,56 @@ type ClientCertificateRequest struct { OCSPStapling bool UnknownExtensions [][]byte } + +// fipsAllowedChains returns chains that are allowed to be used in a TLS connection +// based on the current fips140tls enforcement setting. +// +// If fips140tls is not required, the chains are returned as-is with no processing. +// Otherwise, the returned chains are filtered to only those allowed by FIPS 140-3. +// If this results in no chains it returns an error. +func fipsAllowedChains(chains [][]*x509.Certificate) ([][]*x509.Certificate, error) { + if !fips140tls.Required() { + return chains, nil + } + + permittedChains := make([][]*x509.Certificate, 0, len(chains)) + for _, chain := range chains { + if fipsAllowChain(chain) { + permittedChains = append(permittedChains, chain) + } + } + + if len(permittedChains) == 0 { + return nil, errors.New("tls: no FIPS compatible certificate chains found") + } + + return permittedChains, nil +} + +func fipsAllowChain(chain []*x509.Certificate) bool { + if len(chain) == 0 { + return false + } + + for _, cert := range chain { + if !fipsAllowCert(cert) { + return false + } + } + + return true +} + +func fipsAllowCert(c *x509.Certificate) bool { + // The key must be RSA 2048, RSA 3072, RSA 4096, + // or ECDSA P-256, P-384, P-521. + switch k := c.PublicKey.(type) { + case *rsa.PublicKey: + size := k.N.BitLen() + return size == 2048 || size == 3072 || size == 4096 + case *ecdsa.PublicKey: + return k.Curve == elliptic.P256() || k.Curve == elliptic.P384() || k.Curve == elliptic.P521() + } + + return false +} diff --git a/crypto/tls/common_string.go b/crypto/tls/common_string.go index ef60973bad1..5f588a41e02 100644 --- a/crypto/tls/common_string.go +++ b/crypto/tls/common_string.go @@ -71,13 +71,13 @@ func _() { _ = x[CurveP384-24] _ = x[CurveP521-25] _ = x[X25519-29] - _ = x[x25519Kyber768Draft00-25497] + _ = x[X25519MLKEM768-4588] } const ( _CurveID_name_0 = "CurveP256CurveP384CurveP521" _CurveID_name_1 = "X25519" - _CurveID_name_2 = "X25519Kyber768Draft00" + _CurveID_name_2 = "X25519MLKEM768" ) var ( @@ -91,7 +91,7 @@ func (i CurveID) String() string { return _CurveID_name_0[_CurveID_index_0[i]:_CurveID_index_0[i+1]] case i == 29: return _CurveID_name_1 - case i == 25497: + case i == 4588: return _CurveID_name_2 default: return "CurveID(" + strconv.FormatInt(int64(i), 10) + ")" diff --git a/crypto/tls/conn_test.go b/crypto/tls/conn_test.go index 5e090a017dc..5fd48d6bd5a 100644 --- a/crypto/tls/conn_test.go +++ b/crypto/tls/conn_test.go @@ -230,6 +230,8 @@ func runDynamicRecordSizingTest(t *testing.T, config *Config) { } func TestDynamicRecordSizingWithStreamCipher(t *testing.T) { + skipFIPS(t) // No RC4 in FIPS mode. + config := testConfig.Clone() config.MaxVersion = VersionTLS12 config.CipherSuites = []uint16{TLS_RSA_WITH_RC4_128_SHA} @@ -237,6 +239,8 @@ func TestDynamicRecordSizingWithStreamCipher(t *testing.T) { } func TestDynamicRecordSizingWithCBC(t *testing.T) { + skipFIPS(t) // No CBC cipher suites in defaultCipherSuitesFIPS. + config := testConfig.Clone() config.MaxVersion = VersionTLS12 config.CipherSuites = []uint16{TLS_RSA_WITH_AES_256_CBC_SHA} diff --git a/crypto/tls/defaults.go b/crypto/tls/defaults.go index 509a9b6c081..8054a61cbe5 100644 --- a/crypto/tls/defaults.go +++ b/crypto/tls/defaults.go @@ -14,14 +14,15 @@ import ( // Defaults are collected in this file to allow distributions to more easily patch // them to apply local policies. -var tlskyber = godebug.New("tlskyber") +var tlsmlkem = godebug.New("tlsmlkem") +// defaultCurvePreferences is the default set of supported key exchanges, as +// well as the preference order. func defaultCurvePreferences() []CurveID { - if tlskyber.Value() == "0" { + if tlsmlkem.Value() == "0" { return []CurveID{X25519, CurveP256, CurveP384, CurveP521} } - // For now, x25519Kyber768Draft00 must always be followed by X25519. - return []CurveID{x25519Kyber768Draft00, X25519, CurveP256, CurveP384, CurveP521} + return []CurveID{X25519MLKEM768, X25519, CurveP256, CurveP384, CurveP521} } // defaultSupportedSignatureAlgorithms contains the signature and hash algorithms that @@ -91,7 +92,9 @@ var defaultCipherSuitesTLS13NoAES = []uint16{ TLS_AES_256_GCM_SHA384, } -// The FIPS-only policies below match BoringSSL's ssl_policy_fips_202205. +// The FIPS-only policies below match BoringSSL's +// ssl_compliance_policy_fips_202205, which is based on NIST SP 800-52r2. +// https://cs.opensource.google/boringssl/boringssl/+/master:ssl/ssl_lib.cc;l=3289;drc=ea7a88fa var defaultSupportedVersionsFIPS = []uint16{ VersionTLS12, diff --git a/crypto/tls/ech.go b/crypto/tls/ech.go index e30329ecabc..c9ea3d71e16 100644 --- a/crypto/tls/ech.go +++ b/crypto/tls/ech.go @@ -5,13 +5,29 @@ package tls import ( + "bytes" "errors" - "github.com/runZeroInc/excrypto/crypto/internal/hpke" + "fmt" + "slices" "strings" - "github.com/runZeroInc/excrypto/x/crypto/cryptobyte" + "github.com/runZeroInc/excrypto/crypto/internal/hpke" + + "golang.org/x/crypto/cryptobyte" ) +// sortedSupportedAEADs is just a sorted version of hpke.SupportedAEADS. +// We need this so that when we insert them into ECHConfigs the ordering +// is stable. +var sortedSupportedAEADs []uint16 + +func init() { + for aeadID := range hpke.SupportedAEADs { + sortedSupportedAEADs = append(sortedSupportedAEADs, aeadID) + } + slices.Sort(sortedSupportedAEADs) +} + type echCipher struct { KDFID uint16 AEADID uint16 @@ -40,12 +56,77 @@ type echConfig struct { var errMalformedECHConfig = errors.New("tls: malformed ECHConfigList") +func parseECHConfig(enc []byte) (skip bool, ec echConfig, err error) { + s := cryptobyte.String(enc) + ec.raw = []byte(enc) + if !s.ReadUint16(&ec.Version) { + return false, echConfig{}, errMalformedECHConfig + } + if !s.ReadUint16(&ec.Length) { + return false, echConfig{}, errMalformedECHConfig + } + if len(ec.raw) < int(ec.Length)+4 { + return false, echConfig{}, errMalformedECHConfig + } + ec.raw = ec.raw[:ec.Length+4] + if ec.Version != extensionEncryptedClientHello { + s.Skip(int(ec.Length)) + return true, echConfig{}, nil + } + if !s.ReadUint8(&ec.ConfigID) { + return false, echConfig{}, errMalformedECHConfig + } + if !s.ReadUint16(&ec.KemID) { + return false, echConfig{}, errMalformedECHConfig + } + if !readUint16LengthPrefixed(&s, &ec.PublicKey) { + return false, echConfig{}, errMalformedECHConfig + } + var cipherSuites cryptobyte.String + if !s.ReadUint16LengthPrefixed(&cipherSuites) { + return false, echConfig{}, errMalformedECHConfig + } + for !cipherSuites.Empty() { + var c echCipher + if !cipherSuites.ReadUint16(&c.KDFID) { + return false, echConfig{}, errMalformedECHConfig + } + if !cipherSuites.ReadUint16(&c.AEADID) { + return false, echConfig{}, errMalformedECHConfig + } + ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c) + } + if !s.ReadUint8(&ec.MaxNameLength) { + return false, echConfig{}, errMalformedECHConfig + } + var publicName cryptobyte.String + if !s.ReadUint8LengthPrefixed(&publicName) { + return false, echConfig{}, errMalformedECHConfig + } + ec.PublicName = publicName + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) { + return false, echConfig{}, errMalformedECHConfig + } + for !extensions.Empty() { + var e echExtension + if !extensions.ReadUint16(&e.Type) { + return false, echConfig{}, errMalformedECHConfig + } + if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) { + return false, echConfig{}, errMalformedECHConfig + } + ec.Extensions = append(ec.Extensions, e) + } + + return false, ec, nil +} + // parseECHConfigList parses a draft-ietf-tls-esni-18 ECHConfigList, returning a // slice of parsed ECHConfigs, in the same order they were parsed, or an error // if the list is malformed. func parseECHConfigList(data []byte) ([]echConfig, error) { s := cryptobyte.String(data) - // Skip the length prefix var length uint16 if !s.ReadUint16(&length) { return nil, errMalformedECHConfig @@ -55,69 +136,18 @@ func parseECHConfigList(data []byte) ([]echConfig, error) { } var configs []echConfig for len(s) > 0 { - var ec echConfig - ec.raw = []byte(s) - if !s.ReadUint16(&ec.Version) { - return nil, errMalformedECHConfig + if len(s) < 4 { + return nil, errors.New("tls: malformed ECHConfig") } - if !s.ReadUint16(&ec.Length) { - return nil, errMalformedECHConfig + configLen := uint16(s[2])<<8 | uint16(s[3]) + skip, ec, err := parseECHConfig(s) + if err != nil { + return nil, err } - if len(ec.raw) < int(ec.Length)+4 { - return nil, errMalformedECHConfig + s = s[configLen+4:] + if !skip { + configs = append(configs, ec) } - ec.raw = ec.raw[:ec.Length+4] - if ec.Version != extensionEncryptedClientHello { - s.Skip(int(ec.Length)) - continue - } - if !s.ReadUint8(&ec.ConfigID) { - return nil, errMalformedECHConfig - } - if !s.ReadUint16(&ec.KemID) { - return nil, errMalformedECHConfig - } - if !s.ReadUint16LengthPrefixed((*cryptobyte.String)(&ec.PublicKey)) { - return nil, errMalformedECHConfig - } - var cipherSuites cryptobyte.String - if !s.ReadUint16LengthPrefixed(&cipherSuites) { - return nil, errMalformedECHConfig - } - for !cipherSuites.Empty() { - var c echCipher - if !cipherSuites.ReadUint16(&c.KDFID) { - return nil, errMalformedECHConfig - } - if !cipherSuites.ReadUint16(&c.AEADID) { - return nil, errMalformedECHConfig - } - ec.SymmetricCipherSuite = append(ec.SymmetricCipherSuite, c) - } - if !s.ReadUint8(&ec.MaxNameLength) { - return nil, errMalformedECHConfig - } - var publicName cryptobyte.String - if !s.ReadUint8LengthPrefixed(&publicName) { - return nil, errMalformedECHConfig - } - ec.PublicName = publicName - var extensions cryptobyte.String - if !s.ReadUint16LengthPrefixed(&extensions) { - return nil, errMalformedECHConfig - } - for !extensions.Empty() { - var e echExtension - if !extensions.ReadUint16(&e.Type) { - return nil, errMalformedECHConfig - } - if !extensions.ReadUint16LengthPrefixed((*cryptobyte.String)(&e.Data)) { - return nil, errMalformedECHConfig - } - ec.Extensions = append(ec.Extensions, e) - } - - configs = append(configs, ec) } return configs, nil } @@ -195,6 +225,175 @@ func encodeInnerClientHello(inner *clientHelloMsg, maxNameLength int) ([]byte, e return append(h, make([]byte, paddingLen)...), nil } +func skipUint8LengthPrefixed(s *cryptobyte.String) bool { + var skip uint8 + if !s.ReadUint8(&skip) { + return false + } + return s.Skip(int(skip)) +} + +func skipUint16LengthPrefixed(s *cryptobyte.String) bool { + var skip uint16 + if !s.ReadUint16(&skip) { + return false + } + return s.Skip(int(skip)) +} + +type rawExtension struct { + extType uint16 + data []byte +} + +func extractRawExtensions(hello *clientHelloMsg) ([]rawExtension, error) { + s := cryptobyte.String(hello.original) + if !s.Skip(4+2+32) || // header, version, random + !skipUint8LengthPrefixed(&s) || // session ID + !skipUint16LengthPrefixed(&s) || // cipher suites + !skipUint8LengthPrefixed(&s) { // compression methods + return nil, errors.New("tls: malformed outer client hello") + } + var rawExtensions []rawExtension + var extensions cryptobyte.String + if !s.ReadUint16LengthPrefixed(&extensions) { + return nil, errors.New("tls: malformed outer client hello") + } + + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + return nil, errors.New("tls: invalid inner client hello") + } + rawExtensions = append(rawExtensions, rawExtension{extension, extData}) + } + return rawExtensions, nil +} + +func decodeInnerClientHello(outer *clientHelloMsg, encoded []byte) (*clientHelloMsg, error) { + // Reconstructing the inner client hello from its encoded form is somewhat + // complicated. It is missing its header (message type and length), session + // ID, and the extensions may be compressed. Since we need to put the + // extensions back in the same order as they were in the raw outer hello, + // and since we don't store the raw extensions, or the order we parsed them + // in, we need to reparse the raw extensions from the outer hello in order + // to properly insert them into the inner hello. This _should_ result in raw + // bytes which match the hello as it was generated by the client. + innerReader := cryptobyte.String(encoded) + var versionAndRandom, sessionID, cipherSuites, compressionMethods []byte + var extensions cryptobyte.String + if !innerReader.ReadBytes(&versionAndRandom, 2+32) || + !readUint8LengthPrefixed(&innerReader, &sessionID) || + len(sessionID) != 0 || + !readUint16LengthPrefixed(&innerReader, &cipherSuites) || + !readUint8LengthPrefixed(&innerReader, &compressionMethods) || + !innerReader.ReadUint16LengthPrefixed(&extensions) { + return nil, errors.New("tls: invalid inner client hello") + } + + // The specification says we must verify that the trailing padding is all + // zeros. This is kind of weird for TLS messages, where we generally just + // throw away any trailing garbage. + for _, p := range innerReader { + if p != 0 { + return nil, errors.New("tls: invalid inner client hello") + } + } + + rawOuterExts, err := extractRawExtensions(outer) + if err != nil { + return nil, err + } + + recon := cryptobyte.NewBuilder(nil) + recon.AddUint8(typeClientHello) + recon.AddUint24LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(versionAndRandom) + recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(outer.sessionId) + }) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(cipherSuites) + }) + recon.AddUint8LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(compressionMethods) + }) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + for !extensions.Empty() { + var extension uint16 + var extData cryptobyte.String + if !extensions.ReadUint16(&extension) || + !extensions.ReadUint16LengthPrefixed(&extData) { + recon.SetError(errors.New("tls: invalid inner client hello")) + return + } + if extension == extensionECHOuterExtensions { + if !extData.ReadUint8LengthPrefixed(&extData) { + recon.SetError(errors.New("tls: invalid inner client hello")) + return + } + var i int + for !extData.Empty() { + var extType uint16 + if !extData.ReadUint16(&extType) { + recon.SetError(errors.New("tls: invalid inner client hello")) + return + } + if extType == extensionEncryptedClientHello { + recon.SetError(errors.New("tls: invalid outer extensions")) + return + } + for ; i <= len(rawOuterExts); i++ { + if i == len(rawOuterExts) { + recon.SetError(errors.New("tls: invalid outer extensions")) + return + } + if rawOuterExts[i].extType == extType { + break + } + } + recon.AddUint16(rawOuterExts[i].extType) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(rawOuterExts[i].data) + }) + } + } else { + recon.AddUint16(extension) + recon.AddUint16LengthPrefixed(func(recon *cryptobyte.Builder) { + recon.AddBytes(extData) + }) + } + } + }) + }) + + reconBytes, err := recon.Bytes() + if err != nil { + return nil, err + } + inner := &clientHelloMsg{} + if !inner.unmarshal(reconBytes) { + return nil, errors.New("tls: invalid reconstructed inner client hello") + } + + if !bytes.Equal(inner.encryptedClientHello, []byte{uint8(innerECHExt)}) { + return nil, errInvalidECHExt + } + + if len(inner.supportedVersions) != 1 || (len(inner.supportedVersions) >= 1 && inner.supportedVersions[0] != VersionTLS13) { + return nil, errors.New("tls: client sent encrypted_client_hello extension and offered incompatible versions") + } + + return inner, nil +} + +func decryptECHPayload(context *hpke.Receipient, hello, payload []byte) ([]byte, error) { + outerAAD := bytes.Replace(hello[4:], payload, make([]byte, len(payload)), 1) + return context.Open(outerAAD, payload) +} + func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payload []byte) ([]byte, error) { var b cryptobyte.Builder b.AddUint8(0) // outer @@ -206,7 +405,7 @@ func generateOuterECHExt(id uint8, kdfID, aeadID uint16, encodedKey []byte, payl return b.Bytes() } -func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echContext, useKey bool) error { +func computeAndUpdateOuterECHExtension(outer, inner *clientHelloMsg, ech *echClientContext, useKey bool) error { var encapKey []byte if useKey { encapKey = ech.encapsulatedKey @@ -281,3 +480,159 @@ type ECHRejectionError struct { func (e *ECHRejectionError) Error() string { return "tls: server rejected ECH" } + +var errMalformedECHExt = errors.New("tls: malformed encrypted_client_hello extension") +var errInvalidECHExt = errors.New("tls: client sent invalid encrypted_client_hello extension") + +type echExtType uint8 + +const ( + innerECHExt echExtType = 1 + outerECHExt echExtType = 0 +) + +func parseECHExt(ext []byte) (echType echExtType, cs echCipher, configID uint8, encap []byte, payload []byte, err error) { + data := make([]byte, len(ext)) + copy(data, ext) + s := cryptobyte.String(data) + var echInt uint8 + if !s.ReadUint8(&echInt) { + err = errMalformedECHExt + return + } + echType = echExtType(echInt) + if echType == innerECHExt { + if !s.Empty() { + err = errMalformedECHExt + return + } + return echType, cs, 0, nil, nil, nil + } + if echType != outerECHExt { + err = errInvalidECHExt + return + } + if !s.ReadUint16(&cs.KDFID) { + err = errMalformedECHExt + return + } + if !s.ReadUint16(&cs.AEADID) { + err = errMalformedECHExt + return + } + if !s.ReadUint8(&configID) { + err = errMalformedECHExt + return + } + if !readUint16LengthPrefixed(&s, &encap) { + err = errMalformedECHExt + return + } + if !readUint16LengthPrefixed(&s, &payload) { + err = errMalformedECHExt + return + } + + // NOTE: clone encap and payload so that mutating them does not mutate the + // raw extension bytes. + return echType, cs, configID, bytes.Clone(encap), bytes.Clone(payload), nil +} + +func marshalEncryptedClientHelloConfigList(configs []EncryptedClientHelloKey) ([]byte, error) { + builder := cryptobyte.NewBuilder(nil) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + for _, c := range configs { + builder.AddBytes(c.Config) + } + }) + return builder.Bytes() +} + +func (c *Conn) processECHClientHello(outer *clientHelloMsg) (*clientHelloMsg, *echServerContext, error) { + echType, echCiphersuite, configID, encap, payload, err := parseECHExt(outer.encryptedClientHello) + if err != nil { + if errors.Is(err, errInvalidECHExt) { + c.sendAlert(alertIllegalParameter) + } else { + c.sendAlert(alertDecodeError) + } + + return nil, nil, errInvalidECHExt + } + + if echType == innerECHExt { + return outer, &echServerContext{inner: true}, nil + } + + if len(c.config.EncryptedClientHelloKeys) == 0 { + return outer, nil, nil + } + + for _, echKey := range c.config.EncryptedClientHelloKeys { + skip, config, err := parseECHConfig(echKey.Config) + if err != nil || skip { + c.sendAlert(alertInternalError) + return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys Config: %s", err) + } + if skip { + continue + } + echPriv, err := hpke.ParseHPKEPrivateKey(config.KemID, echKey.PrivateKey) + if err != nil { + c.sendAlert(alertInternalError) + return nil, nil, fmt.Errorf("tls: invalid EncryptedClientHelloKeys PrivateKey: %s", err) + } + info := append([]byte("tls ech\x00"), echKey.Config...) + hpkeContext, err := hpke.SetupReceipient(hpke.DHKEM_X25519_HKDF_SHA256, echCiphersuite.KDFID, echCiphersuite.AEADID, echPriv, info, encap) + if err != nil { + // attempt next trial decryption + continue + } + + encodedInner, err := decryptECHPayload(hpkeContext, outer.original, payload) + if err != nil { + // attempt next trial decryption + continue + } + + // NOTE: we do not enforce that the sent server_name matches the ECH + // configs PublicName, since this is not particularly important, and + // the client already had to know what it was in order to properly + // encrypt the payload. This is only a MAY in the spec, so we're not + // doing anything revolutionary. + + echInner, err := decodeInnerClientHello(outer, encodedInner) + if err != nil { + c.sendAlert(alertIllegalParameter) + return nil, nil, errInvalidECHExt + } + + c.echAccepted = true + + return echInner, &echServerContext{ + hpkeContext: hpkeContext, + configID: configID, + ciphersuite: echCiphersuite, + }, nil + } + + return outer, nil, nil +} + +func buildRetryConfigList(keys []EncryptedClientHelloKey) ([]byte, error) { + var atLeastOneRetryConfig bool + var retryBuilder cryptobyte.Builder + retryBuilder.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) { + for _, c := range keys { + if !c.SendAsRetry { + continue + } + atLeastOneRetryConfig = true + b.AddBytes(c.Config) + } + }) + if !atLeastOneRetryConfig { + return nil, nil + } + return retryBuilder.Bytes() +} diff --git a/crypto/tls/fips_test.go b/crypto/tls/fips_test.go new file mode 100644 index 00000000000..ef355090b58 --- /dev/null +++ b/crypto/tls/fips_test.go @@ -0,0 +1,675 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls + +import ( + "encoding/pem" + "fmt" + "internal/obscuretestdata" + "internal/testenv" + "math/big" + "net" + "runtime" + "strings" + "testing" + "time" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/ecdsa" + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/rsa" + "github.com/runZeroInc/excrypto/crypto/x509" + "github.com/runZeroInc/excrypto/crypto/x509/pkix" +) + +func allCipherSuitesIncludingTLS13() []uint16 { + s := allCipherSuites() + for _, suite := range cipherSuitesTLS13 { + s = append(s, suite.id) + } + return s +} + +func isTLS13CipherSuite(id uint16) bool { + for _, suite := range cipherSuitesTLS13 { + if id == suite.id { + return true + } + } + return false +} + +func generateKeyShare(group CurveID) keyShare { + key, err := generateECDHEKey(rand.Reader, group) + if err != nil { + panic(err) + } + return keyShare{group: group, data: key.PublicKey().Bytes()} +} + +func TestFIPSServerProtocolVersion(t *testing.T) { + test := func(t *testing.T, name string, v uint16, msg string) { + t.Run(name, func(t *testing.T) { + serverConfig := testConfig.Clone() + serverConfig.MinVersion = VersionSSL30 + clientConfig := testConfig.Clone() + clientConfig.MinVersion = v + clientConfig.MaxVersion = v + _, _, err := testHandshake(t, clientConfig, serverConfig) + if msg == "" { + if err != nil { + t.Fatalf("got error: %v, expected success", err) + } + } else { + if err == nil { + t.Fatalf("got success, expected error") + } + if !strings.Contains(err.Error(), msg) { + t.Fatalf("got error %v, expected %q", err, msg) + } + } + }) + } + + runWithFIPSDisabled(t, func(t *testing.T) { + test(t, "VersionTLS10", VersionTLS10, "") + test(t, "VersionTLS11", VersionTLS11, "") + test(t, "VersionTLS12", VersionTLS12, "") + test(t, "VersionTLS13", VersionTLS13, "") + }) + + runWithFIPSEnabled(t, func(t *testing.T) { + test(t, "VersionTLS10", VersionTLS10, "supported versions") + test(t, "VersionTLS11", VersionTLS11, "supported versions") + test(t, "VersionTLS12", VersionTLS12, "") + test(t, "VersionTLS13", VersionTLS13, "") + }) +} + +func isFIPSVersion(v uint16) bool { + return v == VersionTLS12 || v == VersionTLS13 +} + +func isFIPSCipherSuite(id uint16) bool { + switch id { + case TLS_AES_128_GCM_SHA256, + TLS_AES_256_GCM_SHA384, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: + return true + } + return false +} + +func isFIPSCurve(id CurveID) bool { + switch id { + case CurveP256, CurveP384: + return true + } + return false +} + +func isECDSA(id uint16) bool { + for _, suite := range cipherSuites { + if suite.id == id { + return suite.flags&suiteECSign == suiteECSign + } + } + return false // TLS 1.3 cipher suites are not tied to the signature algorithm. +} + +func isFIPSSignatureScheme(alg SignatureScheme) bool { + switch alg { + default: + return false + case PKCS1WithSHA256, + ECDSAWithP256AndSHA256, + PKCS1WithSHA384, + ECDSAWithP384AndSHA384, + PKCS1WithSHA512, + PSSWithSHA256, + PSSWithSHA384, + PSSWithSHA512: + // ok + } + return true +} + +func TestFIPSServerCipherSuites(t *testing.T) { + serverConfig := testConfig.Clone() + serverConfig.Certificates = make([]Certificate, 1) + + for _, id := range allCipherSuitesIncludingTLS13() { + if isECDSA(id) { + serverConfig.Certificates[0].Certificate = [][]byte{testECDSACertificate} + serverConfig.Certificates[0].PrivateKey = testECDSAPrivateKey + } else { + serverConfig.Certificates[0].Certificate = [][]byte{testRSACertificate} + serverConfig.Certificates[0].PrivateKey = testRSAPrivateKey + } + serverConfig.BuildNameToCertificate() + t.Run(fmt.Sprintf("suite=%s", CipherSuiteName(id)), func(t *testing.T) { + clientHello := &clientHelloMsg{ + vers: VersionTLS12, + random: make([]byte, 32), + cipherSuites: []uint16{id}, + compressionMethods: []uint8{compressionNone}, + supportedCurves: defaultCurvePreferences(), + keyShares: []keyShare{generateKeyShare(CurveP256)}, + supportedPoints: []uint8{pointFormatUncompressed}, + supportedVersions: []uint16{VersionTLS12}, + supportedSignatureAlgorithms: defaultSupportedSignatureAlgorithmsFIPS, + } + if isTLS13CipherSuite(id) { + clientHello.supportedVersions = []uint16{VersionTLS13} + } + + runWithFIPSDisabled(t, func(t *testing.T) { + testClientHello(t, serverConfig, clientHello) + }) + + runWithFIPSEnabled(t, func(t *testing.T) { + msg := "" + if !isFIPSCipherSuite(id) { + msg = "no cipher suite supported by both client and server" + } + testClientHelloFailure(t, serverConfig, clientHello, msg) + }) + }) + } +} + +func TestFIPSServerCurves(t *testing.T) { + serverConfig := testConfig.Clone() + serverConfig.CurvePreferences = nil + serverConfig.BuildNameToCertificate() + + for _, curveid := range defaultCurvePreferences() { + t.Run(fmt.Sprintf("curve=%d", curveid), func(t *testing.T) { + clientConfig := testConfig.Clone() + clientConfig.CurvePreferences = []CurveID{curveid} + + runWithFIPSDisabled(t, func(t *testing.T) { + if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil { + t.Fatalf("got error: %v, expected success", err) + } + }) + + // With fipstls forced, bad curves should be rejected. + runWithFIPSEnabled(t, func(t *testing.T) { + _, _, err := testHandshake(t, clientConfig, serverConfig) + if err != nil && isFIPSCurve(curveid) { + t.Fatalf("got error: %v, expected success", err) + } else if err == nil && !isFIPSCurve(curveid) { + t.Fatalf("got success, expected error") + } + }) + }) + } +} + +func fipsHandshake(t *testing.T, clientConfig, serverConfig *Config) (clientErr, serverErr error) { + c, s := localPipe(t) + client := Client(c, clientConfig) + server := Server(s, serverConfig) + done := make(chan error, 1) + go func() { + done <- client.Handshake() + c.Close() + }() + serverErr = server.Handshake() + s.Close() + clientErr = <-done + return +} + +func TestFIPSServerSignatureAndHash(t *testing.T) { + defer func() { + testingOnlyForceClientHelloSignatureAlgorithms = nil + }() + + for _, sigHash := range defaultSupportedSignatureAlgorithms { + t.Run(fmt.Sprintf("%v", sigHash), func(t *testing.T) { + serverConfig := testConfig.Clone() + serverConfig.Certificates = make([]Certificate, 1) + + testingOnlyForceClientHelloSignatureAlgorithms = []SignatureScheme{sigHash} + + sigType, _, _ := typeAndHashFromSignatureScheme(sigHash) + switch sigType { + case signaturePKCS1v15, signatureRSAPSS: + serverConfig.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} + serverConfig.Certificates[0].Certificate = [][]byte{testRSAPSS2048Certificate} + serverConfig.Certificates[0].PrivateKey = testRSAPSS2048PrivateKey + case signatureEd25519: + serverConfig.CipherSuites = []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256} + serverConfig.Certificates[0].Certificate = [][]byte{testEd25519Certificate} + serverConfig.Certificates[0].PrivateKey = testEd25519PrivateKey + case signatureECDSA: + serverConfig.CipherSuites = []uint16{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256} + serverConfig.Certificates[0].Certificate = [][]byte{testECDSACertificate} + serverConfig.Certificates[0].PrivateKey = testECDSAPrivateKey + } + serverConfig.BuildNameToCertificate() + // PKCS#1 v1.5 signature algorithms can't be used standalone in TLS + // 1.3, and the ECDSA ones bind to the curve used. + serverConfig.MaxVersion = VersionTLS12 + + runWithFIPSDisabled(t, func(t *testing.T) { + clientErr, serverErr := fipsHandshake(t, testConfig, serverConfig) + if clientErr != nil { + t.Fatalf("expected handshake with %#x to succeed; client error: %v; server error: %v", sigHash, clientErr, serverErr) + } + }) + + // With fipstls forced, bad curves should be rejected. + runWithFIPSEnabled(t, func(t *testing.T) { + clientErr, _ := fipsHandshake(t, testConfig, serverConfig) + if isFIPSSignatureScheme(sigHash) { + if clientErr != nil { + t.Fatalf("expected handshake with %#x to succeed; err=%v", sigHash, clientErr) + } + } else { + if clientErr == nil { + t.Fatalf("expected handshake with %#x to fail, but it succeeded", sigHash) + } + } + }) + }) + } +} + +func TestFIPSClientHello(t *testing.T) { + runWithFIPSEnabled(t, testFIPSClientHello) +} + +func testFIPSClientHello(t *testing.T) { + // Test that no matter what we put in the client config, + // the client does not offer non-FIPS configurations. + + c, s := net.Pipe() + defer c.Close() + defer s.Close() + + clientConfig := testConfig.Clone() + // All sorts of traps for the client to avoid. + clientConfig.MinVersion = VersionSSL30 + clientConfig.MaxVersion = VersionTLS13 + clientConfig.CipherSuites = allCipherSuites() + clientConfig.CurvePreferences = defaultCurvePreferences() + + go Client(c, clientConfig).Handshake() + srv := Server(s, testConfig) + msg, err := srv.readHandshake(nil) + if err != nil { + t.Fatal(err) + } + hello, ok := msg.(*clientHelloMsg) + if !ok { + t.Fatalf("unexpected message type %T", msg) + } + + if !isFIPSVersion(hello.vers) { + t.Errorf("client vers=%#x", hello.vers) + } + for _, v := range hello.supportedVersions { + if !isFIPSVersion(v) { + t.Errorf("client offered disallowed version %#x", v) + } + } + for _, id := range hello.cipherSuites { + if !isFIPSCipherSuite(id) { + t.Errorf("client offered disallowed suite %#x", id) + } + } + for _, id := range hello.supportedCurves { + if !isFIPSCurve(id) { + t.Errorf("client offered disallowed curve %d", id) + } + } + for _, sigHash := range hello.supportedSignatureAlgorithms { + if !isFIPSSignatureScheme(sigHash) { + t.Errorf("client offered disallowed signature-and-hash %v", sigHash) + } + } +} + +func TestFIPSCertAlgs(t *testing.T) { + // arm and wasm time out generating keys. Nothing in this test is + // architecture-specific, so just don't bother on those. + if testenv.CPUIsSlow() { + t.Skipf("skipping on %s/%s because key generation takes too long", runtime.GOOS, runtime.GOARCH) + } + + // Set up some roots, intermediate CAs, and leaf certs with various algorithms. + // X_Y is X signed by Y. + R1 := fipsCert(t, "R1", fipsRSAKey(t, 2048), nil, fipsCertCA|fipsCertFIPSOK) + R2 := fipsCert(t, "R2", fipsRSAKey(t, 1024), nil, fipsCertCA) + R3 := fipsCert(t, "R3", fipsRSAKey(t, 4096), nil, fipsCertCA|fipsCertFIPSOK) + + M1_R1 := fipsCert(t, "M1_R1", fipsECDSAKey(t, elliptic.P256()), R1, fipsCertCA|fipsCertFIPSOK) + M2_R1 := fipsCert(t, "M2_R1", fipsECDSAKey(t, elliptic.P224()), R1, fipsCertCA) + + I_R1 := fipsCert(t, "I_R1", fipsRSAKey(t, 3072), R1, fipsCertCA|fipsCertFIPSOK) + I_R2 := fipsCert(t, "I_R2", I_R1.key, R2, fipsCertCA|fipsCertFIPSOK) + I_M1 := fipsCert(t, "I_M1", I_R1.key, M1_R1, fipsCertCA|fipsCertFIPSOK) + I_M2 := fipsCert(t, "I_M2", I_R1.key, M2_R1, fipsCertCA|fipsCertFIPSOK) + + I_R3 := fipsCert(t, "I_R3", fipsRSAKey(t, 3072), R3, fipsCertCA|fipsCertFIPSOK) + fipsCert(t, "I_R3", I_R3.key, R3, fipsCertCA|fipsCertFIPSOK) + + L1_I := fipsCert(t, "L1_I", fipsECDSAKey(t, elliptic.P384()), I_R1, fipsCertLeaf|fipsCertFIPSOK) + L2_I := fipsCert(t, "L2_I", fipsRSAKey(t, 1024), I_R1, fipsCertLeaf) + + // client verifying server cert + testServerCert := func(t *testing.T, desc string, pool *x509.CertPool, key interface{}, list [][]byte, ok bool) { + clientConfig := testConfig.Clone() + clientConfig.RootCAs = pool + clientConfig.InsecureSkipVerify = false + clientConfig.ServerName = "example.com" + + serverConfig := testConfig.Clone() + serverConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}} + serverConfig.BuildNameToCertificate() + + clientErr, _ := fipsHandshake(t, clientConfig, serverConfig) + + if (clientErr == nil) == ok { + if ok { + t.Logf("%s: accept", desc) + } else { + t.Logf("%s: reject", desc) + } + } else { + if ok { + t.Errorf("%s: BAD reject (%v)", desc, clientErr) + } else { + t.Errorf("%s: BAD accept", desc) + } + } + } + + // server verifying client cert + testClientCert := func(t *testing.T, desc string, pool *x509.CertPool, key interface{}, list [][]byte, ok bool) { + clientConfig := testConfig.Clone() + clientConfig.ServerName = "example.com" + clientConfig.Certificates = []Certificate{{Certificate: list, PrivateKey: key}} + + serverConfig := testConfig.Clone() + serverConfig.ClientCAs = pool + serverConfig.ClientAuth = RequireAndVerifyClientCert + + _, serverErr := fipsHandshake(t, clientConfig, serverConfig) + + if (serverErr == nil) == ok { + if ok { + t.Logf("%s: accept", desc) + } else { + t.Logf("%s: reject", desc) + } + } else { + if ok { + t.Errorf("%s: BAD reject (%v)", desc, serverErr) + } else { + t.Errorf("%s: BAD accept", desc) + } + } + } + + // Run simple basic test with known answers before proceeding to + // exhaustive test with computed answers. + r1pool := x509.NewCertPool() + r1pool.AddCert(R1.cert) + + runWithFIPSDisabled(t, func(t *testing.T) { + testServerCert(t, "basic", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true) + testClientCert(t, "basic (client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, true) + }) + + runWithFIPSEnabled(t, func(t *testing.T) { + testServerCert(t, "basic (fips)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false) + testClientCert(t, "basic (fips, client cert)", r1pool, L2_I.key, [][]byte{L2_I.der, I_R1.der}, false) + }) + + if t.Failed() { + t.Fatal("basic test failed, skipping exhaustive test") + } + + if testing.Short() { + t.Logf("basic test passed; skipping exhaustive test in -short mode") + return + } + + for l := 1; l <= 2; l++ { + leaf := L1_I + if l == 2 { + leaf = L2_I + } + for i := 0; i < 64; i++ { + reachable := map[string]bool{leaf.parentOrg: true} + reachableFIPS := map[string]bool{leaf.parentOrg: leaf.fipsOK} + list := [][]byte{leaf.der} + listName := leaf.name + addList := func(cond int, c *fipsCertificate) { + if cond != 0 { + list = append(list, c.der) + listName += "," + c.name + if reachable[c.org] { + reachable[c.parentOrg] = true + } + if reachableFIPS[c.org] && c.fipsOK { + reachableFIPS[c.parentOrg] = true + } + } + } + addList(i&1, I_R1) + addList(i&2, I_R2) + addList(i&4, I_M1) + addList(i&8, I_M2) + addList(i&16, M1_R1) + addList(i&32, M2_R1) + + for r := 1; r <= 3; r++ { + pool := x509.NewCertPool() + rootName := "," + shouldVerify := false + shouldVerifyFIPS := false + addRoot := func(cond int, c *fipsCertificate) { + if cond != 0 { + rootName += "," + c.name + pool.AddCert(c.cert) + if reachable[c.org] { + shouldVerify = true + } + if reachableFIPS[c.org] && c.fipsOK { + shouldVerifyFIPS = true + } + } + } + addRoot(r&1, R1) + addRoot(r&2, R2) + rootName = rootName[1:] // strip leading comma + + runWithFIPSDisabled(t, func(t *testing.T) { + testServerCert(t, listName+"->"+rootName[1:], pool, leaf.key, list, shouldVerify) + testClientCert(t, listName+"->"+rootName[1:]+"(client cert)", pool, leaf.key, list, shouldVerify) + }) + + runWithFIPSEnabled(t, func(t *testing.T) { + testServerCert(t, listName+"->"+rootName[1:]+" (fips)", pool, leaf.key, list, shouldVerifyFIPS) + testClientCert(t, listName+"->"+rootName[1:]+" (fips, client cert)", pool, leaf.key, list, shouldVerifyFIPS) + }) + } + } + } +} + +const ( + fipsCertCA = iota + fipsCertLeaf + fipsCertFIPSOK = 0x80 +) + +func fipsRSAKey(t *testing.T, size int) *rsa.PrivateKey { + k, err := rsa.GenerateKey(rand.Reader, size) + if err != nil { + t.Fatal(err) + } + return k +} + +func fipsECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey { + k, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + t.Fatal(err) + } + return k +} + +type fipsCertificate struct { + name string + org string + parentOrg string + der []byte + cert *x509.Certificate + key interface{} + fipsOK bool +} + +func fipsCert(t *testing.T, name string, key interface{}, parent *fipsCertificate, mode int) *fipsCertificate { + org := name + parentOrg := "" + if i := strings.Index(org, "_"); i >= 0 { + org = org[:i] + parentOrg = name[i+1:] + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{org}, + }, + NotBefore: time.Unix(0, 0), + NotAfter: time.Unix(0, 0), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + } + if mode&^fipsCertFIPSOK == fipsCertLeaf { + tmpl.DNSNames = []string{"example.com"} + } else { + tmpl.IsCA = true + tmpl.KeyUsage |= x509.KeyUsageCertSign + } + + var pcert *x509.Certificate + var pkey interface{} + if parent != nil { + pcert = parent.cert + pkey = parent.key + } else { + pcert = tmpl + pkey = key + } + + var pub interface{} + var desc string + switch k := key.(type) { + case *rsa.PrivateKey: + pub = &k.PublicKey + desc = fmt.Sprintf("RSA-%d", k.N.BitLen()) + case *ecdsa.PrivateKey: + pub = &k.PublicKey + desc = "ECDSA-" + k.Curve.Params().Name + default: + t.Fatalf("invalid key %T", key) + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, pcert, pub, pkey) + if err != nil { + t.Fatal(err) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + t.Fatal(err) + } + + fipsOK := mode&fipsCertFIPSOK != 0 + runWithFIPSEnabled(t, func(t *testing.T) { + if fipsAllowCert(cert) != fipsOK { + t.Errorf("fipsAllowCert(cert with %s key) = %v, want %v", desc, !fipsOK, fipsOK) + } + }) + + return &fipsCertificate{name, org, parentOrg, der, cert, key, fipsOK} +} + +// A self-signed test certificate with an RSA key of size 2048, for testing +// RSA-PSS with SHA512. SAN of example.golang. +var ( + testRSAPSS2048Certificate []byte + testRSAPSS2048PrivateKey *rsa.PrivateKey +) + +func init() { + block, _ := pem.Decode(obscuretestdata.Rot13([]byte(` +-----ORTVA PREGVSVPNGR----- +ZVVP/mPPNrrtNjVONtVENYUUK/xu4+4mZH9QnemORpDjQDLWXbMVuipANDRYODNj +RwRDZN4TN1HRPuZUDJAgMFOQomNrSj0kZGNkZQRkAGN0ZQInSj0lZQRlZwxkAGN0 +ZQInZOVkRQNBOtAIONbGO0SwoJHtD28jttRvZN0TPFdTFVo3QDRONDHNN4VOQjNj +ttRXNbVONDPs8sx0A6vrPOK4VBIVsXvgg4xTpBDYrvzPsfwddUplfZVITRgSFZ6R +4Nl141s/7VdqJ0HgVdAo4CKuEBVQ7lQkE284kY6KoPhi/g5uC3HpruLp3uzYvlIq +ZxMDvMJgsHHWs/1dBgZ+buAt59YEJc4q+6vK0yn1WY3RjPVpxxAwW9uDoS7Co2PF ++RF9Lb55XNnc8XBoycpE8ZOFA38odajwsDqPKiBRBwnz2UHkXmRSK5ZN+sN0zr4P +vbPpPEYJXy+TbA9S8sNOsbM+G+2rny4QYhB95eKE8FeBVIOu3KSBe/EIuwgKpAIS +MXpiQg6q68I6wNXNLXz5ayw9TCcq4i+eNtZONNTwHQOBZN4TN1HqQjRO/jDRNjVS +bQNGOtAIUFHRQQNXOtteOtRSODpQNGNZOtAIUEZONs8RNwNNZOxTN1HqRDDFZOPP +QzI4LJ1joTHhM29fLJ5aZN0TPFdTFVo3QDROPjHNN4VONDPBbLfIpSPOuobdr3JU +qP6I7KKKRPzawu01e8u80li0AE379aFQ3pj2Z+UXinKlfJdey5uwTIXj0igjQ81e +I4WmQh7VsVbt5z8+DAP+7YdQMfm88iQXBefblFIBzHPtzPXSKrj+YN+rB/vDRWGe +7rafqqBrKWRc27Rq5iJ+xzJJ3Dztyp2Tjl8jSeZQVdaeaBmON4bPaQRtgKWg0mbt +aEjosRZNJv1nDEl5qG9XN3FC9zb5FrGSFmTTUvR4f4tUHr7wifNSS2dtgQ6+jU6f +m9o6fukaP7t5VyOXuV7FIO/Hdg2lqW+xU1LowZpVd6ANZ5rAZXtMhWe3+mjfFtju +TAnR +-----RAQ PREGVSVPNGR-----`))) + testRSAPSS2048Certificate = block.Bytes + + block, _ = pem.Decode(obscuretestdata.Rot13([]byte(` +-----ORTVA EFN CEVINGR XRL----- +ZVVRcNVONNXPNDRNa/U5AQrbattI+PQyFUlbeorWOaQxP3bcta7V6du3ZeQPSEuY +EHwBuBNZgrAK/+lXaIgSYFXwJ+Q14HGvN+8t8HqiBZF+y2jee/7rLG91UUbJUA4M +v4fyKGWTHVzIeK1SPK/9nweGCdVGLBsF0IdrUshby9WJgFF9kZNvUWWQLlsLHTkr +m29txiuRiJXBrFtTdsPwz5nKRsQNHwq/T6c8V30UDy7muQb2cgu1ZFfkOI+GNCaj +AWahNbdNaNxF1vcsudQsEsUjNK6Tsx/gazcrNl7wirn10sRdmvSDLq1kGd/0ILL7 +I3QIEJFaYj7rariSrbjPtTPchM5L/Ew6KrY/djVQNDNONbVONDPAcZMvsq/it42u +UqPiYhMnLF0E7FhaSycbKRfygTqYSfac0VsbWM/htSDOFNVVsYjZhzH6bKN1m7Hi +98nVLI61QrCeGPQIQSOfUoAzC8WNb8JgohfRojq5mlbO7YLT2+pyxWxyJR73XdHd +ezV+HWrlFpy2Tva7MGkOKm1JCOx9IjpajxrnKctNFVOJ23suRPZ9taLRRjnOrm5G +6Zr8q1gUgLDi7ifXr7eb9j9/UXeEKrwdLXX1YkxusSevlI+z8YMWMa2aKBn6T3tS +Ao8Dx1Hx5CHORAOzlZSWuG4Z/hhFd4LgZeeB2tv8D+sCuhTmp5FfuLXEOc0J4C5e +zgIPgRSENbTONZRAOVSYeI2+UfTw0kLSnfXbi/DCr6UFGE1Uu2VMBAc+bX4bfmJR +wOG4IpaVGzcy6gP1Jl4TpekwAtXVSMNw+1k1YHHYqbeKxhT8le0gNuT9mAlsJfFl +CeFbiP0HIome8Wkkyn+xDIkRDDdJDkCyRIhY8xKnVQN6Ylg1Uchn2YiCNbTONADM +p6Yd2G7+OkYkAqv2z8xMmrw5xtmOc/KqIfoSJEyroVK2XeSUfeUmG9CHx3QR1iMX +Z6cmGg94aDuJFxQtPnj1FbuRyW3USVSjphfS1FWNp3cDrcq8ht6VLqycQZYgOw/C +/5C6OIHgtb05R4+V/G3vLngztyDkGgyM0ExFI2yyNbTONYBKxXSK7nuCis0JxfQu +hGshSBGCbbjtDT0RctJ0jEqPkrt/WYvp3yFQ0tfggDI2JfErpelJpknryEt10EzB +38OobtzunS4kitfFihwBsvMGR8bX1G43Z+6AXfVyZY3LVYocH/9nWkCJl0f2QdQe +pDWuMeyx+cmwON7Oas/HEqjkNbTNXE/PAj14Q+zeY3LYoovPKvlqdkIjki5cqMqm +8guv3GApfJP4vTHEqpIdosHvaICqWvKr/Xnp3JTPrEWnSItoXNBkYgv1EO5ZxVut +Q8rlhcOdx4J1Y1txekdfqw4GSykxjZljwy2R2F4LlD8COg6I04QbIEMfVXmdm+CS +HvbaCd0PtLOPLKidvbWuCrjxBd/L5jeQOrMJ1SDX5DQ9J5Z8/5mkq4eqiWgwuoWc +bBegiZqey6hcl9Um4OWQ3SKjISvCSR7wdrAdv0S21ivYkOCZZQ3HBQS6YY5RlYvE +9I4kIZF8XKkit7ekfhdmZCfpIvnJHY6JAIOufQ2+92qUkFKmm5RWXD== +-----RAQ EFN CEVINGR XRL-----`))) + var err error + testRSAPSS2048PrivateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + panic(err) + } +} diff --git a/crypto/tls/handshake_client.go b/crypto/tls/handshake_client.go index 22fb1673d38..77d0dcbf1be 100644 --- a/crypto/tls/handshake_client.go +++ b/crypto/tls/handshake_client.go @@ -7,232 +7,45 @@ package tls import ( "bytes" "context" - "encoding/binary" + "crypto" "errors" "fmt" "hash" + "internal/byteorder" + "internal/godebug" "io" "net" + "slices" "strconv" "strings" "time" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/ed25519" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" "github.com/runZeroInc/excrypto/crypto/internal/hpke" - "github.com/runZeroInc/excrypto/crypto/internal/mlkem768" "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" "github.com/runZeroInc/excrypto/crypto/x509" - "github.com/runZeroInc/excrypto/internal/byteorder" - "github.com/runZeroInc/excrypto/internal/godebug" ) type clientHandshakeState struct { - c *Conn - ctx context.Context - serverHello *serverHelloMsg - hello *clientHelloMsg - suite *cipherSuite - finishedHash finishedHash - masterSecret []byte - preMasterSecret []byte - session *SessionState // the session being resumed - ticket []byte // a fresh ticket received during this handshake -} - -type CacheKeyGenerator interface { - Key(net.Addr) string -} - -type ClientFingerprintConfiguration struct { - // Version in the handshake header - HandshakeVersion uint16 - - // if len == 32, it will specify the client random. - // Otherwise, the field will be random - // except the top 4 bytes if InsertTimestamp is true - ClientRandom []byte - InsertTimestamp bool - - // if RandomSessionID > 0, will overwrite SessionID w/ that many - // random bytes when a session resumption occurs - RandomSessionID int - SessionID []byte - - // These fields will appear exactly in order in the ClientHello - CipherSuites []uint16 - CompressionMethods []uint8 - Extensions []ClientExtension - - // Optional, both must be non-nil, or neither. - // Custom Session cache implementations allowed - SessionCache ClientSessionCache - CacheKey CacheKeyGenerator -} - -type ClientExtension interface { - // Produce the bytes on the wire for this extension, type and length included - Marshal() []byte - - // Function will return an error if zTLS does not implement the necessary features for this extension - CheckImplemented() error - - // Modifies the config to reflect the state of the extension - WriteToConfig(*Config) error -} - -func (c *ClientFingerprintConfiguration) CheckImplementedExtensions() error { - for _, ext := range c.Extensions { - if err := ext.CheckImplemented(); err != nil { - return err - } - } - return nil -} - -func (c *clientHelloMsg) WriteToConfig(config *Config) error { - config.NextProtos = c.alpnProtocols - config.CipherSuites = c.cipherSuites - config.MaxVersion = c.vers - config.ClientRandom = c.random - config.CurvePreferences = c.supportedCurves - config.HeartbeatEnabled = c.heartbeatEnabled - config.ExtendedRandom = c.extendedRandomEnabled - config.ForceSessionTicketExt = c.ticketSupported - config.ExtendedMasterSecret = c.extendedMasterSecret - config.SignedCertificateTimestampExt = c.scts - return nil -} - -func (c *ClientFingerprintConfiguration) WriteToConfig(config *Config) error { - config.NextProtos = []string{} - config.CipherSuites = c.CipherSuites - config.MaxVersion = c.HandshakeVersion - config.ClientRandom = c.ClientRandom - config.CurvePreferences = []CurveID{} - config.HeartbeatEnabled = false - config.ExtendedRandom = false - config.ForceSessionTicketExt = false - config.ExtendedMasterSecret = false - config.SignedCertificateTimestampExt = false - for _, ext := range c.Extensions { - if err := ext.WriteToConfig(config); err != nil { - return err - } - } - return nil -} - -func currentTimestamp() ([]byte, error) { - t := time.Now().Unix() - buf := new(bytes.Buffer) - err := binary.Write(buf, binary.BigEndian, t) - return buf.Bytes(), err -} - -func (c *ClientFingerprintConfiguration) marshal(config *Config) ([]byte, error) { - if err := c.CheckImplementedExtensions(); err != nil { - return nil, err - } - head := make([]byte, 38) - head[0] = 1 - head[4] = uint8(c.HandshakeVersion >> 8) - head[5] = uint8(c.HandshakeVersion) - if len(c.ClientRandom) == 32 { - copy(head[6:38], c.ClientRandom[0:32]) - } else { - start := 6 - if c.InsertTimestamp { - t, err := currentTimestamp() - if err != nil { - return nil, err - } - copy(head[start:start+4], t) - start = start + 4 - } - _, err := io.ReadFull(config.rand(), head[start:38]) - if err != nil { - return nil, errors.New("tls: short read from Rand: " + err.Error()) - } - } - - if len(c.SessionID) >= 256 { - return nil, errors.New("tls: SessionID too long") - } - sessionID := make([]byte, len(c.SessionID)+1) - sessionID[0] = uint8(len(c.SessionID)) - if len(c.SessionID) > 0 { - copy(sessionID[1:], c.SessionID) - } - - ciphers := make([]byte, 2+2*len(c.CipherSuites)) - ciphers[0] = uint8(len(c.CipherSuites) >> 7) - ciphers[1] = uint8(len(c.CipherSuites) << 1) - for i, suite := range c.CipherSuites { - if !config.ForceSuites { - found := false - for _, impl := range cipherSuites { - if impl.id == suite { - found = true - } - } - if !found { - return nil, errors.New(fmt.Sprintf("tls: unimplemented cipher suite %d", suite)) - } - } - - ciphers[2+i*2] = uint8(suite >> 8) - ciphers[3+i*2] = uint8(suite) - } - - if len(c.CompressionMethods) >= 256 { - return nil, errors.New("tls: Too many compression methods") - } - compressions := make([]byte, len(c.CompressionMethods)+1) - compressions[0] = uint8(len(c.CompressionMethods)) - if len(c.CompressionMethods) > 0 { - copy(compressions[1:], c.CompressionMethods) - if c.CompressionMethods[0] != 0 { - return nil, errors.New(fmt.Sprintf("tls: unimplemented compression method %d", c.CompressionMethods[0])) - } - if len(c.CompressionMethods) > 1 { - return nil, errors.New(fmt.Sprintf("tls: unimplemented compression method %d", c.CompressionMethods[1])) - } - } else { - return nil, errors.New("tls: no compression method") - } - - var extensions []byte - for _, ext := range c.Extensions { - extensions = append(extensions, ext.Marshal()...) - } - if len(extensions) > 0 { - length := make([]byte, 2) - length[0] = uint8(len(extensions) >> 8) - length[1] = uint8(len(extensions)) - extensions = append(length, extensions...) - } - helloArray := [][]byte{head, sessionID, ciphers, compressions, extensions} - hello := []byte{} - for _, b := range helloArray { - hello = append(hello, b...) - } - lengthOnTheWire := len(hello) - 4 - if lengthOnTheWire >= 1<<24 { - return nil, errors.New("ClientHello message too long") - } - hello[1] = uint8(lengthOnTheWire >> 16) - hello[2] = uint8(lengthOnTheWire >> 8) - hello[3] = uint8(lengthOnTheWire) - - return hello, nil + c *Conn + ctx context.Context + serverHello *serverHelloMsg + hello *clientHelloMsg + suite *cipherSuite + finishedHash finishedHash + masterSecret []byte + session *SessionState // the session being resumed + ticket []byte // a fresh ticket received during this handshake } var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme -func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echContext, error) { +func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echClientContext, error) { config := c.config if len(config.ServerName) == 0 && !config.InsecureSkipVerify { return nil, nil, nil, errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") @@ -332,7 +145,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if len(hello.supportedVersions) == 1 { hello.cipherSuites = nil } - if needFIPS() { + if fips140tls.Required() { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13FIPS...) } else if hasAESGCMHardwareSupport { hello.cipherSuites = append(hello.cipherSuites, defaultCipherSuitesTLS13...) @@ -345,27 +158,31 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon } curveID := hello.supportedCurves[0] keyShareKeys = &keySharePrivateKeys{curveID: curveID} - if curveID == x25519Kyber768Draft00 { + // Note that if X25519MLKEM768 is supported, it will be first because + // the preference order is fixed. + if curveID == X25519MLKEM768 { keyShareKeys.ecdhe, err = generateECDHEKey(config.rand(), X25519) if err != nil { return nil, nil, nil, err } - seed := make([]byte, mlkem768.SeedSize) + seed := make([]byte, mlkem.SeedSize) if _, err := io.ReadFull(config.rand(), seed); err != nil { return nil, nil, nil, err } - keyShareKeys.kyber, err = mlkem768.NewKeyFromSeed(seed) + keyShareKeys.mlkem, err = mlkem.NewDecapsulationKey768(seed) if err != nil { return nil, nil, nil, err } - // For draft-tls-westerbaan-xyber768d00-03, we send both a hybrid - // and a standard X25519 key share, since most servers will only - // support the latter. We reuse the same X25519 ephemeral key for - // both, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2. + mlkemEncapsulationKey := keyShareKeys.mlkem.EncapsulationKey().Bytes() + x25519EphemeralKey := keyShareKeys.ecdhe.PublicKey().Bytes() hello.keyShares = []keyShare{ - {group: x25519Kyber768Draft00, data: append(keyShareKeys.ecdhe.PublicKey().Bytes(), - keyShareKeys.kyber.EncapsulationKey()...)}, - {group: X25519, data: keyShareKeys.ecdhe.PublicKey().Bytes()}, + {group: X25519MLKEM768, data: append(mlkemEncapsulationKey, x25519EphemeralKey...)}, + } + // If both X25519MLKEM768 and X25519 are supported, we send both key + // shares (as a fallback) and we reuse the same X25519 ephemeral + // key, as allowed by draft-ietf-tls-hybrid-design-09, Section 3.2. + if slices.Contains(hello.supportedCurves, X25519) { + hello.keyShares = append(hello.keyShares, keyShare{group: X25519, data: x25519EphemeralKey}) } } else { if _, ok := curveForCurveID(curveID); !ok { @@ -390,7 +207,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon hello.quicTransportParameters = p } - var ech *echContext + var ech *echClientContext if c.config.EncryptedClientHelloConfigList != nil { if c.config.MinVersion != 0 && c.config.MinVersion < VersionTLS13 { return nil, nil, nil, errors.New("tls: MinVersion must be >= VersionTLS13 if EncryptedClientHelloConfigList is populated") @@ -406,7 +223,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon if echConfig == nil { return nil, nil, nil, errors.New("tls: EncryptedClientHelloConfigList contains no valid configs") } - ech = &echContext{config: echConfig} + ech = &echClientContext{config: echConfig} hello.encryptedClientHello = []byte{1} // indicate inner hello // We need to explicitly set these 1.2 fields to nil, as we do not // marshal them when encoding the inner hello, otherwise transcripts @@ -435,7 +252,7 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *keySharePrivateKeys, *echCon return hello, keyShareKeys, ech, nil } -type echContext struct { +type echClientContext struct { config *echConfig hpkeContext *hpke.Sender encapsulatedKey []byte @@ -444,6 +261,7 @@ type echContext struct { kdfID uint16 aeadID uint16 echRejected bool + retryConfigs []byte } func (c *Conn) clientHandshake(ctx context.Context) (err error) { @@ -519,7 +337,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { if err := transcriptMsg(hello, transcript); err != nil { return err } - earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript) + earlyTrafficSecret := earlySecret.ClientEarlyTrafficSecret(transcript) c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret) } @@ -586,7 +404,7 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) { } func (c *Conn) loadSession(hello *clientHelloMsg) ( - session *SessionState, earlySecret, binderKey []byte, err error) { + session *SessionState, earlySecret *tls13.EarlySecret, binderKey []byte, err error) { if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil { return nil, nil, nil, nil } @@ -713,8 +531,8 @@ func (c *Conn) loadSession(hello *clientHelloMsg) ( hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())} // Compute the PSK binders. See RFC 8446, Section 4.2.11.2. - earlySecret = cipherSuite.extract(session.secret, nil) - binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil) + earlySecret = tls13.NewEarlySecret(cipherSuite.hash.New, session.secret) + binderKey = earlySecret.ResumptionBinderKey() transcript := cipherSuite.hash.New() if err := computeAndUpdatePSK(hello, binderKey, transcript, cipherSuite.finishedHash); err != nil { return nil, nil, nil, err @@ -845,11 +663,11 @@ func (hs *clientHandshakeState) pickCipherSuite() error { return errors.New("tls: server chose an unconfigured cipher suite") } - if hs.c.config.CipherSuites == nil && !needFIPS() && rsaKexCiphers[hs.suite.id] { + if hs.c.config.CipherSuites == nil && !fips140tls.Required() && rsaKexCiphers[hs.suite.id] { tlsrsakex.Value() // ensure godebug is initialized tlsrsakex.IncNonDefault() } - if hs.c.config.CipherSuites == nil && !needFIPS() && tdesCiphers[hs.suite.id] { + if hs.c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] { tls3des.Value() // ensure godebug is initialized tls3des.IncNonDefault() } @@ -932,11 +750,11 @@ func (hs *clientHandshakeState) doFullHandshake() error { err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, c.peerCertificates[0], skx) c.handshakeLog.ServerKeyExchange = skx.MakeLog(keyAgreement) if err != nil { - c.sendAlert(alertUnexpectedMessage) + c.sendAlert(alertIllegalParameter) return err } if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { - c.curveID = CurveID(byteorder.BeUint16(skx.key[1:])) + c.curveID = CurveID(byteorder.BEUint16(skx.key[1:])) } msg, err = c.readHandshake(&hs.finishedHash) @@ -1197,7 +1015,7 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) { } // checkALPN ensure that the server's choice of ALPN protocol is compatible with -// the protocols that we advertised in the Client Hello. +// the protocols that we advertised in the ClientHello. func checkALPN(clientProtos []string, serverProto string, quic bool) error { if serverProto == "" { if quic && len(clientProtos) > 0 { @@ -1376,21 +1194,16 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } - - var res []x509.CertificateChain - var err error - var validation *x509.Validation - res, validation, err = certs[0].ValidateWithStupidDetail(opts) - c.handshakeLog.ServerCertificates.addParsed(certs, validation) - + chains, err := certs[0].Verify(opts) if err != nil { c.sendAlert(alertBadCertificate) return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } - c.verifiedChains = make([][]*x509.Certificate, len(res)) - for i, cert := range res { - c.verifiedChains[i] = []*x509.Certificate(cert) + c.verifiedChains, err = fipsAllowedChains(chains) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } } } else if !c.config.InsecureSkipVerify { @@ -1404,20 +1217,16 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error { for _, cert := range certs[1:] { opts.Intermediates.AddCert(cert) } - var res []x509.CertificateChain - var err error - var validation *x509.Validation - res, validation, err = certs[0].ValidateWithStupidDetail(opts) - c.handshakeLog.ServerCertificates.addParsed(certs, validation) - + chains, err := certs[0].Verify(opts) if err != nil { c.sendAlert(alertBadCertificate) return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } - c.verifiedChains = make([][]*x509.Certificate, len(res)) - for i, cert := range res { - c.verifiedChains[i] = []*x509.Certificate(cert) + c.verifiedChains, err = fipsAllowedChains(chains) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } } diff --git a/crypto/tls/handshake_client_test.go b/crypto/tls/handshake_client_test.go index 619a4f8a1c8..92ac1eebcc0 100644 --- a/crypto/tls/handshake_client_test.go +++ b/crypto/tls/handshake_client_test.go @@ -12,6 +12,7 @@ import ( "encoding/pem" "errors" "fmt" + "internal/byteorder" "io" "math/big" "net" @@ -27,6 +28,15 @@ import ( "crypto/rand" + "github.com/runZeroInc/excrypto/crypto/ecdsa" + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/rsa" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" + "github.com/runZeroInc/excrypto/crypto/x509" + "github.com/runZeroInc/excrypto/crypto/x509/pkix" + + "crypto/rand" + "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/elliptic" "github.com/runZeroInc/excrypto/crypto/rsa" @@ -209,7 +219,7 @@ func (test *clientTest) connFromCommand() (conn *recordingConn, child *exec.Cmd, var serverInfo bytes.Buffer for _, ext := range test.extensions { pem.Encode(&serverInfo, &pem.Block{ - Type: fmt.Sprintf("SERVERINFO FOR EXTENSION %d", byteorder.BeUint16(ext)), + Type: fmt.Sprintf("SERVERINFO FOR EXTENSION %d", byteorder.BEUint16(ext)), Bytes: ext, }) } @@ -435,7 +445,7 @@ func (test *clientTest) run(t *testing.T, write bool) { } if write { - clientConn.Close() + client.Close() path := test.dataPath() out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { @@ -850,13 +860,17 @@ func testResumption(t *testing.T, version uint16) { if testing.Short() { t.Skip("skipping in -short mode") } + + // Note: using RSA 2048 test certificates because they are compatible with FIPS mode. + testCertificates := []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} serverConfig := &Config{ MaxVersion: version, - CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - Certificates: testConfig.Certificates, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, + Certificates: testCertificates, + Time: testTime, } - issuer, err := x509.ParseCertificate(testRSACertificateIssuer) + issuer, err := x509.ParseCertificate(testRSA2048CertificateIssuer) if err != nil { panic(err) } @@ -866,10 +880,11 @@ func testResumption(t *testing.T, version uint16) { clientConfig := &Config{ MaxVersion: version, - CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, ClientSessionCache: NewLRUClientSessionCache(32), RootCAs: rootCAs, ServerName: "example.golang", + Time: testTime, } testResumeState := func(test string, didResume bool) { @@ -916,7 +931,7 @@ func testResumption(t *testing.T, version uint16) { // An old session ticket is replaced with a ticket encrypted with a fresh key. ticket = getTicket() - serverConfig.Time = func() time.Time { return time.Now().Add(24*time.Hour + time.Minute) } + serverConfig.Time = func() time.Time { return testTime().Add(24*time.Hour + time.Minute) } testResumeState("ResumeWithOldTicket", true) if bytes.Equal(ticket, getTicket()) { t.Fatal("old first ticket matches the fresh one") @@ -924,13 +939,13 @@ func testResumption(t *testing.T, version uint16) { // Once the session master secret is expired, a full handshake should occur. ticket = getTicket() - serverConfig.Time = func() time.Time { return time.Now().Add(24*8*time.Hour + time.Minute) } + serverConfig.Time = func() time.Time { return testTime().Add(24*8*time.Hour + time.Minute) } testResumeState("ResumeWithExpiredTicket", false) if bytes.Equal(ticket, getTicket()) { t.Fatal("expired first ticket matches the fresh one") } - serverConfig.Time = func() time.Time { return time.Now() } // reset the time back + serverConfig.Time = testTime // reset the time back key1 := randomKey() serverConfig.SetSessionTicketKeys([][32]byte{key1}) @@ -947,11 +962,11 @@ func testResumption(t *testing.T, version uint16) { testResumeState("KeyChangeFinish", true) // Age the session ticket a bit, but not yet expired. - serverConfig.Time = func() time.Time { return time.Now().Add(24*time.Hour + time.Minute) } + serverConfig.Time = func() time.Time { return testTime().Add(24*time.Hour + time.Minute) } testResumeState("OldSessionTicket", true) ticket = getTicket() // Expire the session ticket, which would force a full handshake. - serverConfig.Time = func() time.Time { return time.Now().Add(24*8*time.Hour + time.Minute) } + serverConfig.Time = func() time.Time { return testTime().Add(24*8*time.Hour + 2*time.Minute) } testResumeState("ExpiredSessionTicket", false) if bytes.Equal(ticket, getTicket()) { t.Fatal("new ticket wasn't provided after old ticket expired") @@ -959,7 +974,7 @@ func testResumption(t *testing.T, version uint16) { // Age the session ticket a bit at a time, but don't expire it. d := 0 * time.Hour - serverConfig.Time = func() time.Time { return time.Now().Add(d) } + serverConfig.Time = func() time.Time { return testTime().Add(d) } deleteTicket() testResumeState("GetFreshSessionTicket", false) for i := 0; i < 13; i++ { @@ -970,7 +985,7 @@ func testResumption(t *testing.T, version uint16) { // handshake occurs for TLS 1.2. Resumption should still occur for // TLS 1.3 since the client should be using a fresh ticket sent over // by the server. - d += 12 * time.Hour + d += 12*time.Hour + time.Minute if version == VersionTLS13 { testResumeState("ExpiredSessionTicket", true) } else { @@ -984,8 +999,9 @@ func testResumption(t *testing.T, version uint16) { // before the serverConfig is used works. serverConfig = &Config{ MaxVersion: version, - CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - Certificates: testConfig.Certificates, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, + Certificates: testCertificates, + Time: testTime, } serverConfig.SetSessionTicketKeys([][32]byte{key2}) @@ -994,7 +1010,7 @@ func testResumption(t *testing.T, version uint16) { // In TLS 1.3, cross-cipher suite resumption is allowed as long as the KDF // hash matches. Also, Config.CipherSuites does not apply to TLS 1.3. if version != VersionTLS13 { - clientConfig.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_RC4_128_SHA} + clientConfig.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384} testResumeState("DifferentCipherSuite", false) testResumeState("DifferentCipherSuiteRecovers", true) } @@ -1010,7 +1026,8 @@ func testResumption(t *testing.T, version uint16) { // Use a different curve than the client to force a HelloRetryRequest. CurvePreferences: []CurveID{CurveP521, CurveP384, CurveP256}, MaxVersion: version, - Certificates: testConfig.Certificates, + Certificates: testCertificates, + Time: testTime, } testResumeState("InitialHandshake", false) testResumeState("WithHelloRetryRequest", true) @@ -1018,8 +1035,9 @@ func testResumption(t *testing.T, version uint16) { // Reset serverConfig back. serverConfig = &Config{ MaxVersion: version, - CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA}, - Certificates: testConfig.Certificates, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384}, + Certificates: testCertificates, + Time: testTime, } } @@ -1274,7 +1292,7 @@ func TestServerSelectingUnconfiguredApplicationProtocol(t *testing.T) { go func() { client := Client(c, &Config{ ServerName: "foo", - CipherSuites: []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256}, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, NextProtos: []string{"http", "something-else"}, }) errChan <- client.Handshake() @@ -1294,7 +1312,7 @@ func TestServerSelectingUnconfiguredApplicationProtocol(t *testing.T) { serverHello := &serverHelloMsg{ vers: VersionTLS12, random: make([]byte, 32), - cipherSuite: TLS_RSA_WITH_AES_128_GCM_SHA256, + cipherSuite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, alpnProtocol: "how-about-this", } serverHelloBytes := mustMarshal(t, serverHello) @@ -1310,7 +1328,7 @@ func TestServerSelectingUnconfiguredApplicationProtocol(t *testing.T) { s.Close() if err := <-errChan; !strings.Contains(err.Error(), "server selected unadvertised ALPN protocol") { - t.Fatalf("Expected error about unconfigured cipher suite but got %q", err) + t.Fatalf("Expected error about unconfigured ALPN protocol but got %q", err) } } @@ -1726,7 +1744,10 @@ func testVerifyConnection(t *testing.T, version uint16) { }, } for _, test := range tests { - issuer, err := x509.ParseCertificate(testRSACertificateIssuer) + // Note: using RSA 2048 test certificates because they are compatible with FIPS mode. + testCertificates := []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} + + issuer, err := x509.ParseCertificate(testRSA2048CertificateIssuer) if err != nil { panic(err) } @@ -1737,7 +1758,8 @@ func testVerifyConnection(t *testing.T, version uint16) { serverConfig := &Config{ MaxVersion: version, - Certificates: []Certificate{testConfig.Certificates[0]}, + Certificates: testCertificates, + Time: testTime, ClientCAs: rootCAs, NextProtos: []string{"protocol1"}, } @@ -1750,7 +1772,8 @@ func testVerifyConnection(t *testing.T, version uint16) { ClientSessionCache: NewLRUClientSessionCache(32), RootCAs: rootCAs, ServerName: "example.golang", - Certificates: []Certificate{testConfig.Certificates[0]}, + Certificates: testCertificates, + Time: testTime, NextProtos: []string{"protocol1"}, } test.configureClient(clientConfig, &clientCalled) @@ -1785,7 +1808,8 @@ func TestVerifyPeerCertificate(t *testing.T) { } func testVerifyPeerCertificate(t *testing.T, version uint16) { - issuer, err := x509.ParseCertificate(testRSACertificateIssuer) + // Note: using RSA 2048 test certificates because they are compatible with FIPS mode. + issuer, err := x509.ParseCertificate(testRSA2048CertificateIssuer) if err != nil { panic(err) } @@ -1793,8 +1817,6 @@ func testVerifyPeerCertificate(t *testing.T, version uint16) { rootCAs := x509.NewCertPool() rootCAs.AddCert(issuer) - now := func() time.Time { return time.Unix(1476984729, 0) } - sentinelErr := errors.New("TestVerifyPeerCertificate") verifyPeerCertificateCallback := func(called *bool, rawCerts [][]byte, validatedChains [][]*x509.Certificate) error { @@ -2040,11 +2062,11 @@ func testVerifyPeerCertificate(t *testing.T, version uint16) { config.ServerName = "example.golang" config.ClientAuth = RequireAndVerifyClientCert config.ClientCAs = rootCAs - config.Time = now + config.Time = testTime config.MaxVersion = version config.Certificates = make([]Certificate, 1) - config.Certificates[0].Certificate = [][]byte{testRSACertificate} - config.Certificates[0].PrivateKey = testRSAPrivateKey + config.Certificates[0].Certificate = [][]byte{testRSA2048Certificate} + config.Certificates[0].PrivateKey = testRSA2048PrivateKey config.Certificates[0].SignedCertificateTimestamps = [][]byte{[]byte("dummy sct 1"), []byte("dummy sct 2")} config.Certificates[0].OCSPStaple = []byte("dummy ocsp") test.configureServer(config, &serverCalled) @@ -2055,9 +2077,10 @@ func testVerifyPeerCertificate(t *testing.T, version uint16) { }() config := testConfig.Clone() + config.Certificates = []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} config.ServerName = "example.golang" config.RootCAs = rootCAs - config.Time = now + config.Time = testTime config.MaxVersion = version test.configureClient(config, &clientCalled) clientErr := Client(c, config).Handshake() @@ -2338,8 +2361,8 @@ var getClientCertificateTests = []struct { panic("empty AcceptableCAs") } cert := &Certificate{ - Certificate: [][]byte{testRSACertificate}, - PrivateKey: testRSAPrivateKey, + Certificate: [][]byte{testRSA2048Certificate}, + PrivateKey: testRSA2048PrivateKey, } return cert, nil } @@ -2359,25 +2382,34 @@ func TestGetClientCertificate(t *testing.T) { } func testGetClientCertificate(t *testing.T, version uint16) { - issuer, err := x509.ParseCertificate(testRSACertificateIssuer) + // Note: using RSA 2048 test certificates because they are compatible with FIPS mode. + issuer, err := x509.ParseCertificate(testRSA2048CertificateIssuer) if err != nil { panic(err) } for i, test := range getClientCertificateTests { serverConfig := testConfig.Clone() + serverConfig.Certificates = []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} serverConfig.ClientAuth = VerifyClientCertIfGiven serverConfig.RootCAs = x509.NewCertPool() serverConfig.RootCAs.AddCert(issuer) serverConfig.ClientCAs = serverConfig.RootCAs - serverConfig.Time = func() time.Time { return time.Unix(1476984729, 0) } + serverConfig.Time = testTime serverConfig.MaxVersion = version clientConfig := testConfig.Clone() + clientConfig.Certificates = []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} clientConfig.MaxVersion = version test.setup(clientConfig, serverConfig) + // TLS 1.1 isn't available for FIPS required + if fips140tls.Required() && clientConfig.MaxVersion == VersionTLS11 { + t.Logf("skipping test %d for FIPS mode", i) + continue + } + type serverResult struct { cs ConnectionState err error @@ -2516,11 +2548,15 @@ func TestDowngradeCanary(t *testing.T) { if err := testDowngradeCanary(t, VersionTLS12, VersionTLS12); err != nil { t.Errorf("client didn't ignore expected TLS 1.2 canary") } - if err := testDowngradeCanary(t, VersionTLS11, VersionTLS11); err != nil { - t.Errorf("client unexpectedly reacted to a canary in TLS 1.1") - } - if err := testDowngradeCanary(t, VersionTLS10, VersionTLS10); err != nil { - t.Errorf("client unexpectedly reacted to a canary in TLS 1.0") + if !fips140tls.Required() { + if err := testDowngradeCanary(t, VersionTLS11, VersionTLS11); err != nil { + t.Errorf("client unexpectedly reacted to a canary in TLS 1.1") + } + if err := testDowngradeCanary(t, VersionTLS10, VersionTLS10); err != nil { + t.Errorf("client unexpectedly reacted to a canary in TLS 1.0") + } + } else { + t.Logf("skiping TLS 1.1 and TLS 1.0 downgrade canary checks in FIPS mode") } } @@ -2530,7 +2566,8 @@ func TestResumptionKeepsOCSPAndSCT(t *testing.T) { } func testResumptionKeepsOCSPAndSCT(t *testing.T, ver uint16) { - issuer, err := x509.ParseCertificate(testRSACertificateIssuer) + // Note: using RSA 2048 test certificates because they are compatible with FIPS mode. + issuer, err := x509.ParseCertificate(testRSA2048CertificateIssuer) if err != nil { t.Fatalf("failed to parse test issuer") } @@ -2541,8 +2578,10 @@ func testResumptionKeepsOCSPAndSCT(t *testing.T, ver uint16) { ClientSessionCache: NewLRUClientSessionCache(32), ServerName: "example.golang", RootCAs: roots, + Time: testTime, } serverConfig := testConfig.Clone() + serverConfig.Certificates = []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} serverConfig.MaxVersion = ver serverConfig.Certificates[0].OCSPStaple = []byte{1, 2, 3} serverConfig.Certificates[0].SignedCertificateTimestamps = [][]byte{{4, 5, 6}} @@ -2677,11 +2716,15 @@ func testTLS13OnlyClientHelloCipherSuite(t *testing.T, ciphers []uint16) { serverConfig := &Config{ Certificates: testConfig.Certificates, GetConfigForClient: func(chi *ClientHelloInfo) (*Config, error) { - if len(chi.CipherSuites) != len(defaultCipherSuitesTLS13NoAES) { + expectedCiphersuites := defaultCipherSuitesTLS13NoAES + if fips140tls.Required() { + expectedCiphersuites = defaultCipherSuitesTLS13FIPS + } + if len(chi.CipherSuites) != len(expectedCiphersuites) { t.Errorf("only TLS 1.3 suites should be advertised, got=%x", chi.CipherSuites) } else { - for i := range defaultCipherSuitesTLS13NoAES { - if want, got := defaultCipherSuitesTLS13NoAES[i], chi.CipherSuites[i]; want != got { + for i := range expectedCiphersuites { + if want, got := expectedCiphersuites[i], chi.CipherSuites[i]; want != got { t.Errorf("cipher at index %d does not match, want=%x, got=%x", i, want, got) } } diff --git a/crypto/tls/handshake_client_tls13.go b/crypto/tls/handshake_client_tls13.go index 0d8a4d4a5fd..a2d1bd9f03c 100644 --- a/crypto/tls/handshake_client_tls13.go +++ b/crypto/tls/handshake_client_tls13.go @@ -7,14 +7,16 @@ package tls import ( "bytes" "context" + "crypto" "errors" "hash" "slices" "time" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/hmac" - "github.com/runZeroInc/excrypto/crypto/internal/mlkem768" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/crypto/subtle" ) @@ -27,7 +29,7 @@ type clientHandshakeStateTLS13 struct { keyShareKeys *keySharePrivateKeys session *SessionState - earlySecret []byte + earlySecret *tls13.EarlySecret binderKey []byte certReq *certificateRequestMsgTLS13 @@ -35,10 +37,10 @@ type clientHandshakeStateTLS13 struct { sentDummyCCS bool suite *cipherSuiteTLS13 transcript hash.Hash - masterSecret []byte + masterSecret *tls13.MasterSecret trafficSecret []byte // client_application_traffic_secret_0 - echContext *echContext + echContext *echClientContext } // handshake requires hs.c, hs.hello, hs.serverHello, hs.keyShareKeys, and, @@ -84,14 +86,13 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } } - var echRetryConfigList []byte if hs.echContext != nil { confTranscript := cloneHash(hs.echContext.innerTranscript, hs.suite.hash) confTranscript.Write(hs.serverHello.original[:30]) confTranscript.Write(make([]byte, 8)) confTranscript.Write(hs.serverHello.original[38:]) - acceptConfirmation := hs.suite.expandLabel( - hs.suite.extract(hs.echContext.innerHello.random, nil), + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + hkdf.Extract(hs.suite.hash.New, hs.echContext.innerHello.random, nil), "ech accept confirmation", confTranscript.Sum(nil), 8, @@ -104,7 +105,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error { if hs.serverHello.encryptedClientHello != nil { c.sendAlert(alertUnsupportedExtension) - return errors.New("tls: unexpected encrypted_client_hello extension in server hello despite ECH being accepted") + return errors.New("tls: unexpected encrypted client hello extension in server hello despite ECH being accepted") } if hs.hello.serverName == "" && hs.serverHello.serverNameAck { @@ -113,9 +114,6 @@ func (hs *clientHandshakeStateTLS13) handshake() error { } } else { hs.echContext.echRejected = true - // If the server sent us retry configs, we'll return these to - // the user so they can update their Config. - echRetryConfigList = hs.serverHello.encryptedClientHello } } @@ -154,7 +152,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error { if hs.echContext != nil && hs.echContext.echRejected { c.sendAlert(alertECHRequired) - return &ECHRejectionError{echRetryConfigList} + return &ECHRejectionError{hs.echContext.retryConfigs} } if hs.session != nil { @@ -276,8 +274,8 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { copy(hrrHello, hs.serverHello.original) hrrHello = bytes.Replace(hrrHello, hs.serverHello.encryptedClientHello, make([]byte, 8), 1) confTranscript.Write(hrrHello) - acceptConfirmation := hs.suite.expandLabel( - hs.suite.extract(hs.echContext.innerHello.random, nil), + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + hkdf.Extract(hs.suite.hash.New, hs.echContext.innerHello.random, nil), "hrr ech accept confirmation", confTranscript.Sum(nil), 8, @@ -296,7 +294,7 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { } else if hs.serverHello.encryptedClientHello != nil { // Unsolicited ECH extension should be rejected c.sendAlert(alertUnsupportedExtension) - return errors.New("tls: unexpected ECH extension in serverHello") + return errors.New("tls: unexpected encrypted client hello extension in serverHello") } // The only HelloRetryRequest extensions we support are key_share and @@ -330,12 +328,11 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: server sent an unnecessary HelloRetryRequest key_share") } - // Note: we don't support selecting X25519Kyber768Draft00 in a HRR, - // because we currently only support it at all when CurvePreferences is - // empty, which will cause us to also send a key share for it. + // Note: we don't support selecting X25519MLKEM768 in a HRR, because it + // is currently first in preference order, so if it's enabled we'll + // always send a key share for it. // - // This will have to change once we support selecting hybrid KEMs - // without sending key shares for them. + // This will have to change once we support multiple hybrid KEMs. if _, ok := curveForCurveID(curveID); !ok { c.sendAlert(alertInternalError) return errors.New("tls: CurvePreferences includes unsupported curve") @@ -488,12 +485,12 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c := hs.c ecdhePeerData := hs.serverHello.serverShare.data - if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if len(ecdhePeerData) != x25519PublicKeySize+mlkem768.CiphertextSize { + if hs.serverHello.serverShare.group == X25519MLKEM768 { + if len(ecdhePeerData) != mlkem.CiphertextSize768+x25519PublicKeySize { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid server key share") + return errors.New("tls: invalid server X25519MLKEM768 key share") } - ecdhePeerData = hs.serverHello.serverShare.data[:x25519PublicKeySize] + ecdhePeerData = hs.serverHello.serverShare.data[mlkem.CiphertextSize768:] } peerKey, err := hs.keyShareKeys.ecdhe.Curve().NewPublicKey(ecdhePeerData) if err != nil { @@ -505,33 +502,30 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid server key share") } - if hs.serverHello.serverShare.group == x25519Kyber768Draft00 { - if hs.keyShareKeys.kyber == nil { + if hs.serverHello.serverShare.group == X25519MLKEM768 { + if hs.keyShareKeys.mlkem == nil { return c.sendAlert(alertInternalError) } - ciphertext := hs.serverHello.serverShare.data[x25519PublicKeySize:] - kyberShared, err := kyberDecapsulate(hs.keyShareKeys.kyber, ciphertext) + ciphertext := hs.serverHello.serverShare.data[:mlkem.CiphertextSize768] + mlkemShared, err := hs.keyShareKeys.mlkem.Decapsulate(ciphertext) if err != nil { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid Kyber server key share") + return errors.New("tls: invalid X25519MLKEM768 server key share") } - sharedKey = append(sharedKey, kyberShared...) + sharedKey = append(mlkemShared, sharedKey...) } c.curveID = hs.serverHello.serverShare.group earlySecret := hs.earlySecret if !hs.usingPSK { - earlySecret = hs.suite.extract(nil, nil) + earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, nil) } - handshakeSecret := hs.suite.extract(sharedKey, - hs.suite.deriveSecret(earlySecret, "derived", nil)) + handshakeSecret := earlySecret.HandshakeSecret(sharedKey) - clientSecret := hs.suite.deriveSecret(handshakeSecret, - clientHandshakeTrafficLabel, hs.transcript) + clientSecret := handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret) - serverSecret := hs.suite.deriveSecret(handshakeSecret, - serverHandshakeTrafficLabel, hs.transcript) + serverSecret := handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript) c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret) if c.quic != nil { @@ -553,8 +547,7 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error { return err } - hs.masterSecret = hs.suite.extract(nil, - hs.suite.deriveSecret(handshakeSecret, "derived", nil)) + hs.masterSecret = handshakeSecret.MasterSecret() return nil } @@ -614,9 +607,13 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error { return errors.New("tls: server accepted 0-RTT with the wrong ALPN") } } - if hs.echContext != nil && !hs.echContext.echRejected && encryptedExtensions.echRetryConfigs != nil { - c.sendAlert(alertUnsupportedExtension) - return errors.New("tls: server sent ECH retry configs after accepting ECH") + if hs.echContext != nil { + if hs.echContext.echRejected { + hs.echContext.retryConfigs = encryptedExtensions.echRetryConfigs + } else if encryptedExtensions.echRetryConfigs != nil { + c.sendAlert(alertUnsupportedExtension) + return errors.New("tls: server sent encrypted client hello retry configs after accepting encrypted client hello") + } } return nil @@ -769,10 +766,8 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error { // Derive secrets that take context through the server Finished. - hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret, - clientApplicationTrafficLabel, hs.transcript) - serverSecret := hs.suite.deriveSecret(hs.masterSecret, - serverApplicationTrafficLabel, hs.transcript) + hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript) + serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript) c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret) err = c.config.writeKeyLog(keyLogLabelClientTraffic, hs.hello.random, hs.trafficSecret) @@ -879,8 +874,7 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error { c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, hs.trafficSecret) if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil { - c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret, - resumptionLabel, hs.transcript) + c.resumptionSecret = hs.masterSecret.ResumptionMasterSecret(hs.transcript) } if c.quic != nil { @@ -924,7 +918,7 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error { return c.sendAlert(alertInternalError) } - psk := cipherSuite.expandLabel(c.resumptionSecret, "resumption", + psk := tls13.ExpandLabel(cipherSuite.hash.New, c.resumptionSecret, "resumption", msg.nonce, cipherSuite.hash.Size()) session := c.sessionState() diff --git a/crypto/tls/handshake_messages.go b/crypto/tls/handshake_messages.go index 236929d2c9c..9c1da459096 100644 --- a/crypto/tls/handshake_messages.go +++ b/crypto/tls/handshake_messages.go @@ -729,6 +729,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool { } m.pskBinders = append(m.pskBinders, binder) } + case extensionEncryptedClientHello: + if !extData.ReadBytes(&m.encryptedClientHello, len(extData)) { + return false + } default: buff := make([]byte, len(extData)) extData.CopyBytes(buff) diff --git a/crypto/tls/handshake_messages_test.go b/crypto/tls/handshake_messages_test.go index 7166d6a0c32..596e5bb9f48 100644 --- a/crypto/tls/handshake_messages_test.go +++ b/crypto/tls/handshake_messages_test.go @@ -239,6 +239,9 @@ func (*clientHelloMsg) Generate(rand *rand.Rand, size int) reflect.Value { if rand.Intn(10) > 5 { m.earlyData = true } + if rand.Intn(10) > 5 { + m.encryptedClientHello = randomBytes(rand.Intn(50)+1, rand) + } return reflect.ValueOf(m) } diff --git a/crypto/tls/handshake_server.go b/crypto/tls/handshake_server.go index d8175c8ff2c..be1f16ef865 100644 --- a/crypto/tls/handshake_server.go +++ b/crypto/tls/handshake_server.go @@ -6,43 +6,43 @@ package tls import ( "context" + "crypto" "errors" "fmt" "hash" + "internal/byteorder" "io" "time" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/ed25519" "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/crypto/subtle" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" "github.com/runZeroInc/excrypto/crypto/x509" - "github.com/runZeroInc/excrypto/internal/byteorder" ) // serverHandshakeState contains details of a server handshake in progress. // It's discarded once the handshake has completed. type serverHandshakeState struct { - c *Conn - ctx context.Context - clientHello *clientHelloMsg - hello *serverHelloMsg - suite *cipherSuite - ecdheOk bool - ecSignOk bool - rsaDecryptOk bool - rsaSignOk bool - sessionState *SessionState - finishedHash finishedHash - masterSecret []byte - preMasterSecret PreMasterSecret - cert *Certificate + c *Conn + ctx context.Context + clientHello *clientHelloMsg + hello *serverHelloMsg + suite *cipherSuite + ecdheOk bool + ecSignOk bool + rsaDecryptOk bool + rsaSignOk bool + sessionState *SessionState + finishedHash finishedHash + masterSecret []byte + cert *Certificate } // serverHandshake performs a TLS handshake as a server. func (c *Conn) serverHandshake(ctx context.Context) error { - clientHello, err := c.readClientHello(ctx) + clientHello, ech, err := c.readClientHello(ctx) if err != nil { return err } @@ -52,6 +52,7 @@ func (c *Conn) serverHandshake(ctx context.Context) error { c: c, ctx: ctx, clientHello: clientHello, + echContext: ech, } return hs.handshake() } @@ -132,17 +133,27 @@ func (hs *serverHandshakeState) handshake() error { } // readClientHello reads a ClientHello message and selects the protocol version. -func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { +func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, *echServerContext, error) { // clientHelloMsg is included in the transcript, but we haven't initialized // it yet. The respective handshake functions will record it themselves. msg, err := c.readHandshake(nil) if err != nil { - return nil, err + return nil, nil, err } clientHello, ok := msg.(*clientHelloMsg) if !ok { c.sendAlert(alertUnexpectedMessage) - return nil, unexpectedMessageError(clientHello, msg) + return nil, nil, unexpectedMessageError(clientHello, msg) + } + + // ECH processing has to be done before we do any other negotiation based on + // the contents of the client hello, since we may swap it out completely. + var ech *echServerContext + if len(clientHello.encryptedClientHello) != 0 { + clientHello, ech, err = c.processECHClientHello(clientHello) + if err != nil { + return nil, nil, err + } } var configForClient *Config @@ -151,7 +162,7 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { chi := clientHelloInfo(ctx, c, clientHello) if configForClient, err = c.config.GetConfigForClient(chi); err != nil { c.sendAlert(alertInternalError) - return nil, err + return nil, nil, err } else if configForClient != nil { c.config = configForClient } @@ -165,18 +176,30 @@ func (c *Conn) readClientHello(ctx context.Context) (*clientHelloMsg, error) { c.vers, ok = c.config.mutualVersion(roleServer, clientVersions) if !ok { c.sendAlert(alertProtocolVersion) - return nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) + return nil, nil, fmt.Errorf("tls: client offered only unsupported versions: %x", clientVersions) } c.haveVers = true c.in.version = c.vers c.out.version = c.vers + // This check reflects some odd specification implied behavior. Client-facing servers + // are supposed to reject hellos with outer ECH and inner ECH that offers 1.2, but + // backend servers are allowed to accept hellos with inner ECH that offer 1.2, since + // they cannot expect client-facing servers to behave properly. Since we act as both + // a client-facing and backend server, we only enforce 1.3 being negotiated if we + // saw a hello with outer ECH first. The spec probably should've made this an error, + // but it didn't, and this matches the boringssl behavior. + if c.vers != VersionTLS13 && (ech != nil && !ech.inner) { + c.sendAlert(alertIllegalParameter) + return nil, nil, errors.New("tls: Encrypted Client Hello cannot be used pre-TLS 1.3") + } + if c.config.MinVersion == 0 && c.vers < VersionTLS12 { tls10server.Value() // ensure godebug is initialized tls10server.IncNonDefault() } - return clientHello, nil + return clientHello, ech, nil } func (hs *serverHandshakeState) processClientHello() error { @@ -374,11 +397,11 @@ func (hs *serverHandshakeState) pickCipherSuite() error { } c.cipherSuite = hs.suite.id - if c.config.CipherSuites == nil && !needFIPS() && rsaKexCiphers[hs.suite.id] { + if c.config.CipherSuites == nil && !fips140tls.Required() && rsaKexCiphers[hs.suite.id] { tlsrsakex.Value() // ensure godebug is initialized tlsrsakex.IncNonDefault() } - if c.config.CipherSuites == nil && !needFIPS() && tdesCiphers[hs.suite.id] { + if c.config.CipherSuites == nil && !fips140tls.Required() && tdesCiphers[hs.suite.id] { tls3des.Value() // ensure godebug is initialized tls3des.IncNonDefault() } @@ -595,7 +618,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { } if skx != nil { if len(skx.key) >= 3 && skx.key[0] == 3 /* named curve */ { - c.curveID = CurveID(byteorder.BeUint16(skx.key[1:])) + c.curveID = CurveID(byteorder.BEUint16(skx.key[1:])) } if _, err := hs.c.writeHandshakeRecord(skx, &hs.finishedHash); err != nil { return err @@ -683,7 +706,7 @@ func (hs *serverHandshakeState) doFullHandshake() error { preMasterSecret, err := keyAgreement.processClientKeyExchange(c.config, hs.cert, ckx, c.vers) if err != nil { - c.sendAlert(alertHandshakeFailure) + c.sendAlert(alertIllegalParameter) return err } if hs.hello.extendedMasterSecret { @@ -925,7 +948,11 @@ func (c *Conn) processCertsFromClient(certificate Certificate) error { return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} } - c.verifiedChains = chains + c.verifiedChains, err = fipsAllowedChains(chains) + if err != nil { + c.sendAlert(alertBadCertificate) + return &CertificateVerificationError{UnverifiedCertificates: certs, Err: err} + } } c.peerCertificates = certs diff --git a/crypto/tls/handshake_server_test.go b/crypto/tls/handshake_server_test.go index 1812eecd72d..a015e5bae5e 100644 --- a/crypto/tls/handshake_server_test.go +++ b/crypto/tls/handshake_server_test.go @@ -7,6 +7,7 @@ package tls import ( "bytes" "context" + "crypto" "encoding/pem" "errors" "fmt" @@ -24,6 +25,13 @@ import ( "crypto/rand" + "github.com/runZeroInc/excrypto/crypto/ecdh" + "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" + "github.com/runZeroInc/excrypto/crypto/x509" + + "crypto/rand" + "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/ecdh" "github.com/runZeroInc/excrypto/crypto/elliptic" @@ -56,12 +64,13 @@ func testClientHelloFailure(t *testing.T, serverConfig *Config, m handshakeMessa }() ctx := context.Background() conn := Server(s, serverConfig) - ch, err := conn.readClientHello(ctx) + ch, ech, err := conn.readClientHello(ctx) if conn.vers == VersionTLS13 { hs := serverHandshakeStateTLS13{ c: conn, ctx: ctx, clientHello: ch, + echContext: ech, } if err == nil { err = hs.processClientHello() @@ -120,7 +129,7 @@ func TestRejectBadProtocolVersion(t *testing.T) { func TestNoSuiteOverlap(t *testing.T) { clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), cipherSuites: []uint16{0xff00}, compressionMethods: []uint8{compressionNone}, @@ -130,9 +139,9 @@ func TestNoSuiteOverlap(t *testing.T) { func TestNoCompressionOverlap(t *testing.T) { clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, compressionMethods: []uint8{0xff}, } testClientHelloFailure(t, testConfig, clientHello, "client does not support uncompressed connections") @@ -140,7 +149,7 @@ func TestNoCompressionOverlap(t *testing.T) { func TestNoRC4ByDefault(t *testing.T) { clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, compressionMethods: []uint8{compressionNone}, @@ -164,9 +173,9 @@ func TestDontSelectECDSAWithRSAKey(t *testing.T) { // Test that, even when both sides support an ECDSA cipher suite, it // won't be selected if the server's private key doesn't support it. clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA}, + cipherSuites: []uint16{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}, compressionMethods: []uint8{compressionNone}, supportedCurves: []CurveID{CurveP256}, supportedPoints: []uint8{pointFormatUncompressed}, @@ -190,9 +199,9 @@ func TestDontSelectRSAWithECDSAKey(t *testing.T) { // Test that, even when both sides support an RSA cipher suite, it // won't be selected if the server's private key doesn't support it. clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, + cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, compressionMethods: []uint8{compressionNone}, supportedCurves: []CurveID{CurveP256}, supportedPoints: []uint8{pointFormatUncompressed}, @@ -212,6 +221,8 @@ func TestDontSelectRSAWithECDSAKey(t *testing.T) { } func TestRenegotiationExtension(t *testing.T) { + skipFIPS(t) // #70505 + clientHello := &clientHelloMsg{ vers: VersionTLS12, compressionMethods: []uint8{compressionNone}, @@ -264,6 +275,8 @@ func TestRenegotiationExtension(t *testing.T) { } func TestTLS12OnlyCipherSuites(t *testing.T) { + skipFIPS(t) // No TLS 1.1 in FIPS mode. + // Test that a Server doesn't select a TLS 1.2-only cipher suite when // the client negotiates TLS 1.1. clientHello := &clientHelloMsg{ @@ -325,13 +338,18 @@ func TestTLSPointFormats(t *testing.T) { supportedPoints []uint8 wantSupportedPoints bool }{ - {"ECC", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{pointFormatUncompressed}, true}, - {"ECC without ec_point_format", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, nil, false}, - {"ECC with extra values", []uint16{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA}, []CurveID{CurveP256}, []uint8{13, 37, pointFormatUncompressed, 42}, true}, + {"ECC", []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, []CurveID{CurveP256}, []uint8{pointFormatUncompressed}, true}, + {"ECC without ec_point_format", []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, []CurveID{CurveP256}, nil, false}, + {"ECC with extra values", []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, []CurveID{CurveP256}, []uint8{13, 37, pointFormatUncompressed, 42}, true}, {"RSA", []uint16{TLS_RSA_WITH_AES_256_GCM_SHA384}, nil, nil, false}, {"RSA with ec_point_format", []uint16{TLS_RSA_WITH_AES_256_GCM_SHA384}, nil, []uint8{pointFormatUncompressed}, false}, } for _, tt := range tests { + // The RSA subtests should be enabled for FIPS 140 required mode: #70505 + if strings.HasPrefix(tt.name, "RSA") && fips140tls.Required() { + t.Logf("skipping in FIPS mode.") + continue + } t.Run(tt.name, func(t *testing.T) { clientHello := &clientHelloMsg{ vers: VersionTLS12, @@ -345,7 +363,9 @@ func TestTLSPointFormats(t *testing.T) { c, s := localPipe(t) replyChan := make(chan any) go func() { - cli := Client(c, testConfig) + clientConfig := testConfig.Clone() + clientConfig.Certificates = []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} + cli := Client(c, clientConfig) cli.vers = clientHello.vers if _, err := cli.writeHandshakeRecord(clientHello, nil); err != nil { testFatal(t, err) @@ -358,9 +378,10 @@ func TestTLSPointFormats(t *testing.T) { replyChan <- reply } }() - config := testConfig.Clone() - config.CipherSuites = clientHello.cipherSuites - Server(s, config).Handshake() + serverConfig := testConfig.Clone() + serverConfig.Certificates = []Certificate{{Certificate: [][]byte{testRSA2048Certificate}, PrivateKey: testRSA2048PrivateKey}} + serverConfig.CipherSuites = clientHello.cipherSuites + Server(s, serverConfig).Handshake() s.Close() reply := <-replyChan if err, ok := reply.(error); ok { @@ -435,6 +456,8 @@ func TestVersion(t *testing.T) { } func TestCipherSuitePreference(t *testing.T) { + skipFIPS(t) // No RC4 or CHACHA20_POLY1305 in FIPS mode. + serverConfig := &Config{ CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256}, @@ -503,15 +526,17 @@ func TestCrossVersionResume(t *testing.T) { func testCrossVersionResume(t *testing.T, version uint16) { serverConfig := &Config{ - CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, Certificates: testConfig.Certificates, + Time: testTime, } clientConfig := &Config{ - CipherSuites: []uint16{TLS_RSA_WITH_AES_128_CBC_SHA}, + CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, InsecureSkipVerify: true, ClientSessionCache: NewLRUClientSessionCache(1), ServerName: "servername", MinVersion: VersionTLS12, + Time: testTime, } // Establish a session at TLS 1.3. @@ -925,22 +950,6 @@ func TestHandshakeServerKeySharePreference(t *testing.T) { runServerTestTLS13(t, test) } -// TestHandshakeServerUnsupportedKeyShare tests a client that sends a key share -// that's not in the supported groups list. -func TestHandshakeServerUnsupportedKeyShare(t *testing.T) { - pk, _ := ecdh.X25519().GenerateKey(rand.Reader) - clientHello := &clientHelloMsg{ - vers: VersionTLS12, - random: make([]byte, 32), - supportedVersions: []uint16{VersionTLS13}, - cipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256}, - compressionMethods: []uint8{compressionNone}, - keyShares: []keyShare{{group: X25519, data: pk.PublicKey().Bytes()}}, - supportedCurves: []CurveID{CurveP256}, - } - testClientHelloFailure(t, testConfig, clientHello, "client sent key share for group it does not support") -} - func TestHandshakeServerALPN(t *testing.T) { config := testConfig.Clone() config.NextProtos = []string{"proto1", "proto2"} @@ -1082,16 +1091,16 @@ func TestHandshakeServerGetCertificateExtensions(t *testing.T) { testVersions := []uint16{VersionTLS12, VersionTLS13} for _, vers := range testVersions { t.Run(fmt.Sprintf("TLS version %04x", vers), func(t *testing.T) { - pk, _ := ecdh.X25519().GenerateKey(rand.Reader) + pk, _ := ecdh.P256().GenerateKey(rand.Reader) clientHello := &clientHelloMsg{ vers: vers, random: make([]byte, 32), - cipherSuites: []uint16{TLS_CHACHA20_POLY1305_SHA256}, + cipherSuites: []uint16{TLS_AES_128_GCM_SHA256}, compressionMethods: []uint8{compressionNone}, serverName: "test", - keyShares: []keyShare{{group: X25519, data: pk.PublicKey().Bytes()}}, - supportedCurves: []CurveID{X25519}, - supportedSignatureAlgorithms: []SignatureScheme{Ed25519}, + keyShares: []keyShare{{group: CurveP256, data: pk.PublicKey().Bytes()}}, + supportedCurves: []CurveID{CurveP256}, + supportedSignatureAlgorithms: []SignatureScheme{ECDSAWithP256AndSHA256}, } // the clientHelloMsg initialized just above is serialized with @@ -1140,9 +1149,9 @@ func TestHandshakeServerSNIGetCertificateError(t *testing.T) { } clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, compressionMethods: []uint8{compressionNone}, serverName: "test", } @@ -1161,9 +1170,9 @@ func TestHandshakeServerEmptyCertificates(t *testing.T) { serverConfig.Certificates = nil clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, compressionMethods: []uint8{compressionNone}, } testClientHelloFailure(t, serverConfig, clientHello, errMsg) @@ -1173,9 +1182,9 @@ func TestHandshakeServerEmptyCertificates(t *testing.T) { serverConfig.GetCertificate = nil clientHello = &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, compressionMethods: []uint8{compressionNone}, } testClientHelloFailure(t, serverConfig, clientHello, "no certificates") @@ -1498,9 +1507,9 @@ func TestSNIGivenOnFailure(t *testing.T) { const expectedServerName = "test.testing" clientHello := &clientHelloMsg{ - vers: VersionTLS10, + vers: VersionTLS12, random: make([]byte, 32), - cipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA}, + cipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256}, compressionMethods: []uint8{compressionNone}, serverName: expectedServerName, } @@ -1520,7 +1529,7 @@ func TestSNIGivenOnFailure(t *testing.T) { }() conn := Server(s, serverConfig) ctx := context.Background() - ch, err := conn.readClientHello(ctx) + ch, _, err := conn.readClientHello(ctx) hs := serverHandshakeState{ c: conn, ctx: ctx, @@ -1756,7 +1765,7 @@ T+E0J8wlH24pgwQHzy7Ko2qLwn1b5PW8ecrlvP1g func TestMultipleCertificates(t *testing.T) { clientConfig := testConfig.Clone() - clientConfig.CipherSuites = []uint16{TLS_RSA_WITH_AES_128_GCM_SHA256} + clientConfig.CipherSuites = []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256} clientConfig.MaxVersion = VersionTLS12 serverConfig := testConfig.Clone() @@ -1778,6 +1787,8 @@ func TestMultipleCertificates(t *testing.T) { } func TestAESCipherReordering(t *testing.T) { + skipFIPS(t) // No CHACHA20_POLY1305 for FIPS. + currentAESSupport := hasAESGCMHardwareSupport defer func() { hasAESGCMHardwareSupport = currentAESSupport }() @@ -1921,6 +1932,8 @@ func TestAESCipherReordering(t *testing.T) { } func TestAESCipherReorderingTLS13(t *testing.T) { + skipFIPS(t) // No CHACHA20_POLY1305 for FIPS. + currentAESSupport := hasAESGCMHardwareSupport defer func() { hasAESGCMHardwareSupport = currentAESSupport }() diff --git a/crypto/tls/handshake_server_tls13.go b/crypto/tls/handshake_server_tls13.go index 6e3b5fe82dc..9b289a41f39 100644 --- a/crypto/tls/handshake_server_tls13.go +++ b/crypto/tls/handshake_server_tls13.go @@ -7,17 +7,22 @@ package tls import ( "bytes" "context" + "crypto" "errors" "hash" + "internal/byteorder" "io" "slices" + "sort" "time" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/hmac" - "github.com/runZeroInc/excrypto/crypto/internal/mlkem768" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/hkdf" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" + "github.com/runZeroInc/excrypto/crypto/internal/hpke" "github.com/runZeroInc/excrypto/crypto/rsa" - "github.com/runZeroInc/excrypto/internal/byteorder" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" ) // maxClientPSKIdentities is the number of client PSK identities the server will @@ -25,6 +30,18 @@ import ( // messages cause too much work in session ticket decryption attempts. const maxClientPSKIdentities = 5 +type echServerContext struct { + hpkeContext *hpke.Receipient + configID uint8 + ciphersuite echCipher + transcript hash.Hash + // inner indicates that the initial client_hello we recieved contained an + // encrypted_client_hello extension that indicated it was an "inner" hello. + // We don't do any additional processing of the hello in this case, so all + // fields above are unset. + inner bool +} + type serverHandshakeStateTLS13 struct { c *Conn ctx context.Context @@ -36,13 +53,14 @@ type serverHandshakeStateTLS13 struct { suite *cipherSuiteTLS13 cert *Certificate sigAlg SignatureScheme - earlySecret []byte + earlySecret *tls13.EarlySecret sharedKey []byte - handshakeSecret []byte - masterSecret []byte + handshakeSecret *tls13.HandshakeSecret + masterSecret *tls13.MasterSecret trafficSecret []byte // client_application_traffic_secret_0 transcript hash.Hash clientFinished []byte + echContext *echServerContext } func (hs *serverHandshakeStateTLS13) handshake() error { @@ -162,7 +180,7 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { if !hasAESGCMHardwareSupport || !aesgcmPreferred(hs.clientHello.cipherSuites) { preferenceList = defaultCipherSuitesTLS13NoAES } - if needFIPS() { + if fips140tls.Required() { preferenceList = defaultCipherSuitesTLS13FIPS } for _, suiteID := range preferenceList { @@ -179,36 +197,44 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { hs.hello.cipherSuite = hs.suite.id hs.transcript = hs.suite.hash.New() - // Pick the key exchange method in server preference order, but give - // priority to key shares, to avoid a HelloRetryRequest round-trip. - var selectedGroup CurveID - var clientKeyShare *keyShare + // First, if a post-quantum key exchange is available, use one. See + // draft-ietf-tls-key-share-prediction-01, Section 4 for why this must be + // first. + // + // Second, if the client sent a key share for a group we support, use that, + // to avoid a HelloRetryRequest round-trip. + // + // Finally, pick in our fixed preference order. preferredGroups := c.config.curvePreferences(c.vers) - for _, preferredGroup := range preferredGroups { - ki := slices.IndexFunc(hs.clientHello.keyShares, func(ks keyShare) bool { - return ks.group == preferredGroup - }) - if ki != -1 { - clientKeyShare = &hs.clientHello.keyShares[ki] - selectedGroup = clientKeyShare.group - if !slices.Contains(hs.clientHello.supportedCurves, selectedGroup) { - c.sendAlert(alertIllegalParameter) - return errors.New("tls: client sent key share for group it does not support") - } - break - } + preferredGroups = slices.DeleteFunc(preferredGroups, func(group CurveID) bool { + return !slices.Contains(hs.clientHello.supportedCurves, group) + }) + if len(preferredGroups) == 0 { + c.sendAlert(alertHandshakeFailure) + return errors.New("tls: no key exchanges supported by both client and server") } - if selectedGroup == 0 { - for _, preferredGroup := range preferredGroups { - if slices.Contains(hs.clientHello.supportedCurves, preferredGroup) { - selectedGroup = preferredGroup - break + hasKeyShare := func(group CurveID) bool { + for _, ks := range hs.clientHello.keyShares { + if ks.group == group { + return true } } + return false } - if selectedGroup == 0 { - c.sendAlert(alertHandshakeFailure) - return errors.New("tls: no ECDHE curve supported by both client and server") + sort.SliceStable(preferredGroups, func(i, j int) bool { + return hasKeyShare(preferredGroups[i]) && !hasKeyShare(preferredGroups[j]) + }) + sort.SliceStable(preferredGroups, func(i, j int) bool { + return isPQKeyExchange(preferredGroups[i]) && !isPQKeyExchange(preferredGroups[j]) + }) + selectedGroup := preferredGroups[0] + + var clientKeyShare *keyShare + for _, ks := range hs.clientHello.keyShares { + if ks.group == selectedGroup { + clientKeyShare = &ks + break + } } if clientKeyShare == nil { ks, err := hs.doHelloRetryRequest(selectedGroup) @@ -221,13 +247,13 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { ecdhGroup := selectedGroup ecdhData := clientKeyShare.data - if selectedGroup == x25519Kyber768Draft00 { + if selectedGroup == X25519MLKEM768 { ecdhGroup = X25519 - if len(ecdhData) != x25519PublicKeySize+mlkem768.EncapsulationKeySize { + if len(ecdhData) != mlkem.EncapsulationKeySize768+x25519PublicKeySize { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid Kyber client key share") + return errors.New("tls: invalid X25519MLKEM768 client key share") } - ecdhData = ecdhData[:x25519PublicKeySize] + ecdhData = ecdhData[mlkem.EncapsulationKeySize768:] } if _, ok := curveForCurveID(ecdhGroup); !ok { c.sendAlert(alertInternalError) @@ -249,14 +275,24 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error { c.sendAlert(alertIllegalParameter) return errors.New("tls: invalid client key share") } - if selectedGroup == x25519Kyber768Draft00 { - ciphertext, kyberShared, err := kyberEncapsulate(clientKeyShare.data[x25519PublicKeySize:]) + if selectedGroup == X25519MLKEM768 { + k, err := mlkem.NewEncapsulationKey768(clientKeyShare.data[:mlkem.EncapsulationKeySize768]) if err != nil { c.sendAlert(alertIllegalParameter) - return errors.New("tls: invalid Kyber client key share") + return errors.New("tls: invalid X25519MLKEM768 client key share") } - hs.sharedKey = append(hs.sharedKey, kyberShared...) - hs.hello.serverShare.data = append(hs.hello.serverShare.data, ciphertext...) + mlkemSharedSecret, ciphertext := k.Encapsulate() + // draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.3: "For + // X25519MLKEM768, the shared secret is the concatenation of the ML-KEM + // shared secret and the X25519 shared secret. The shared secret is 64 + // bytes (32 bytes for each part)." + hs.sharedKey = append(mlkemSharedSecret, hs.sharedKey...) + // draft-kwiatkowski-tls-ecdhe-mlkem-02, Section 3.1.2: "When the + // X25519MLKEM768 group is negotiated, the server's key exchange value + // is the concatenation of an ML-KEM ciphertext returned from + // encapsulation to the client's encapsulation key, and the server's + // ephemeral X25519 share." + hs.hello.serverShare.data = append(ciphertext, hs.hello.serverShare.data...) } selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil) @@ -383,8 +419,8 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error { } } - hs.earlySecret = hs.suite.extract(sessionState.secret, nil) - binderKey := hs.suite.deriveSecret(hs.earlySecret, resumptionBinderLabel, nil) + hs.earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, sessionState.secret) + binderKey := hs.earlySecret.ResumptionBinderKey() // Clone the transcript in case a HelloRetryRequest was recorded. transcript := cloneHash(hs.transcript, hs.suite.hash) if transcript == nil { @@ -412,7 +448,7 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error { if err := transcriptMsg(hs.clientHello, transcript); err != nil { return err } - earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript) + earlyTrafficSecret := hs.earlySecret.ClientEarlyTrafficSecret(transcript) c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret) } @@ -530,6 +566,22 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) selectedGroup: selectedGroup, } + if hs.echContext != nil { + // Compute the acceptance message. + helloRetryRequest.encryptedClientHello = make([]byte, 8) + confTranscript := cloneHash(hs.transcript, hs.suite.hash) + if err := transcriptMsg(helloRetryRequest, confTranscript); err != nil { + return nil, err + } + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + hkdf.Extract(hs.suite.hash.New, hs.clientHello.random, nil), + "hrr ech accept confirmation", + confTranscript.Sum(nil), + 8, + ) + helloRetryRequest.encryptedClientHello = acceptConfirmation + } + if _, err := hs.c.writeHandshakeRecord(helloRetryRequest, hs.transcript); err != nil { return nil, err } @@ -550,6 +602,45 @@ func (hs *serverHandshakeStateTLS13) doHelloRetryRequest(selectedGroup CurveID) return nil, unexpectedMessageError(clientHello, msg) } + if hs.echContext != nil { + if len(clientHello.encryptedClientHello) == 0 { + c.sendAlert(alertMissingExtension) + return nil, errors.New("tls: second client hello missing encrypted client hello extension") + } + + echType, echCiphersuite, configID, encap, payload, err := parseECHExt(clientHello.encryptedClientHello) + if err != nil { + c.sendAlert(alertDecodeError) + return nil, errors.New("tls: client sent invalid encrypted client hello extension") + } + + if echType == outerECHExt && hs.echContext.inner || echType == innerECHExt && !hs.echContext.inner { + c.sendAlert(alertDecodeError) + return nil, errors.New("tls: unexpected switch in encrypted client hello extension type") + } + + if echType == outerECHExt { + if echCiphersuite != hs.echContext.ciphersuite || configID != hs.echContext.configID || len(encap) != 0 { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("tls: second client hello encrypted client hello extension does not match") + } + + encodedInner, err := decryptECHPayload(hs.echContext.hpkeContext, clientHello.original, payload) + if err != nil { + c.sendAlert(alertDecryptError) + return nil, errors.New("tls: failed to decrypt second client hello encrypted client hello extension payload") + } + + echInner, err := decodeInnerClientHello(clientHello, encodedInner) + if err != nil { + c.sendAlert(alertIllegalParameter) + return nil, errors.New("tls: client sent invalid encrypted client hello extension") + } + + clientHello = echInner + } + } + if len(clientHello.keyShares) != 1 { c.sendAlert(alertIllegalParameter) return nil, errors.New("tls: client didn't send one key share in second ClientHello") @@ -637,9 +728,27 @@ func illegalClientHelloChange(ch, ch1 *clientHelloMsg) bool { func (hs *serverHandshakeStateTLS13) sendServerParameters() error { c := hs.c + if hs.echContext != nil { + copy(hs.hello.random[32-8:], make([]byte, 8)) + echTranscript := cloneHash(hs.transcript, hs.suite.hash) + echTranscript.Write(hs.clientHello.original) + if err := transcriptMsg(hs.hello, echTranscript); err != nil { + return err + } + // compute the acceptance message + acceptConfirmation := tls13.ExpandLabel(hs.suite.hash.New, + hkdf.Extract(hs.suite.hash.New, hs.clientHello.random, nil), + "ech accept confirmation", + echTranscript.Sum(nil), + 8, + ) + copy(hs.hello.random[32-8:], acceptConfirmation) + } + if err := transcriptMsg(hs.clientHello, hs.transcript); err != nil { return err } + if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil { return err } @@ -650,16 +759,13 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { earlySecret := hs.earlySecret if earlySecret == nil { - earlySecret = hs.suite.extract(nil, nil) + earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, nil) } - hs.handshakeSecret = hs.suite.extract(hs.sharedKey, - hs.suite.deriveSecret(earlySecret, "derived", nil)) + hs.handshakeSecret = earlySecret.HandshakeSecret(hs.sharedKey) - clientSecret := hs.suite.deriveSecret(hs.handshakeSecret, - clientHandshakeTrafficLabel, hs.transcript) + clientSecret := hs.handshakeSecret.ClientHandshakeTrafficSecret(hs.transcript) c.in.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, clientSecret) - serverSecret := hs.suite.deriveSecret(hs.handshakeSecret, - serverHandshakeTrafficLabel, hs.transcript) + serverSecret := hs.handshakeSecret.ServerHandshakeTrafficSecret(hs.transcript) c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelHandshake, serverSecret) if c.quic != nil { @@ -693,6 +799,16 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error { encryptedExtensions.earlyData = hs.earlyData } + // If client sent ECH extension, but we didn't accept it, + // send retry configs, if available. + if len(hs.c.config.EncryptedClientHelloKeys) > 0 && len(hs.clientHello.encryptedClientHello) > 0 && hs.echContext == nil { + encryptedExtensions.echRetryConfigs, err = buildRetryConfigList(hs.c.config.EncryptedClientHelloKeys) + if err != nil { + c.sendAlert(alertInternalError) + return err + } + } + if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil { return err } @@ -784,13 +900,10 @@ func (hs *serverHandshakeStateTLS13) sendServerFinished() error { // Derive secrets that take context through the server Finished. - hs.masterSecret = hs.suite.extract(nil, - hs.suite.deriveSecret(hs.handshakeSecret, "derived", nil)) + hs.masterSecret = hs.handshakeSecret.MasterSecret() - hs.trafficSecret = hs.suite.deriveSecret(hs.masterSecret, - clientApplicationTrafficLabel, hs.transcript) - serverSecret := hs.suite.deriveSecret(hs.masterSecret, - serverApplicationTrafficLabel, hs.transcript) + hs.trafficSecret = hs.masterSecret.ClientApplicationTrafficSecret(hs.transcript) + serverSecret := hs.masterSecret.ServerApplicationTrafficSecret(hs.transcript) c.out.setTrafficSecret(hs.suite, QUICEncryptionLevelApplication, serverSecret) if c.quic != nil { @@ -837,12 +950,7 @@ func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool { } // Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9. - for _, pskMode := range hs.clientHello.pskModes { - if pskMode == pskModeDHE { - return true - } - } - return false + return slices.Contains(hs.clientHello.pskModes, pskModeDHE) } func (hs *serverHandshakeStateTLS13) sendSessionTickets() error { @@ -856,8 +964,7 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error { return err } - c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret, - resumptionLabel, hs.transcript) + c.resumptionSecret = hs.masterSecret.ResumptionMasterSecret(hs.transcript) if !hs.shouldSendSessionTickets() { return nil @@ -872,7 +979,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error { } // ticket_nonce, which must be unique per connection, is always left at // zero because we only ever send one ticket per connection. - psk := suite.expandLabel(c.resumptionSecret, "resumption", + psk := tls13.ExpandLabel(suite.hash.New, c.resumptionSecret, "resumption", nil, suite.hash.Size()) m := new(newSessionTicketMsgTLS13) @@ -907,7 +1014,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error { if _, err := c.config.rand().Read(ageAdd); err != nil { return err } - m.ageAdd = byteorder.LeUint32(ageAdd) + m.ageAdd = byteorder.LEUint32(ageAdd) if earlyData { // RFC 9001, Section 4.6.1 diff --git a/crypto/tls/handshake_test.go b/crypto/tls/handshake_test.go index 5c47acc94a4..c05db864ed2 100644 --- a/crypto/tls/handshake_test.go +++ b/crypto/tls/handshake_test.go @@ -50,6 +50,9 @@ var ( ) func runTestAndUpdateIfNeeded(t *testing.T, name string, run func(t *testing.T, update bool), wait bool) { + // FIPS mode is non-deterministic and so isn't suited for testing against static test transcripts. + skipFIPS(t) + success := t.Run(name, func(t *testing.T) { if !*update && !wait { t.Parallel() @@ -519,10 +522,21 @@ func fromHex(s string) []byte { return b } +// testTime is 2016-10-20T17:32:09.000Z, which is within the validity period of +// [testRSACertificate], [testRSACertificateIssuer], [testRSA2048Certificate], +// [testRSA2048CertificateIssuer], and [testECDSACertificate]. +var testTime = func() time.Time { return time.Unix(1476984729, 0) } + var testRSACertificate = fromHex("3082024b308201b4a003020102020900e8f09d3fe25beaa6300d06092a864886f70d01010b0500301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f74301e170d3136303130313030303030305a170d3235303130313030303030305a301a310b3009060355040a1302476f310b300906035504031302476f30819f300d06092a864886f70d010101050003818d0030818902818100db467d932e12270648bc062821ab7ec4b6a25dfe1e5245887a3647a5080d92425bc281c0be97799840fb4f6d14fd2b138bc2a52e67d8d4099ed62238b74a0b74732bc234f1d193e596d9747bf3589f6c613cc0b041d4d92b2b2423775b1c3bbd755dce2054cfa163871d1e24c4f31d1a508baab61443ed97a77562f414c852d70203010001a38193308190300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff0402300030190603551d0e041204109f91161f43433e49a6de6db680d79f60301b0603551d230414301280104813494d137e1631bba301d5acab6e7b30190603551d1104123010820e6578616d706c652e676f6c616e67300d06092a864886f70d01010b0500038181009d30cc402b5b50a061cbbae55358e1ed8328a9581aa938a495a1ac315a1a84663d43d32dd90bf297dfd320643892243a00bccf9c7db74020015faad3166109a276fd13c3cce10c5ceeb18782f16c04ed73bbb343778d0c1cf10fa1d8408361c94c722b9daedb4606064df4c1b33ec0d1bd42d4dbfe3d1360845c21d33be9fae7") var testRSACertificateIssuer = fromHex("3082021930820182a003020102020900ca5e4e811a965964300d06092a864886f70d01010b0500301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f74301e170d3136303130313030303030305a170d3235303130313030303030305a301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f7430819f300d06092a864886f70d010101050003818d0030818902818100d667b378bb22f34143b6cd2008236abefaf2852adf3ab05e01329e2c14834f5105df3f3073f99dab5442d45ee5f8f57b0111c8cb682fbb719a86944eebfffef3406206d898b8c1b1887797c9c5006547bb8f00e694b7a063f10839f269f2c34fff7a1f4b21fbcd6bfdfb13ac792d1d11f277b5c5b48600992203059f2a8f8cc50203010001a35d305b300e0603551d0f0101ff040403020204301d0603551d250416301406082b0601050507030106082b06010505070302300f0603551d130101ff040530030101ff30190603551d0e041204104813494d137e1631bba301d5acab6e7b300d06092a864886f70d01010b050003818100c1154b4bab5266221f293766ae4138899bd4c5e36b13cee670ceeaa4cbdf4f6679017e2fe649765af545749fe4249418a56bd38a04b81e261f5ce86b8d5c65413156a50d12449554748c59a30c515bc36a59d38bddf51173e899820b282e40aa78c806526fd184fb6b4cf186ec728edffa585440d2b3225325f7ab580e87dd76") +var testRSA2048Certificate = fromHex("30820316308201fea003020102020900e8f09d3fe25beaa6300d06092a864886f70d01010b0500301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f74301e170d3136303130313030303030305a170d3338303130313030303030305a301a310b3009060355040a1302476f310b300906035504031302476f30820122300d06092a864886f70d01010105000382010f003082010a0282010100e0ac47db9ba1b7f98a996c62dc1d248d4ee570544136fe4e911e22fccc0fe2b20982f3c4cdd8f4065c5068c873ca0a768b80dc915edc66541a5f26cdea44e56e411221e2f9927bf4e009fee76dbe0e118dcc13392efd6f42d8eb2fd5bc8f63ac77800c84d3be90c20c321273254b9137ef61f825dad1ec2c5e75aa4be6d3104899bd5ac400da7ab942b4227a3870ae5bb97870aa09a1082fb8e78b944cd7fd1b0c6fb1cce03b5430b12ef9ce2d95e01821766e998df0cc99202a57cf030577bd2dc0ec85a49f203511bb6f0e9f43398ead0958f8d7534c61e81daf4501faaa68d9cbc725b58401900fa48a3e2333b15c88cf0c5cc8f33fb9464f9d5f5768b8f10203010001a35a3058300e0603551d0f0101ff0404030205a0301d0603551d250416301406082b0601050507030106082b06010505070302300c0603551d130101ff0402300030190603551d1104123010820e6578616d706c652e676f6c616e67300d06092a864886f70d01010b050003820101009e83f835e2da08204ee6f8bdca793cf83c7aec175349c1642dfbe9f4d0dcfb1aedb4d0122e16c2ad92e63dd31cce10ca5dd04be48cded0fdc8fea49e891d9d93e778a67d54b619ac167ce7bb0f6000ca00c5677d09df3eb10080134ba32bfe4132d33954dc479cb266288d53d3f43af9c78c0ca59d396498bdc56d4966dc6b7e49081f7f2ae1d704bb9f9effed93c57d3b738da02edff3999e3f1a5dce2b093951947d233d9c6b6a12b4b1611826aa02544980089eebbcf22a1a96bd35a3ddf638578989334a93d5081fab442b4383ba6213b7cdd74110582244a2abd937828b311d8dd69178756db7874293b9810c5c2e833f91d49d283a62caaf359141997f") + +var testRSA2048CertificateIssuer = fromHex("308203223082020aa003020102020900ca5e4e811a965964300d06092a864886f70d01010b0500301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f74301e170d3136303130313030303030305a170d3235303130313030303030305a301f310b3009060355040a1302476f3110300e06035504031307476f20526f6f7430820122300d06092a864886f70d01010105000382010f003082010a0282010100b308c1720c7054abe66e1be6f8a11246808215a810e8936e47601f7ec1afeb02ad69a5000959d4e08ebc4455ef90b39616f380b8ff2e76f29942d7e009cf010824fe56f69140ac39b761595255ec2aa35155ca2eea884f57b25f8a52f41f56f65b0197cb6c637f9adfa97d8ac27565449f64e67f8b918646ffd630601b0badd8d38aea421fe413ee94f10ea5874c2fd6d8c1b9febaa5ca0ce759993a232c9c48e52230bbf58777b0c30e07e9e0914133730d844b9887b950d5a17c779ac69de2d9c65d26f1ea46c7dd7ac636af6d77df7c9218f78c7b5f08b025867f343ac66cd43a657ac44bfd7e9d07e95a22ff9a0babf72dcffc66eba0a1d90731f67e3bbd0203010001a361305f300e0603551d0f0101ff040403020204301d0603551d250416301406082b0601050507030106082b06010505070302300f0603551d130101ff040530030101ff301d0603551d0e0416041460145a6ce2e8a15b1b68db9a4752ce8684d6ba2d300d06092a864886f70d01010b050003820101001d342fe0b50a25d57a8b13bc14d0abb1eea7431ee752aa423e1306654183e44e9d48bbf592cd32ce77310fdc4e8bbcd724fc43d2723f454bfe605ff90d38d8c6fe60b36c6f4d2d7e4e79bceeb2484f0565274b0d0c4a8562370677624a4c133e332a9e63d4b47544c14e4908ee8685dd0760ae6f4ab089ede2b0cdc595ecefbee7d8be80d57b2d4e4510b6ceda54d1a5980540214191d81cc89a983da43d4043f8efe97a2e231c5153bded520acce87ec8c64a3408f0eb4c742c4a877e8b5b7b7f72497734a41a95994a7a103262ea6d598d03fd5cb0579ed4702424da8893334c58215bc655d49656aedcd02d18676f45d6b9469ae04b89abe9b358391cce99") + +var testRSA2048PrivateKey, _ = x509.ParsePKCS1PrivateKey(fromHex("308204a40201000282010100e0ac47db9ba1b7f98a996c62dc1d248d4ee570544136fe4e911e22fccc0fe2b20982f3c4cdd8f4065c5068c873ca0a768b80dc915edc66541a5f26cdea44e56e411221e2f9927bf4e009fee76dbe0e118dcc13392efd6f42d8eb2fd5bc8f63ac77800c84d3be90c20c321273254b9137ef61f825dad1ec2c5e75aa4be6d3104899bd5ac400da7ab942b4227a3870ae5bb97870aa09a1082fb8e78b944cd7fd1b0c6fb1cce03b5430b12ef9ce2d95e01821766e998df0cc99202a57cf030577bd2dc0ec85a49f203511bb6f0e9f43398ead0958f8d7534c61e81daf4501faaa68d9cbc725b58401900fa48a3e2333b15c88cf0c5cc8f33fb9464f9d5f5768b8f10203010001028201007aac96efca229b199e1bf79a63256677e1c455792bc2a348b2e409a68ea57dda486740430d4290bb885c3f5a741eb567d4f41f7b2098a726f4df4f88cf899edc7c9b31f584dffedece15a7212642c7dbbdd8d806392a183e1fc30af36169c9bab9e528f0bdcd27ad4c8b6a97849da6452c6809de61848db80c3ba3289e785042cdfd46fbfee5f78adcba2927fcd8cbe9dcaa97190457eaa45d77adbe0db820aff0c8511d837ab5b307bad5f85afd2cc70d9659ec58045d97ced1eb7950670ac559449c0305fddefda1bac88d36629a177f65abad182c6470830b39e7f6dbdef4df813ccaef01d5a42d37213b2b9647e2ff56a63e6b6a4b6e8a1567bbfd77042102818100eb66f205e8507c78f7167dbef3ddf02fde6a67bd15152609e9296576e28c79678177145ae98e0a2fee58fdb3d626fb6beae3e0ae0b76bc47d16fcdeb16f0caca8a0902779979382609705ae84514de480c2fb2ddda3049347cc1bde9f1a359747079ef3dce020a3c186c90e63bc20b5489a40d768b1c1c35c679edc5662e18c702818100f454ffff95b126b55cb13b68a3841600fc0bc69ff4064f7ceb122495fa972fdb05ca2fa1c6e2e84432f81c96875ab12226e8ce92ba808c4f6325f27ce058791f05db96e623687d3cfc198e748a07521a8c7ee9e7e8faf95b0985be82b867a49f7d5d50fac3881d2c39dedfdbca3ebe847b859c9864cf7a543e4688f5a60118870281806cee737ac65950704daeebbb8c701c709a54d4f28baa00b33f6137a1bf0e5033d4963d2620c3e8f4eb2fe51eee2f95d3079c31e1784e96ac093fdaa33a376d3032961ebd27990fa192669abab715041385082196461c6813d0d37ac5a25afbcf452937cb7ae438c63c6b28d651bae6b1550c446aa1cefd42e9388d0df6cdc80b02818100cac172c33504923bb494fad8e5c0a9c5dd63244bfe63f238969632b82700a95cd71c2694d887d9f92656d0da75ae640a1441e392cda3f94bb3da7cb4f6335527d2639c809467946e34423cfe26c0d6786398ba20922d1b1a59f79bd5bc937d8040b75c890c13fb298548977a3c05ff71cf535c54f66b5a77684a7e4363a3cb2702818100a4d782f35d5a07f9c1f8f9c378564b220387d1e481cc856b631de7637d8bb77c851db070122050ac230dc6e45edf4523471c717c1cb86a36b2fd3358fae349d51be54d71d7dbeaa6af668323e2b51933f0b8488aa12723e0f32207068b4aa64ed54bcef4acbbbe35b92802faba7ed45ae52bef8313d9ef4393ccc5cf868ddbf8")) + // testRSAPSSCertificate has signatureAlgorithm rsassaPss, but subjectPublicKeyInfo // algorithm rsaEncryption, for use with the rsa_pss_rsae_* SignatureSchemes. // See also TestRSAPSSKeyError. testRSAPSSCertificate is self-signed. diff --git a/crypto/tls/internal/fips140tls/fipstls.go b/crypto/tls/internal/fips140tls/fipstls.go new file mode 100644 index 00000000000..79655f75472 --- /dev/null +++ b/crypto/tls/internal/fips140tls/fipstls.go @@ -0,0 +1,38 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package fips140tls controls whether crypto/tls requires FIPS-approved settings. +package fips140tls + +import ( + "sync/atomic" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140" +) + +var required atomic.Bool + +func init() { + if fips140.Enabled { + Force() + } +} + +// Force forces crypto/tls to restrict TLS configurations to FIPS-approved settings. +// By design, this call is impossible to undo (except in tests). +func Force() { + required.Store(true) +} + +// Required reports whether FIPS-approved settings are required. +// +// Required is true if FIPS 140-3 mode is enabled with GODEBUG=fips140=on, or if +// the crypto/tls/fipsonly package is imported by a Go+BoringCrypto build. +func Required() bool { + return required.Load() +} + +func TestingOnlyAbandon() { + required.Store(false) +} diff --git a/crypto/tls/key_schedule.go b/crypto/tls/key_schedule.go index 111067ecae0..e40193bac05 100644 --- a/crypto/tls/key_schedule.go +++ b/crypto/tls/key_schedule.go @@ -6,94 +6,28 @@ package tls import ( "errors" - "fmt" - "github.com/runZeroInc/excrypto/crypto/ecdh" - "github.com/runZeroInc/excrypto/crypto/hmac" - "github.com/runZeroInc/excrypto/crypto/internal/mlkem768" "hash" "io" - "github.com/runZeroInc/excrypto/x/crypto/cryptobyte" - "github.com/runZeroInc/excrypto/x/crypto/hkdf" - "github.com/runZeroInc/excrypto/x/crypto/sha3" + "github.com/runZeroInc/excrypto/crypto/ecdh" + "github.com/runZeroInc/excrypto/crypto/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/mlkem" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" ) // This file contains the functions necessary to compute the TLS 1.3 key // schedule. See RFC 8446, Section 7. -const ( - resumptionBinderLabel = "res binder" - clientEarlyTrafficLabel = "c e traffic" - clientHandshakeTrafficLabel = "c hs traffic" - serverHandshakeTrafficLabel = "s hs traffic" - clientApplicationTrafficLabel = "c ap traffic" - serverApplicationTrafficLabel = "s ap traffic" - exporterLabel = "exp master" - resumptionLabel = "res master" - trafficUpdateLabel = "traffic upd" -) - -// expandLabel implements HKDF-Expand-Label from RFC 8446, Section 7.1. -func (c *cipherSuiteTLS13) expandLabel(secret []byte, label string, context []byte, length int) []byte { - var hkdfLabel cryptobyte.Builder - hkdfLabel.AddUint16(uint16(length)) - hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes([]byte("tls13 ")) - b.AddBytes([]byte(label)) - }) - hkdfLabel.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(context) - }) - hkdfLabelBytes, err := hkdfLabel.Bytes() - if err != nil { - // Rather than calling BytesOrPanic, we explicitly handle this error, in - // order to provide a reasonable error message. It should be basically - // impossible for this to panic, and routing errors back through the - // tree rooted in this function is quite painful. The labels are fixed - // size, and the context is either a fixed-length computed hash, or - // parsed from a field which has the same length limitation. As such, an - // error here is likely to only be caused during development. - // - // NOTE: another reasonable approach here might be to return a - // randomized slice if we encounter an error, which would break the - // connection, but avoid panicking. This would perhaps be safer but - // significantly more confusing to users. - panic(fmt.Errorf("failed to construct HKDF label: %s", err)) - } - out := make([]byte, length) - n, err := hkdf.Expand(c.hash.New, secret, hkdfLabelBytes).Read(out) - if err != nil || n != length { - panic("tls: HKDF-Expand-Label invocation failed unexpectedly") - } - return out -} - -// deriveSecret implements Derive-Secret from RFC 8446, Section 7.1. -func (c *cipherSuiteTLS13) deriveSecret(secret []byte, label string, transcript hash.Hash) []byte { - if transcript == nil { - transcript = c.hash.New() - } - return c.expandLabel(secret, label, transcript.Sum(nil), c.hash.Size()) -} - -// extract implements HKDF-Extract with the cipher suite hash. -func (c *cipherSuiteTLS13) extract(newSecret, currentSecret []byte) []byte { - if newSecret == nil { - newSecret = make([]byte, c.hash.Size()) - } - return hkdf.Extract(c.hash.New, newSecret, currentSecret) -} - // nextTrafficSecret generates the next traffic secret, given the current one, // according to RFC 8446, Section 7.2. func (c *cipherSuiteTLS13) nextTrafficSecret(trafficSecret []byte) []byte { - return c.expandLabel(trafficSecret, trafficUpdateLabel, nil, c.hash.Size()) + return tls13.ExpandLabel(c.hash.New, trafficSecret, "traffic upd", nil, c.hash.Size()) } // trafficKey generates traffic keys according to RFC 8446, Section 7.3. func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) { - key = c.expandLabel(trafficSecret, "key", nil, c.keyLen) - iv = c.expandLabel(trafficSecret, "iv", nil, aeadNonceLength) + key = tls13.ExpandLabel(c.hash.New, trafficSecret, "key", nil, c.keyLen) + iv = tls13.ExpandLabel(c.hash.New, trafficSecret, "iv", nil, aeadNonceLength) return } @@ -101,7 +35,7 @@ func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) { // to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey // selection. func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte { - finishedKey := c.expandLabel(baseKey, "finished", nil, c.hash.Size()) + finishedKey := tls13.ExpandLabel(c.hash.New, baseKey, "finished", nil, c.hash.Size()) verifyData := hmac.New(c.hash.New, finishedKey) verifyData.Write(transcript.Sum(nil)) return verifyData.Sum(nil) @@ -109,51 +43,17 @@ func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) [] // exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to // RFC 8446, Section 7.5. -func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript hash.Hash) func(string, []byte, int) ([]byte, error) { - expMasterSecret := c.deriveSecret(masterSecret, exporterLabel, transcript) +func (c *cipherSuiteTLS13) exportKeyingMaterial(s *tls13.MasterSecret, transcript hash.Hash) func(string, []byte, int) ([]byte, error) { + expMasterSecret := s.ExporterMasterSecret(transcript) return func(label string, context []byte, length int) ([]byte, error) { - secret := c.deriveSecret(expMasterSecret, label, nil) - h := c.hash.New() - h.Write(context) - return c.expandLabel(secret, "exporter", h.Sum(nil), length), nil + return expMasterSecret.Exporter(label, context, length), nil } } type keySharePrivateKeys struct { curveID CurveID ecdhe *ecdh.PrivateKey - kyber *mlkem768.DecapsulationKey -} - -// kyberDecapsulate implements decapsulation according to Kyber Round 3. -func kyberDecapsulate(dk *mlkem768.DecapsulationKey, c []byte) ([]byte, error) { - K, err := mlkem768.Decapsulate(dk, c) - if err != nil { - return nil, err - } - return kyberSharedSecret(K, c), nil -} - -// kyberEncapsulate implements encapsulation according to Kyber Round 3. -func kyberEncapsulate(ek []byte) (c, ss []byte, err error) { - c, ss, err = mlkem768.Encapsulate(ek) - if err != nil { - return nil, nil, err - } - return c, kyberSharedSecret(ss, c), nil -} - -func kyberSharedSecret(K, c []byte) []byte { - // Package mlkem768 implements ML-KEM, which compared to Kyber removed a - // final hashing step. Compute SHAKE-256(K || SHA3-256(c), 32) to match Kyber. - // See https://words.filippo.io/mlkem768/#bonus-track-using-a-ml-kem-implementation-as-kyber-v3. - h := sha3.NewShake256() - h.Write(K) - ch := sha3.Sum256(c) - h.Write(ch[:]) - out := make([]byte, 32) - h.Read(out) - return out + mlkem *mlkem.DecapsulationKey768 } const x25519PublicKeySize = 32 diff --git a/crypto/tls/key_schedule_test.go b/crypto/tls/key_schedule_test.go index 86daf303866..b23d4b8ad85 100644 --- a/crypto/tls/key_schedule_test.go +++ b/crypto/tls/key_schedule_test.go @@ -7,13 +7,80 @@ package tls import ( "bytes" "encoding/hex" - "github.com/runZeroInc/excrypto/crypto/internal/mlkem768" - "hash" "strings" "testing" "unicode" + + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls13" + "github.com/runZeroInc/excrypto/crypto/sha256" ) +func TestACVPVectors(t *testing.T) { + // https://github.com/usnistgov/ACVP-Server/blob/3a7333f63/gen-val/json-files/TLS-v1.3-KDF-RFC8446/prompt.json#L428-L436 + psk := fromHex("56288B726C73829F7A3E47B103837C8139ACF552E7530C7A710B35ED41191698") + dhe := fromHex("EFFE9EC26AA29FD750DFA6A10B944D74071595B27EE88887D5E11C84590B5CC3") + helloClientRandom := fromHex("E9137679E582BA7C1DB41CF725F86C6D09C8C05F297BAD9A65B552EAF524FDE4") + helloServerRandom := fromHex("23ECCFD030790748C8F8D8A656FD98D717F1B62AF3712F97211D2070B499F98A") + finishedClientRandom := fromHex("62A62FA75563ED4FDCAA0BC16567B314871C304ACF06B0FFC3F08C1797594D43") + finishedServerRandom := fromHex("C750EDA6696CD101B142BD79E00E6AC8C5F2C0ABC78DD64F4D991326659E9299") + + // https://github.com/usnistgov/ACVP-Server/blob/3a7333f63/gen-val/json-files/TLS-v1.3-KDF-RFC8446/expectedResults.json#L571-L581 + clientEarlyTrafficSecret := fromHex("3272189698C3594D18F58EFA3F12B638A249515099BE7A2FA9836BABE74F0111") + earlyExporterMasterSecret := fromHex("88E078F562CDC930219F6A5E98A1CE8C6E5F3DAC5AC516459A96F2EF8F114C66") + clientHandshakeTrafficSecret := fromHex("B32306C3CE9932C460A1FE6C0F060593974842036B96FA45049B7352E71C2AD2") + serverHandshakeTrafficSecret := fromHex("22787F8CA269D34BC549AC8BA19F2040938A3AA370D7CC9D60F720882B88D01B") + clientApplicationTrafficSecret := fromHex("47D7EA08397B5871154B0FE85584BCC30A87C69E84D69B56007C5B21F76493BA") + serverApplicationTrafficSecret := fromHex("EFBDB0C873C0480DA57307083839A8984BE25B9A8545E4FCA029940FE2800565") + exporterMasterSecret := fromHex("8A43D787EE3804EAD4A2A5B32972F9896B696295645D7222E1FD081DDD939834") + resumptionMasterSecret := fromHex("5F4C961329C91044011ACBECB0B289282E0E3FED045CB3EA924DFFE5FE654B3D") + + // The "Random" values are undocumented, but they are meant to be written to + // the hash in sequence to develop the transcript. + transcript := sha256.New() + + es := tls13.NewEarlySecret(sha256.New, psk) + + transcript.Write(helloClientRandom) + + if got := es.ClientEarlyTrafficSecret(transcript); !bytes.Equal(got, clientEarlyTrafficSecret) { + t.Errorf("clientEarlyTrafficSecret = %x, want %x", got, clientEarlyTrafficSecret) + } + if got := tls13.TestingOnlyExporterSecret(es.EarlyExporterMasterSecret(transcript)); !bytes.Equal(got, earlyExporterMasterSecret) { + t.Errorf("earlyExporterMasterSecret = %x, want %x", got, earlyExporterMasterSecret) + } + + hs := es.HandshakeSecret(dhe) + + transcript.Write(helloServerRandom) + + if got := hs.ClientHandshakeTrafficSecret(transcript); !bytes.Equal(got, clientHandshakeTrafficSecret) { + t.Errorf("clientHandshakeTrafficSecret = %x, want %x", got, clientHandshakeTrafficSecret) + } + if got := hs.ServerHandshakeTrafficSecret(transcript); !bytes.Equal(got, serverHandshakeTrafficSecret) { + t.Errorf("serverHandshakeTrafficSecret = %x, want %x", got, serverHandshakeTrafficSecret) + } + + ms := hs.MasterSecret() + + transcript.Write(finishedServerRandom) + + if got := ms.ClientApplicationTrafficSecret(transcript); !bytes.Equal(got, clientApplicationTrafficSecret) { + t.Errorf("clientApplicationTrafficSecret = %x, want %x", got, clientApplicationTrafficSecret) + } + if got := ms.ServerApplicationTrafficSecret(transcript); !bytes.Equal(got, serverApplicationTrafficSecret) { + t.Errorf("serverApplicationTrafficSecret = %x, want %x", got, serverApplicationTrafficSecret) + } + if got := tls13.TestingOnlyExporterSecret(ms.ExporterMasterSecret(transcript)); !bytes.Equal(got, exporterMasterSecret) { + t.Errorf("exporterMasterSecret = %x, want %x", got, exporterMasterSecret) + } + + transcript.Write(finishedClientRandom) + + if got := ms.ResumptionMasterSecret(transcript); !bytes.Equal(got, resumptionMasterSecret) { + t.Errorf("resumptionMasterSecret = %x, want %x", got, resumptionMasterSecret) + } +} + // This file contains tests derived from draft-ietf-tls-tls13-vectors-07. func parseVector(v string) []byte { @@ -32,78 +99,6 @@ func parseVector(v string) []byte { return res } -func TestDeriveSecret(t *testing.T) { - chTranscript := cipherSuitesTLS13[0].hash.New() - chTranscript.Write(parseVector(` - payload (512 octets): 01 00 01 fc 03 03 1b c3 ce b6 bb e3 9c ff - 93 83 55 b5 a5 0a db 6d b2 1b 7a 6a f6 49 d7 b4 bc 41 9d 78 76 - 48 7d 95 00 00 06 13 01 13 03 13 02 01 00 01 cd 00 00 00 0b 00 - 09 00 00 06 73 65 72 76 65 72 ff 01 00 01 00 00 0a 00 14 00 12 - 00 1d 00 17 00 18 00 19 01 00 01 01 01 02 01 03 01 04 00 33 00 - 26 00 24 00 1d 00 20 e4 ff b6 8a c0 5f 8d 96 c9 9d a2 66 98 34 - 6c 6b e1 64 82 ba dd da fe 05 1a 66 b4 f1 8d 66 8f 0b 00 2a 00 - 00 00 2b 00 03 02 03 04 00 0d 00 20 00 1e 04 03 05 03 06 03 02 - 03 08 04 08 05 08 06 04 01 05 01 06 01 02 01 04 02 05 02 06 02 - 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01 00 15 00 57 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 - 00 29 00 dd 00 b8 00 b2 2c 03 5d 82 93 59 ee 5f f7 af 4e c9 00 - 00 00 00 26 2a 64 94 dc 48 6d 2c 8a 34 cb 33 fa 90 bf 1b 00 70 - ad 3c 49 88 83 c9 36 7c 09 a2 be 78 5a bc 55 cd 22 60 97 a3 a9 - 82 11 72 83 f8 2a 03 a1 43 ef d3 ff 5d d3 6d 64 e8 61 be 7f d6 - 1d 28 27 db 27 9c ce 14 50 77 d4 54 a3 66 4d 4e 6d a4 d2 9e e0 - 37 25 a6 a4 da fc d0 fc 67 d2 ae a7 05 29 51 3e 3d a2 67 7f a5 - 90 6c 5b 3f 7d 8f 92 f2 28 bd a4 0d da 72 14 70 f9 fb f2 97 b5 - ae a6 17 64 6f ac 5c 03 27 2e 97 07 27 c6 21 a7 91 41 ef 5f 7d - e6 50 5e 5b fb c3 88 e9 33 43 69 40 93 93 4a e4 d3 57 fa d6 aa - cb 00 21 20 3a dd 4f b2 d8 fd f8 22 a0 ca 3c f7 67 8e f5 e8 8d - ae 99 01 41 c5 92 4d 57 bb 6f a3 1b 9e 5f 9d`)) - - type args struct { - secret []byte - label string - transcript hash.Hash - } - tests := []struct { - name string - args args - want []byte - }{ - { - `derive secret for handshake "tls13 derived"`, - args{ - parseVector(`PRK (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c e2 - 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a`), - "derived", - nil, - }, - parseVector(`expanded (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba - b6 97 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba`), - }, - { - `derive secret "tls13 c e traffic"`, - args{ - parseVector(`PRK (32 octets): 9b 21 88 e9 b2 fc 6d 64 d7 1d c3 29 90 0e 20 bb - 41 91 50 00 f6 78 aa 83 9c bb 79 7c b7 d8 33 2c`), - "c e traffic", - chTranscript, - }, - parseVector(`expanded (32 octets): 3f bb e6 a6 0d eb 66 c3 0a 32 79 5a ba 0e - ff 7e aa 10 10 55 86 e7 be 5c 09 67 8d 63 b6 ca ab 62`), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := cipherSuitesTLS13[0] - if got := c.deriveSecret(tt.args.secret, tt.args.label, tt.args.transcript); !bytes.Equal(got, tt.want) { - t.Errorf("cipherSuiteTLS13.deriveSecret() = % x, want % x", got, tt.want) - } - }) - } -} - func TestTrafficKey(t *testing.T) { trafficSecret := parseVector( `PRK (32 octets): b6 7b 7d 69 0c c1 6c 4e 75 e5 42 13 cb 2d 37 b4 @@ -123,90 +118,3 @@ func TestTrafficKey(t *testing.T) { t.Errorf("cipherSuiteTLS13.trafficKey() gotIV = % x, want % x", gotIV, wantIV) } } - -func TestExtract(t *testing.T) { - type args struct { - newSecret []byte - currentSecret []byte - } - tests := []struct { - name string - args args - want []byte - }{ - { - `extract secret "early"`, - args{ - nil, - nil, - }, - parseVector(`secret (32 octets): 33 ad 0a 1c 60 7e c0 3b 09 e6 cd 98 93 68 0c - e2 10 ad f3 00 aa 1f 26 60 e1 b2 2e 10 f1 70 f9 2a`), - }, - { - `extract secret "master"`, - args{ - nil, - parseVector(`salt (32 octets): 43 de 77 e0 c7 77 13 85 9a 94 4d b9 db 25 90 b5 - 31 90 a6 5b 3e e2 e4 f1 2d d7 a0 bb 7c e2 54 b4`), - }, - parseVector(`secret (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a - 47 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19`), - }, - { - `extract secret "handshake"`, - args{ - parseVector(`IKM (32 octets): 8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d - 35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d`), - parseVector(`salt (32 octets): 6f 26 15 a1 08 c7 02 c5 67 8f 54 fc 9d ba b6 97 - 16 c0 76 18 9c 48 25 0c eb ea c3 57 6c 36 11 ba`), - }, - parseVector(`secret (32 octets): 1d c8 26 e9 36 06 aa 6f dc 0a ad c1 2f 74 1b - 01 04 6a a6 b9 9f 69 1e d2 21 a9 f0 ca 04 3f be ac`), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - c := cipherSuitesTLS13[0] - if got := c.extract(tt.args.newSecret, tt.args.currentSecret); !bytes.Equal(got, tt.want) { - t.Errorf("cipherSuiteTLS13.extract() = % x, want % x", got, tt.want) - } - }) - } -} - -func TestKyberDecapsulate(t *testing.T) { - // From https://pq-crystals.org/kyber/data/kyber-submission-nist-round3.zip - dkBytes, _ := hex.DecodeString("07638FB69868F3D320E5862BD96933FEB311B362093C9B5D50170BCED43F1B536D9A204BB1F22695950BA1F2A9E8EB828B284488760B3FC84FABA04275D5628E39C5B2471374283C503299C0AB49B66B8BBB56A4186624F919A2BA59BB08D8551880C2BEFC4F87F25F59AB587A79C327D792D54C974A69262FF8A78938289E9A87B688B083E0595FE218B6BB1505941CE2E81A5A64C5AAC60417256985349EE47A52420A5F97477B7236AC76BC70E8288729287EE3E34A3DBC3683C0B7B10029FC203418537E7466BA6385A8FF301EE12708F82AAA1E380FC7A88F8F205AB7E88D7E95952A55BA20D09B79A47141D62BF6EB7DD307B08ECA13A5BC5F6B68581C6865B27BBCDDAB142F4B2CBFF488C8A22705FAA98A2B9EEA3530C76662335CC7EA3A00777725EBCCCD2A4636B2D9122FF3AB77123CE0883C1911115E50C9E8A94194E48DD0D09CFFB3ADCD2C1E92430903D07ADBF00532031575AA7F9E7B5A1F3362DEC936D4043C05F2476C07578BC9CBAF2AB4E382727AD41686A96B2548820BB03B32F11B2811AD62F489E951632ABA0D1DF89680CC8A8B53B481D92A68D70B4EA1C3A6A561C0692882B5CA8CC942A8D495AFCB06DE89498FB935B775908FE7A03E324D54CC19D4E1AABD3593B38B19EE1388FE492B43127E5A504253786A0D69AD32601C28E2C88504A5BA599706023A61363E17C6B9BB59BDC697452CD059451983D738CA3FD034E3F5988854CA05031DB09611498988197C6B30D258DFE26265541C89A4B31D6864E9389B03CB74F7EC4323FB9421A4B9790A26D17B0398A26767350909F84D57B6694DF830664CA8B3C3C03ED2AE67B89006868A68527CCD666459AB7F056671000C6164D3A7F266A14D97CBD7004D6C92CACA770B844A4FA9B182E7B18CA885082AC5646FCB4A14E1685FEB0C9CE3372AB95365C04FD83084F80A23FF10A05BF15F7FA5ACC6C0CB462C33CA524FA6B8BB359043BA68609EAA2536E81D08463B19653B5435BA946C9ADDEB202B04B031CC960DCC12E4518D428B32B257A4FC7313D3A7980D80082E934F9D95C32B0A0191A23604384DD9E079BBBAA266D14C3F756B9F2133107433A4E83FA7187282A809203A4FAF841851833D121AC383843A5E55BC2381425E16C7DB4CC9AB5C1B0D91A47E2B8DE0E582C86B6B0D907BB360B97F40AB5D038F6B75C814B27D9B968D419832BC8C2BEE605EF6E5059D33100D90485D378450014221736C07407CAC260408AA64926619788B8601C2A752D1A6CBF820D7C7A04716203225B3895B9342D147A8185CFC1BB65BA06B4142339903C0AC4651385B45D98A8B19D28CD6BAB088787F7EE1B12461766B43CBCCB96434427D93C065550688F6948ED1B5475A425F1B85209D061C08B56C1CC069F6C0A7C6F29358CAB911087732A649D27C9B98F9A48879387D9B00C25959A71654D6F6A946164513E47A75D005986C2363C09F6B537ECA78B9303A5FA457608A586A653A347DB04DFCC19175B3A301172536062A658A95277570C8852CA8973F4AE123A334047DD711C8927A634A03388A527B034BF7A8170FA702C1F7C23EC32D18A2374890BE9C787A9409C82D192C4BB705A2F996CE405DA72C2D9C843EE9F8313ECC7F86D6294D59159D9A879A542E260922ADF999051CC45200C9FFDB60449C49465979272367C083A7D6267A3ED7A7FD47957C219327F7CA73A4007E1627F00B11CC80573C15AEE6640FB8562DFA6B240CA0AD351AC4AC155B96C14C8AB13DD262CDFD51C4BB5572FD616553D17BDD430ACBEA3E95F0B698D66990AB51E5D03783A8B3D278A5720454CF9695CFDCA08485BA099C51CD92A7EA7587C1D15C28E609A81852601B0604010679AA482D51261EC36E36B8719676217FD74C54786488F4B4969C05A8BA27CA3A77CCE73B965923CA554E422B9B61F4754641608AC16C9B8587A32C1C5DD788F88B36B717A46965635DEB67F45B129B99070909C93EB80B42C2B3F3F70343A7CF37E8520E7BCFC416ACA4F18C7981262BA2BFC756AE03278F0EC66DC2057696824BA6769865A601D7148EF6F54E5AF5686AA2906F994CE38A5E0B938F239007003022C03392DF3401B1E4A3A7EBC6161449F73374C8B0140369343D9295FDF511845C4A46EBAAB6CA5492F6800B98C0CC803653A4B1D6E6AAED1932BACC5FEFAA818BA502859BA5494C5F5402C8536A9C4C1888150617F80098F6B2A99C39BC5DC7CF3B5900A21329AB59053ABAA64ED163E859A8B3B3CA3359B750CCC3E710C7AC43C8191CB5D68870C06391C0CB8AEC72B897AC6BE7FBAACC676ED66314C83630E89448C88A1DF04ACEB23ABF2E409EF333C622289C18A2134E650C45257E47475FA33AA537A5A8F7680214716C50D470E3284963CA64F54677AEC54B5272162BF52BC8142E1D4183FC017454A6B5A496831759064024745978CBD51A6CEDC8955DE4CC6D363670A47466E82BE5C23603A17BF22ACDB7CC984AF08C87E14E27753CF587A8EC3447E62C649E887A67C36C9CE98721B697213275646B194F36758673A8ED11284455AFC7A8529F69C97A3C2D7B8C636C0BA55614B768E624E712930F776169B01715725351BC74B47395ED52B25A1313C95164814C34C979CBDFAB85954662CAB485E75087A98CC74BB82CA2D1B5BF2803238480638C40E90B43C7460E7AA917F010151FAB1169987B372ABB59271F7006C24E60236B84B9DDD600623704254617FB498D89E58B0368BCB2103E79353EB587860C1422E476162E425BC2381DB82C6592737E1DD602864B0167A71EC1F223305C02FE25052AF2B3B5A55A0D7A2022D9A798DC0C5874A98702AAF4054C5D80338A5248B5B7BD09C53B5E2A084B047D277A861B1A73BB51488DE04EF573C85230A0470B73175C9FA50594F66A5F50B4150054C93B68186F8B5CBC49316C8548A642B2B36A1D454C7489AC33B2D2CE6668096782A2C1E0866D21A65E16B585E7AF8618BDF3184C1986878508917277B93E10706B1614972B2A94C7310FE9C708C231A1A8AC8D9314A529A97F469BF64962D820648443099A076D55D4CEA824A58304844F99497C10A25148618A315D72CA857D1B04D575B94F85C01D19BEF211BF0AA3362E7041FD16596D808E867B44C4C00D1CDA3418967717F147D0EB21B42AAEE74AC35D0B92414B958531AADF463EC6305AE5ECAF79174002F26DDECC813BF32672E8529D95A4E730A7AB4A3E8F8A8AF979A665EAFD465FC64A0C5F8F3F9003489415899D59A543D8208C54A3166529B53922D4EC143B50F01423B177895EDEE22BB739F647ECF85F50BC25EF7B5A725DEE868626ED79D451140800E03B59B956F8210E556067407D13DC90FA9E8B872BFB8F") - dk, err := mlkem768.NewKeyFromExtendedEncoding(dkBytes) - if err != nil { - t.Fatal(err) - } - ct, _ := hex.DecodeString("B52C56B92A4B7CE9E4CB7C5B1B163167A8A1675B2FDEF84A5B67CA15DB694C9F11BD027C30AE22EC921A1D911599AF0585E48D20DA70DF9F39E32EF95D4C8F44BFEFDAA5DA64F1054631D04D6D3CFD0A540DD7BA3886E4B5F13E878788604C95C096EAB3919F427521419A946C26CC041475D7124CDC01D0373E5B09C7A70603CFDB4FB3405023F2264DC3F983C4FC02A2D1B268F2208A1F6E2A6209BFF12F6F465F0B069C3A7F84F606D8A94064003D6EC114C8E808D3053884C1D5A142FBF20112EB360FDA3F0F28B172AE50F5E7D83801FB3F0064B687187074BD7FE30EDDAA334CF8FC04FA8CED899CEADE4B4F28B68372BAF98FF482A415B731155B75CEB976BE0EA0285BA01A27F1857A8FB377A3AE0C23B2AA9A079BFABFF0D5B2F1CD9B718BEA03C42F343A39B4F142D01AD8ACBB50E38853CF9A50C8B44C3CF671A4A9043B26DDBB24959AD6715C08521855C79A23B9C3D6471749C40725BDD5C2776D43AED20204BAA141EFB3304917474B7F9F7A4B08B1A93DAED98C67495359D37D67F7438BEE5E43585634B26C6B3810D7CDCBC0F6EB877A6087E68ACB8480D3A8CF6900447E49B417F15A53B607A0E216B855970D37406870B4568722DA77A4084703816784E2F16BED18996532C5D8B7F5D214464E5F3F6E905867B0CE119E252A66713253544685D208E1723908A0CE97834652E08AE7BDC881A131B73C71E84D20D68FDEFF4F5D70CD1AF57B78E3491A9865942321800A203C05ED1FEEB5A28E584E19F6535E7F84E4A24F84A72DCAF5648B4A4235DD664464482F03176E888C28BFC6C1CB238CFFA35A321E71791D9EA8ED0878C61121BF8D2A4AB2C1A5E120BC40ABB1892D1715090A0EE48252CA297A99AA0E510CF26B1ADD06CA543E1C5D6BDCD3B9C585C8538045DB5C252EC3C8C3C954D9BE5907094A894E60EAB43538CFEE82E8FFC0791B0D0F43AC1627830A61D56DAD96C62958B0DE780B78BD47A604550DAB83FFF227C324049471F35248CFB849B25724FF704D5277AA352D550958BE3B237DFF473EC2ADBAEA48CA2658AEFCC77BBD4264AB374D70EAE5B964416CE8226A7E3255A0F8D7E2ADCA062BCD6D78D60D1B32E11405BE54B66EF0FDDD567702A3BCCFEDE3C584701269ED14809F06F8968356BB9267FE86E514252E88BB5C30A7ECB3D0E621021EE0FBF7871B09342BF84F55C97EAF86C48189C7FF4DF389F077E2806E5FA73B3E9458A16C7E275F4F602275580EB7B7135FB537FA0CD95D6EA58C108CD8943D70C1643111F4F01CA8A8276A902666ED81B78D168B006F16AAA3D8E4CE4F4D0FB0997E41AEFFB5B3DAA838732F357349447F387776C793C0479DE9E99498CC356FDB0075A703F23C55D47B550EC89B02ADE89329086A50843456FEDC3788AC8D97233C54560467EE1D0F024B18428F0D73B30E19F5C63B9ABF11415BEA4D0170130BAABD33C05E6524E5FB5581B22B0433342248266D0F1053B245CC2462DC44D34965102482A8ED9E4E964D5683E5D45D0C8269") - ss, err := kyberDecapsulate(dk, ct) - if err != nil { - t.Fatal(err) - } - exp, _ := hex.DecodeString("914CB67FE5C38E73BF74181C0AC50428DEDF7750A98058F7D536708774535B29") - if !bytes.Equal(ss, exp) { - t.Fatalf("got %x, want %x", ss, exp) - } -} - -func TestKyberEncapsulate(t *testing.T) { - dk, err := mlkem768.GenerateKey() - if err != nil { - t.Fatal(err) - } - ct, ss, err := kyberEncapsulate(dk.EncapsulationKey()) - if err != nil { - t.Fatal(err) - } - dkSS, err := kyberDecapsulate(dk, ct) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(ss, dkSS) { - t.Fatalf("got %x, want %x", ss, dkSS) - } -} diff --git a/crypto/tls/notboring.go b/crypto/tls/notboring.go deleted file mode 100644 index 39db9ee2341..00000000000 --- a/crypto/tls/notboring.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tls - -func needFIPS() bool { return false } diff --git a/crypto/tls/prf.go b/crypto/tls/prf.go index a6bc92335ba..832be251179 100644 --- a/crypto/tls/prf.go +++ b/crypto/tls/prf.go @@ -5,18 +5,21 @@ package tls import ( + "crypto" "errors" "fmt" "hash" - "github.com/runZeroInc/excrypto/crypto" "github.com/runZeroInc/excrypto/crypto/hmac" + "github.com/runZeroInc/excrypto/crypto/internal/fips140/tls12" "github.com/runZeroInc/excrypto/crypto/md5" "github.com/runZeroInc/excrypto/crypto/sha1" "github.com/runZeroInc/excrypto/crypto/sha256" "github.com/runZeroInc/excrypto/crypto/sha512" ) +type prfFunc func(secret []byte, label string, seed []byte, keyLen int) []byte + // Split a premaster secret in two as specified in RFC 4346, Section 5. func splitPreMasterSecret(secret []byte) (s1, s2 []byte) { s1 = secret[0 : (len(secret)+1)/2] @@ -46,7 +49,8 @@ func pHash(result, secret, seed []byte, hash func() hash.Hash) { } // prf10 implements the TLS 1.0 pseudo-random function, as defined in RFC 2246, Section 5. -func prf10(result, secret, label, seed []byte) { +func prf10(secret []byte, label string, seed []byte, keyLen int) []byte { + result := make([]byte, keyLen) hashSHA1 := sha1.New hashMD5 := md5.New @@ -62,16 +66,14 @@ func prf10(result, secret, label, seed []byte) { for i, b := range result2 { result[i] ^= b } + + return result } // prf12 implements the TLS 1.2 pseudo-random function, as defined in RFC 5246, Section 5. -func prf12(hashFunc func() hash.Hash) func(result, secret, label, seed []byte) { - return func(result, secret, label, seed []byte) { - labelAndSeed := make([]byte, len(label)+len(seed)) - copy(labelAndSeed, label) - copy(labelAndSeed[len(label):], seed) - - pHash(result, secret, labelAndSeed, hashFunc) +func prf12(hashFunc func() hash.Hash) prfFunc { + return func(secret []byte, label string, seed []byte, keyLen int) []byte { + return tls12.PRF(hashFunc, secret, label, seed, keyLen) } } @@ -80,13 +82,13 @@ const ( finishedVerifyLength = 12 // Length of verify_data in a Finished message. ) -var masterSecretLabel = []byte("master secret") -var extendedMasterSecretLabel = []byte("extended master secret") -var keyExpansionLabel = []byte("key expansion") -var clientFinishedLabel = []byte("client finished") -var serverFinishedLabel = []byte("server finished") +const masterSecretLabel = "master secret" +const extendedMasterSecretLabel = "extended master secret" +const keyExpansionLabel = "key expansion" +const clientFinishedLabel = "client finished" +const serverFinishedLabel = "server finished" -func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secret, label, seed []byte), crypto.Hash) { +func prfAndHashForVersion(version uint16, suite *cipherSuite) (prfFunc, crypto.Hash) { switch version { case VersionTLS10, VersionTLS11: return prf10, crypto.Hash(0) @@ -100,7 +102,7 @@ func prfAndHashForVersion(version uint16, suite *cipherSuite) (func(result, secr } } -func prfForVersion(version uint16, suite *cipherSuite) func(result, secret, label, seed []byte) { +func prfForVersion(version uint16, suite *cipherSuite) prfFunc { prf, _ := prfAndHashForVersion(version, suite) return prf } @@ -112,17 +114,19 @@ func masterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecr seed = append(seed, clientRandom...) seed = append(seed, serverRandom...) - masterSecret := make([]byte, masterSecretLength) - prfForVersion(version, suite)(masterSecret, preMasterSecret, masterSecretLabel, seed) - return masterSecret + return prfForVersion(version, suite)(preMasterSecret, masterSecretLabel, seed, masterSecretLength) } // extMasterFromPreMasterSecret generates the extended master secret from the // pre-master secret. See RFC 7627. func extMasterFromPreMasterSecret(version uint16, suite *cipherSuite, preMasterSecret, transcript []byte) []byte { - masterSecret := make([]byte, masterSecretLength) - prfForVersion(version, suite)(masterSecret, preMasterSecret, extendedMasterSecretLabel, transcript) - return masterSecret + prf, hash := prfAndHashForVersion(version, suite) + if version == VersionTLS12 { + // Use the FIPS 140-3 module only for TLS 1.2 with EMS, which is the + // only TLS 1.0-1.2 approved mode per IG D.Q. + return tls12.MasterSecret(hash.New, preMasterSecret, transcript) + } + return prf(preMasterSecret, extendedMasterSecretLabel, transcript, masterSecretLength) } // keysFromMasterSecret generates the connection keys from the master @@ -134,8 +138,7 @@ func keysFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clie seed = append(seed, clientRandom...) n := 2*macLen + 2*keyLen + 2*ivLen - keyMaterial := make([]byte, n) - prfForVersion(version, suite)(keyMaterial, masterSecret, keyExpansionLabel, seed) + keyMaterial := prfForVersion(version, suite)(masterSecret, keyExpansionLabel, seed, n) clientMAC = keyMaterial[:macLen] keyMaterial = keyMaterial[macLen:] serverMAC = keyMaterial[:macLen] @@ -178,7 +181,7 @@ type finishedHash struct { buffer []byte version uint16 - prf func(result, secret, label, seed []byte) + prf prfFunc } func (h *finishedHash) Write(msg []byte) (n int, err error) { @@ -210,17 +213,13 @@ func (h finishedHash) Sum() []byte { // clientSum returns the contents of the verify_data member of a client's // Finished message. func (h finishedHash) clientSum(masterSecret []byte) []byte { - out := make([]byte, finishedVerifyLength) - h.prf(out, masterSecret, clientFinishedLabel, h.Sum()) - return out + return h.prf(masterSecret, clientFinishedLabel, h.Sum(), finishedVerifyLength) } // serverSum returns the contents of the verify_data member of a server's // Finished message. func (h finishedHash) serverSum(masterSecret []byte) []byte { - out := make([]byte, finishedVerifyLength) - h.prf(out, masterSecret, serverFinishedLabel, h.Sum()) - return out + return h.prf(masterSecret, serverFinishedLabel, h.Sum(), finishedVerifyLength) } // hashForClientCertificate returns the handshake messages so far, pre-hashed if @@ -293,8 +292,6 @@ func ekmFromMasterSecret(version uint16, suite *cipherSuite, masterSecret, clien seed = append(seed, context...) } - keyMaterial := make([]byte, length) - prfForVersion(version, suite)(keyMaterial, masterSecret, []byte(label), seed) - return keyMaterial, nil + return prfForVersion(version, suite)(masterSecret, label, seed, length), nil } } diff --git a/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-ECDSA b/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-ECDSA index a35aa57ee09..b79aec63004 100644 --- a/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-ECDSA +++ b/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-ECDSA @@ -17,11 +17,11 @@ 000000f0 bb 29 07 30 ff f6 84 af c4 cf c2 ed 90 99 5f 58 |.).0.........._X| 00000100 cb 3b 74 |.;t| >>> Flow 2 (server to client) -00000000 16 03 01 00 5d 02 00 00 59 03 01 60 8a bf 2b 6f |....]...Y..`..+o| -00000010 c1 f0 f3 7b d4 78 2c d9 15 3f 33 6a 4b 96 aa 83 |...{.x,..?3jK...| -00000020 54 ae 66 8f 0f e7 b1 68 31 00 39 20 d8 4d f2 2c |T.f....h1.9 .M.,| -00000030 8b 85 98 d6 af e0 07 98 d5 cb 89 72 fb 8d c0 73 |...........r...s| -00000040 cc 68 cf 58 21 4a 8a fc c3 8e 90 0a c0 09 00 00 |.h.X!J..........| +00000000 16 03 01 00 5d 02 00 00 59 03 01 58 ee cb 2c 2e |....]...Y..X..,.| +00000010 24 e7 e4 50 2b 4b 9c 0f ab f5 79 c1 12 b6 89 e5 |$..P+K....y.....| +00000020 83 41 06 cb 45 f6 66 e9 e1 d2 fe 20 95 fd 7f 15 |.A..E.f.... ....| +00000030 44 4e 44 18 9b fc 16 a7 8a a4 d9 f7 ca 49 85 e8 |DND..........I..| +00000040 00 ce 02 26 38 38 1b 8d 6d 16 f2 b8 c0 09 00 00 |...&88..m.......| 00000050 11 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 17 |................| 00000060 00 00 16 03 01 02 0e 0b 00 02 0a 00 02 07 00 02 |................| 00000070 04 30 82 02 00 30 82 01 62 02 09 00 b8 bf 2d 47 |.0...0..b.....-G| @@ -57,17 +57,17 @@ 00000250 c9 86 2e dd d7 11 69 7f 85 7c 56 de fb 31 78 2b |......i..|V..1x+| 00000260 e4 c7 78 0d ae cb be 9e 4e 36 24 31 7b 6a 0f 39 |..x.....N6$1{j.9| 00000270 95 12 07 8f 2a 16 03 01 00 b5 0c 00 00 b1 03 00 |....*...........| -00000280 1d 20 68 5a 6f c6 20 26 b5 5c 47 c6 5e fb 23 c4 |. hZo. &.\G.^.#.| -00000290 bc 9c 3f 3a 9b ed d6 8e a3 c8 66 7d 9b cb d0 30 |..?:......f}...0| -000002a0 f9 60 00 8b 30 81 88 02 42 01 b2 fb f8 5f d0 14 |.`..0...B...._..| -000002b0 9d 3c 55 0f 16 50 6f d5 0c 4c 3e 73 2e a9 23 5f |.s..#_| -000002c0 e8 9c 02 5d 4c 6d b0 c1 9e 0d ac 59 36 6c d5 c2 |...]Lm.....Y6l..| -000002d0 4c 94 94 94 6f a4 df 26 1a 54 f5 74 b8 49 75 49 |L...o..&.T.t.IuI| -000002e0 9c aa cd 91 24 f3 52 88 6a 1e 80 02 42 01 81 a2 |....$.R.j...B...| -000002f0 76 e2 e8 b0 2a 8e 4e ed 6d be 2f e3 ca 4c ff f2 |v...*.N.m./..L..| -00000300 d3 14 c0 b5 f8 c5 53 04 97 de 14 b2 8e af 77 86 |......S.......w.| -00000310 de bf 4a 59 cf 7b 8a 73 d3 95 c3 28 ca 25 63 8e |..JY.{.s...(.%c.| -00000320 9e 02 7f 8a 04 bb 69 1e 41 31 76 2b 5a 54 ed 16 |......i.A1v+ZT..| +00000280 1d 20 b2 d4 b5 f1 3f 61 7d 07 30 8b fd 17 76 41 |. ....?a}.0...vA| +00000290 12 c5 e1 25 f1 e9 ad 68 26 55 4b f5 60 16 b0 44 |...%...h&UK.`..D| +000002a0 90 7a 00 8b 30 81 88 02 42 01 26 8b c1 0d 38 f2 |.z..0...B.&...8.| +000002b0 0f 22 de fd 81 53 5b 5a 87 b6 57 23 33 22 06 8f |."...S[Z..W#3"..| +000002c0 8b 59 f1 70 85 46 41 f2 b7 0c 80 77 df 40 08 77 |.Y.p.FA....w.@.w| +000002d0 e1 b8 21 7f 55 77 c7 b7 ef ef b5 31 ae a0 22 a8 |..!.Uw.....1..".| +000002e0 d3 e0 67 e8 67 bc a9 cb 03 47 76 02 42 00 f4 7a |..g.g....Gv.B..z| +000002f0 eb 2f d0 82 d7 06 75 35 e4 61 fb cf 27 93 95 29 |./....u5.a..'..)| +00000300 6e 2c b5 d4 01 45 5b b6 d9 72 e9 f9 13 a6 5f bd |n,...E[..r...._.| +00000310 24 76 3b 8e 48 7a ce 4f f5 c2 77 75 66 2d 18 6d |$v;.Hz.O..wuf-.m| +00000320 7d 9e c7 95 0c fe 0b 80 15 67 b2 f2 f6 5a dd 16 |}........g...Z..| 00000330 03 01 00 0a 0d 00 00 06 03 01 02 40 00 00 16 03 |...........@....| 00000340 01 00 04 0e 00 00 00 |.......| >>> Flow 3 (client to server) @@ -107,29 +107,29 @@ 00000210 03 01 00 25 10 00 00 21 20 2f e5 7d a3 47 cd 62 |...%...! /.}.G.b| 00000220 43 15 28 da ac 5f bb 29 07 30 ff f6 84 af c4 cf |C.(.._.).0......| 00000230 c2 ed 90 99 5f 58 cb 3b 74 16 03 01 00 91 0f 00 |...._X.;t.......| -00000240 00 8d 00 8b 30 81 88 02 42 01 57 eb e6 90 3c fe |....0...B.W...<.| -00000250 10 8d e9 7d 50 a5 d3 83 43 64 e7 d0 cb 65 ef b9 |...}P...Cd...e..| -00000260 56 59 b9 52 09 e8 52 d7 d7 4d a7 57 09 dd 1f 83 |VY.R..R..M.W....| -00000270 22 0e 4c 4e 8b 50 0d 68 72 26 9e 2a 8b 6d cb 88 |".LN.P.hr&.*.m..| -00000280 c8 f4 0d 7d 85 80 e2 ec 7f f1 be 02 42 01 68 5d |...}........B.h]| -00000290 f8 91 45 82 61 7f 57 e2 2e b2 54 7d f3 11 11 44 |..E.a.W...T}...D| -000002a0 2f ee 48 91 17 5c 04 3d b8 0e eb ed 66 33 b1 62 |/.H..\.=....f3.b| -000002b0 61 a1 03 e8 77 cf 44 4b 93 fc 4f 12 24 2f d1 67 |a...w.DK..O.$/.g| -000002c0 c8 4b 07 e3 cb a8 7d 5d 82 d4 a2 ec d7 0b f8 14 |.K....}]........| -000002d0 03 01 00 01 01 16 03 01 00 30 28 45 dc 18 db 52 |.........0(E...R| -000002e0 a7 b5 1e 68 7a 06 03 8a 23 87 07 ea 79 38 29 ec |...hz...#...y8).| -000002f0 1b b7 b9 cb 1b 04 ac ba 1d b2 d5 8e 71 e0 27 30 |............q.'0| -00000300 02 d1 c9 4d 35 69 38 71 c9 c7 |...M5i8q..| +00000240 00 8d 00 8b 30 81 88 02 42 01 87 b2 c8 53 94 c3 |....0...B....S..| +00000250 9f ea 8a 04 35 1e 53 fb c0 db c5 43 e8 99 b7 81 |....5.S....C....| +00000260 43 fd 47 fd a6 cd 89 b5 0a b9 d0 5d 79 b0 c2 1a |C.G........]y...| +00000270 94 1f d8 2e 2b 8d 23 2a c8 08 63 98 f6 01 1f 8f |....+.#*..c.....| +00000280 67 60 3a 88 44 63 7e 8b 91 3e 48 02 42 00 e1 1f |g`:.Dc~..>H.B...| +00000290 69 9a 97 bb b6 e9 97 4d f5 f5 d7 23 56 be 1d ff |i......M...#V...| +000002a0 f0 22 67 8d a3 a1 13 33 5b f7 0b c2 a3 c8 5a 8c |."g....3[.....Z.| +000002b0 e8 65 b8 a8 7e 6c 99 11 db ab bd 80 0a ca dd 5a |.e..~l.........Z| +000002c0 d4 e0 28 12 38 7a b4 86 e7 3e 13 05 a1 3f 5f 14 |..(.8z...>...?_.| +000002d0 03 01 00 01 01 16 03 01 00 30 9a fa ce 11 0b 50 |.........0.....P| +000002e0 fa a1 59 9b 89 41 a9 0b 1b 74 dd 8f 7b a9 e0 39 |..Y..A...t..{..9| +000002f0 5d e3 cc ee 11 f4 27 15 bc 4c 8f e1 2c 51 ec 8e |].....'..L..,Q..| +00000300 ed 6c 97 75 4c e9 da fb 68 9d |.l.uL...h.| >>> Flow 4 (server to client) -00000000 14 03 01 00 01 01 16 03 01 00 30 3d a8 df 2e 80 |..........0=....| -00000010 26 22 66 32 fb 6e bc 9e f5 d6 6a 5e 0a 18 34 92 |&"f2.n....j^..4.| -00000020 f9 42 40 e4 9c b1 7a 28 d2 52 e9 b8 13 ce 89 01 |.B@...z(.R......| -00000030 23 44 ab 2e 75 3e c2 96 f5 59 61 |#D..u>...Ya| +00000000 14 03 01 00 01 01 16 03 01 00 30 12 06 0c 4d 1c |..........0...M.| +00000010 81 47 06 5f b9 85 68 60 9c 26 4a 14 f2 40 60 7f |.G._..h`.&J..@`.| +00000020 d1 1e 51 9e f2 70 2f be 54 cb 42 b3 b3 8d 90 9c |..Q..p/.T.B.....| +00000030 9d d2 0d e2 f7 b6 56 31 11 af 5f |......V1.._| >>> Flow 5 (client to server) -00000000 17 03 01 00 20 64 ac d2 1b 98 4d 4e ec 22 25 35 |.... d....MN."%5| -00000010 7c 7c 79 15 a8 54 0d 93 1a b3 3e ff 68 27 d0 d5 |||y..T....>.h'..| -00000020 82 24 7b dc e7 17 03 01 00 20 98 da 6d 26 de b8 |.${...... ..m&..| -00000030 4b d0 ee 2c 18 52 05 86 59 b0 b6 e7 42 74 54 ea |K..,.R..Y...BtT.| -00000040 e6 96 de ec 82 dc 1f 3a 6c 5c 15 03 01 00 20 cc |.......:l\.... .| -00000050 11 da e4 75 e5 0c 06 84 e5 89 3e a4 ef 3c 28 10 |...u......>..<(.| -00000060 68 0a 6f d4 6e ae 14 a6 ca 7c ba 98 c4 28 7e |h.o.n....|...(~| +00000000 17 03 01 00 20 0f d0 65 cd d4 59 d0 9e 07 06 6d |.... ..e..Y....m| +00000010 8a 11 34 d9 3a 31 59 70 02 39 08 ef 28 98 36 2f |..4.:1Yp.9..(.6/| +00000020 0d 5b d0 23 3f 17 03 01 00 20 5b 16 82 9c 29 c6 |.[.#?.... [...).| +00000030 78 34 76 09 5e bb 01 5e fc 01 b2 55 9b ef 67 a8 |x4v.^..^...U..g.| +00000040 ca 9f b9 6d b5 ea 87 48 8e 76 15 03 01 00 20 43 |...m...H.v.... C| +00000050 12 3b f6 99 31 67 d8 8a 94 d1 37 ef 01 ca a1 62 |.;..1g....7....b| +00000060 9f d7 2a eb 7e 37 79 d9 a4 07 15 5f fb 84 32 |..*.~7y...._..2| diff --git a/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-RSA b/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-RSA index 687eba43e6c..b02fe3d25b9 100644 --- a/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-RSA +++ b/crypto/tls/testdata/Client-TLSv10-ClientCert-ECDSA-RSA @@ -17,11 +17,11 @@ 000000f0 bb 29 07 30 ff f6 84 af c4 cf c2 ed 90 99 5f 58 |.).0.........._X| 00000100 cb 3b 74 |.;t| >>> Flow 2 (server to client) -00000000 16 03 01 00 5d 02 00 00 59 03 01 48 c6 a1 aa 01 |....]...Y..H....| -00000010 b8 4b a1 f5 6d 71 28 f7 fd 76 9d 12 50 d3 56 e2 |.K..mq(..v..P.V.| -00000020 ca 77 d4 82 07 65 e2 c5 12 40 35 20 e3 ee 0a df |.w...e...@5 ....| -00000030 1f 4a d9 b3 a6 6f a0 24 49 8d ab d9 d8 3c 24 36 |.J...o.$I....<$6| -00000040 35 ba 64 8b 7d ec 29 91 d9 26 5e 61 c0 13 00 00 |5.d.}.)..&^a....| +00000000 16 03 01 00 5d 02 00 00 59 03 01 0b 4b bc a9 e8 |....]...Y...K...| +00000010 4a 08 69 de 73 f8 fc 53 c6 e9 cd cf 25 7a 5f b6 |J.i.s..S....%z_.| +00000020 60 48 65 3e f5 5f 9f 14 e7 38 a0 20 ec fe 94 b2 |`He>._...8. ....| +00000030 6d a9 d7 91 c4 92 6b 3e a4 2e 88 72 07 c3 47 12 |m.....k>...r..G.| +00000040 78 cc 45 86 f9 56 16 f9 d7 d9 38 c2 c0 13 00 00 |x.E..V....8.....| 00000050 11 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 17 |................| 00000060 00 00 16 03 01 02 59 0b 00 02 55 00 02 52 00 02 |......Y...U..R..| 00000070 4f 30 82 02 4b 30 82 01 b4 a0 03 02 01 02 02 09 |O0..K0..........| @@ -61,17 +61,17 @@ 00000290 73 bb b3 43 77 8d 0c 1c f1 0f a1 d8 40 83 61 c9 |s..Cw.......@.a.| 000002a0 4c 72 2b 9d ae db 46 06 06 4d f4 c1 b3 3e c0 d1 |Lr+...F..M...>..| 000002b0 bd 42 d4 db fe 3d 13 60 84 5c 21 d3 3b e9 fa e7 |.B...=.`.\!.;...| -000002c0 16 03 01 00 aa 0c 00 00 a6 03 00 1d 20 55 51 65 |............ UQe| -000002d0 bb 06 22 d7 d6 97 39 d1 f4 dc 95 06 b3 a4 a7 00 |.."...9.........| -000002e0 d1 e5 98 bc 97 12 03 25 03 12 ab 20 4f 00 80 71 |.......%... O..q| -000002f0 8d 3c 54 44 ba df 73 92 76 16 d1 ec b1 de a2 27 |.>> Flow 3 (client to server) @@ -111,29 +111,29 @@ 00000210 03 01 00 25 10 00 00 21 20 2f e5 7d a3 47 cd 62 |...%...! /.}.G.b| 00000220 43 15 28 da ac 5f bb 29 07 30 ff f6 84 af c4 cf |C.(.._.).0......| 00000230 c2 ed 90 99 5f 58 cb 3b 74 16 03 01 00 90 0f 00 |...._X.;t.......| -00000240 00 8c 00 8a 30 81 87 02 41 79 c5 57 74 46 9a 18 |....0...Ay.WtF..| -00000250 ad e0 b0 ba 68 f5 0e e2 58 94 dc 73 84 5a f8 86 |....h...X..s.Z..| -00000260 86 8e 2a 37 82 02 a1 4b 19 cd 71 b3 99 04 64 b0 |..*7...K..q...d.| -00000270 db 4a cc 41 a6 17 28 38 f1 67 bd 59 16 97 71 32 |.J.A..(8.g.Y..q2| -00000280 06 71 24 2c f3 df 34 1b a3 b8 02 42 01 2f 2f db |.q$,..4....B.//.| -00000290 45 07 94 53 89 81 59 0b 92 9d 1a 05 42 b3 1c 40 |E..S..Y.....B..@| -000002a0 38 50 a1 8e a6 35 15 76 ca 75 7e fc 8d 7b 36 f3 |8P...5.v.u~..{6.| -000002b0 e3 e7 bf f9 bd 94 35 a0 c5 2b 35 88 be 5d ee f1 |......5..+5..]..| -000002c0 00 f9 9c a8 01 a8 92 5d d6 17 b6 54 98 e4 14 03 |.......]...T....| -000002d0 01 00 01 01 16 03 01 00 30 1f d6 11 ac 58 b3 20 |........0....X. | -000002e0 31 6d 3b f5 83 98 50 75 ff 4f 79 61 2b fc 0f 6c |1m;...Pu.Oya+..l| -000002f0 a6 4d 9e 65 38 e3 ca 12 76 0a 56 1e dd 73 da e1 |.M.e8...v.V..s..| -00000300 66 5a 33 62 8f 7d c3 ed ad |fZ3b.}...| +00000240 00 8c 00 8a 30 81 87 02 41 2c f8 d2 c2 28 75 28 |....0...A,...(u(| +00000250 67 de 75 fb 7a 09 20 8a ec 06 a6 42 03 ad 3c 95 |g.u.z. ....B..<.| +00000260 bb 00 f6 10 71 c7 90 fe 08 16 fa ed 7d 71 24 a2 |....q.......}q$.| +00000270 b6 76 ce f9 1b ff a9 ff 05 b6 dd d8 63 2b 74 86 |.v..........c+t.| +00000280 65 f5 ef f7 36 41 47 77 b5 88 02 42 00 8a 80 f8 |e...6AGw...B....| +00000290 9b cf de a6 b7 c3 d8 48 a1 a0 47 7e cf 33 fc f7 |.......H..G~.3..| +000002a0 fc 87 40 cf 8d c3 81 85 c7 19 9e 37 9e 54 f7 3e |..@........7.T.>| +000002b0 d1 1c 42 83 21 d7 2e ae 02 7b 3c ce 97 f3 9b a0 |..B.!....{<.....| +000002c0 a3 4e b9 a0 9c 78 f0 7e 9c 96 fc 78 e6 08 14 03 |.N...x.~...x....| +000002d0 01 00 01 01 16 03 01 00 30 84 a0 4f 8d 01 40 ca |........0..O..@.| +000002e0 c0 fd ea 1a 9c df 27 cc 25 00 56 e2 30 05 c0 d9 |......'.%.V.0...| +000002f0 c7 21 48 37 6b 35 c3 a4 4e bf 67 98 87 78 0f 3c |.!H7k5..N.g..x.<| +00000300 74 72 4f 6a c5 0d fd 0c 84 |trOj.....| >>> Flow 4 (server to client) -00000000 14 03 01 00 01 01 16 03 01 00 30 92 36 25 46 a5 |..........0.6%F.| -00000010 44 e6 31 25 cd 24 15 df 13 f8 5b a3 af 7c 12 43 |D.1%.$....[..|.C| -00000020 b8 8c 39 84 bc 25 06 02 02 86 78 20 1b ec 98 1a |..9..%....x ....| -00000030 31 b3 b8 cf 82 92 77 ff 08 87 fb |1.....w....| +00000000 14 03 01 00 01 01 16 03 01 00 30 01 da 6e 2a 83 |..........0..n*.| +00000010 20 ad 52 16 f2 c6 c1 55 b8 77 0d 5f c6 48 dc e7 | .R....U.w._.H..| +00000020 72 29 88 0a 2b 1a d1 1e fd fb c0 c3 18 c8 43 47 |r)..+.........CG| +00000030 a9 8f d3 fe f3 d8 d2 a8 ce 79 44 |.........yD| >>> Flow 5 (client to server) -00000000 17 03 01 00 20 2f bd ab e0 2f 9a 81 58 99 35 bb |.... /.../..X.5.| -00000010 86 61 6e 15 be 31 d7 ad 44 1d d9 cf 2f fc d9 f6 |.an..1..D.../...| -00000020 da b6 48 32 27 17 03 01 00 20 76 70 b7 7d 1b 05 |..H2'.... vp.}..| -00000030 ee 54 99 bf 89 79 79 b5 68 c1 84 3c 6d 47 5c d1 |.T...yy.h..>> Flow 2 (server to client) -00000000 16 03 03 00 5d 02 00 00 59 03 03 d4 19 2c 08 c6 |....]...Y....,..| -00000010 ef 32 88 79 a1 84 fc 79 38 62 b2 dd 4b a7 0b b3 |.2.y...y8b..K...| -00000020 d3 13 3d d4 f7 c7 4d d9 8b c6 8e 20 56 8e 90 3d |..=...M.... V..=| -00000030 2b 6e 2d cf 7e c1 c6 b0 e5 d8 d2 af 3a 06 88 c6 |+n-.~.......:...| -00000040 ed e7 18 76 ab 45 c5 76 47 67 95 ad c0 09 00 00 |...v.E.vGg......| +00000000 16 03 03 00 5d 02 00 00 59 03 03 58 a7 ae 6c fc |....]...Y..X..l.| +00000010 0d 53 51 d5 37 8d 1f 88 75 27 bf 34 8f 3d d3 ee |.SQ.7...u'.4.=..| +00000020 95 db 77 d9 c0 3b f1 57 4f 84 e2 20 7f 0d d1 84 |..w..;.WO.. ....| +00000030 0c 7a 4e 41 7f 74 58 6c f5 38 e6 8d 5b 04 15 e1 |.zNA.tXl.8..[...| +00000040 3d 4a ba 7e 58 f4 e8 bf f4 42 8a f5 c0 09 00 00 |=J.~X....B......| 00000050 11 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 17 |................| 00000060 00 00 16 03 03 02 0e 0b 00 02 0a 00 02 07 00 02 |................| 00000070 04 30 82 02 00 30 82 01 62 02 09 00 b8 bf 2d 47 |.0...0..b.....-G| @@ -56,23 +56,23 @@ 00000240 8c 25 c1 33 13 83 0d 94 06 bb d4 37 7a f6 ec 7a |.%.3.......7z..z| 00000250 c9 86 2e dd d7 11 69 7f 85 7c 56 de fb 31 78 2b |......i..|V..1x+| 00000260 e4 c7 78 0d ae cb be 9e 4e 36 24 31 7b 6a 0f 39 |..x.....N6$1{j.9| -00000270 95 12 07 8f 2a 16 03 03 00 b7 0c 00 00 b3 03 00 |....*...........| -00000280 1d 20 e9 3c ed d4 07 43 c1 20 f5 41 35 e1 49 4a |. .<...C. .A5.IJ| -00000290 1c 52 5d 77 f2 07 59 18 55 e0 66 d1 49 ab 95 74 |.R]w..Y.U.f.I..t| -000002a0 81 1c 04 03 00 8b 30 81 88 02 42 01 f1 52 e7 13 |......0...B..R..| -000002b0 3e 9b 3b 4f 90 0f d2 9b 50 11 fe df 1f 12 f2 d9 |>.;O....P.......| -000002c0 0d 89 bc 6c 01 93 45 ca b8 3c 09 cf b2 01 e9 99 |...l..E..<......| -000002d0 87 fb 1d ac 91 7f 77 a2 21 5e 07 5e 65 3b ec 31 |......w.!^.^e;.1| -000002e0 d7 b5 b9 1d 88 c8 82 f5 03 a9 37 e8 b9 02 42 01 |..........7...B.| -000002f0 78 c4 90 fb e3 7f 5a 7a 66 0a 44 f5 66 0e 1e ac |x.....Zzf.D.f...| -00000300 cb 53 c4 74 55 3a b8 b7 ec b3 f6 4c e1 a8 b6 cb |.S.tU:.....L....| -00000310 a1 e5 37 ac e2 17 e2 6c 8f 71 37 b0 f1 ca b8 1b |..7....l.q7.....| -00000320 df 91 16 a4 7f 31 b1 4f 3e e4 30 f0 5a 5e df 93 |.....1.O>.0.Z^..| -00000330 85 16 03 03 00 3a 0d 00 00 36 03 01 02 40 00 2e |.....:...6...@..| -00000340 04 03 05 03 06 03 08 07 08 08 08 09 08 0a 08 0b |................| -00000350 08 04 08 05 08 06 04 01 05 01 06 01 03 03 02 03 |................| -00000360 03 01 02 01 03 02 02 02 04 02 05 02 06 02 00 00 |................| -00000370 16 03 03 00 04 0e 00 00 00 |.........| +00000270 95 12 07 8f 2a 16 03 03 00 b6 0c 00 00 b2 03 00 |....*...........| +00000280 1d 20 b2 5c fe 84 12 a5 2f 8f da 91 3f 44 52 4e |. .\..../...?DRN| +00000290 03 ad e4 f9 8a 14 71 0d 0b 46 86 81 d2 1b 4a 5b |......q..F....J[| +000002a0 d9 4e 04 03 00 8a 30 81 87 02 42 01 78 d2 c2 a9 |.N....0...B.x...| +000002b0 b7 77 f8 eb 8d 9d 36 1f 79 e7 d1 74 a9 9d a4 53 |.w....6.y..t...S| +000002c0 5e 08 4a 92 dc ac b3 32 bf ce e7 f0 80 af 56 48 |^.J....2......VH| +000002d0 42 e2 fd 22 c7 35 11 13 db 2a 6d 74 4a e5 02 ce |B..".5...*mtJ...| +000002e0 ea 43 e6 b0 69 18 3e d3 86 85 76 ff 56 02 41 44 |.C..i.>...v.V.AD| +000002f0 2a e1 6b ae 5d f9 39 5c ec 76 ee 01 d7 04 42 ca |*.k.].9\.v....B.| +00000300 45 07 e0 59 38 75 d4 47 61 a4 5b 0f a9 7a ba 79 |E..Y8u.Ga.[..z.y| +00000310 fa 79 92 41 88 8a d3 be c2 38 3e f7 95 6c 99 3f |.y.A.....8>..l.?| +00000320 4c ff af 10 33 14 25 e0 d7 8a fd 70 17 2b 73 ee |L...3.%....p.+s.| +00000330 16 03 03 00 3a 0d 00 00 36 03 01 02 40 00 2e 04 |....:...6...@...| +00000340 03 05 03 06 03 08 07 08 08 08 09 08 0a 08 0b 08 |................| +00000350 04 08 05 08 06 04 01 05 01 06 01 03 03 02 03 03 |................| +00000360 01 02 01 03 02 02 02 04 02 05 02 06 02 00 00 16 |................| +00000370 03 03 00 04 0e 00 00 00 |........| >>> Flow 3 (client to server) 00000000 16 03 03 02 0a 0b 00 02 06 00 02 03 00 02 00 30 |...............0| 00000010 82 01 fc 30 82 01 5e 02 09 00 9a 30 84 6c 26 35 |...0..^....0.l&5| @@ -109,32 +109,32 @@ 00000200 e4 fa cc b1 8a ce e2 23 a0 87 f0 e1 67 51 eb 16 |.......#....gQ..| 00000210 03 03 00 25 10 00 00 21 20 2f e5 7d a3 47 cd 62 |...%...! /.}.G.b| 00000220 43 15 28 da ac 5f bb 29 07 30 ff f6 84 af c4 cf |C.(.._.).0......| -00000230 c2 ed 90 99 5f 58 cb 3b 74 16 03 03 00 93 0f 00 |...._X.;t.......| -00000240 00 8f 04 03 00 8b 30 81 88 02 42 01 c9 2c e8 1f |......0...B..,..| -00000250 5b a0 97 0f 70 2d 46 e3 6e b4 4c 82 fc be df 05 |[...p-F.n.L.....| -00000260 09 b0 d7 e4 0a 06 71 66 d6 3b 3e b6 56 2c 22 c7 |......qf.;>.V,".| -00000270 7c 3a 63 5c 34 22 5b 3a 49 a2 97 f3 2f 58 e7 2a ||:c\4"[:I.../X.*| -00000280 cd f4 05 84 db 8b 0b 22 e3 50 1b e7 6d 02 42 01 |.......".P..m.B.| -00000290 fd 1e bc 64 8b 01 3f a0 f8 8b 41 be 4d 97 07 6c |...d..?...A.M..l| -000002a0 3b 09 74 d6 00 76 b3 a0 c0 d0 92 d0 78 24 d7 b2 |;.t..v......x$..| -000002b0 1f c0 86 90 59 3a fb a8 e1 f6 80 23 db f7 90 93 |....Y:.....#....| -000002c0 95 fd 37 14 8c 34 f3 07 10 f8 dd e3 66 28 1c b2 |..7..4......f(..| -000002d0 a0 14 03 03 00 01 01 16 03 03 00 40 00 00 00 00 |...........@....| -000002e0 00 00 00 00 00 00 00 00 00 00 00 00 6c b8 ab 0b |............l...| -000002f0 c1 2d 2f 4e d2 25 14 96 24 c6 18 97 82 93 1c 1b |.-/N.%..$.......| -00000300 b4 ca 77 8b 17 7c cf 08 ca 06 e2 ef f3 97 6a 31 |..w..|........j1| -00000310 88 aa 46 a2 d3 7b 65 56 e2 77 72 df |..F..{eV.wr.| +00000230 c2 ed 90 99 5f 58 cb 3b 74 16 03 03 00 92 0f 00 |...._X.;t.......| +00000240 00 8e 04 03 00 8a 30 81 87 02 41 0c 11 31 08 e0 |......0...A..1..| +00000250 7d 4e 11 1e 80 7c 50 70 ea 1b 68 16 d9 e6 93 b7 |}N...|Pp..h.....| +00000260 fb 6f 0d 6e ce 8b d2 7c 86 70 c3 e9 ed 35 3c 29 |.o.n...|.p...5<)| +00000270 c7 d8 6c 8f 43 c9 a1 7a 4a ae 19 22 6e e3 85 7e |..l.C..zJ.."n..~| +00000280 a0 5d 7f 19 a5 b7 25 ad d7 1a 5f 42 02 42 00 d8 |.]....%..._B.B..| +00000290 6a 25 47 2a 0e 1a 38 51 1c 73 aa 2d 10 9e 4b 65 |j%G*..8Q.s.-..Ke| +000002a0 56 34 72 e5 02 09 f6 30 ce a8 a0 59 75 7b 13 42 |V4r....0...Yu{.B| +000002b0 1e 31 f3 dd 08 a9 65 df 2d e6 aa 29 5d 9a 5c eb |.1....e.-..)].\.| +000002c0 d3 67 af 29 4e d2 76 e5 17 1d 7e e7 0a 50 c4 d0 |.g.)N.v...~..P..| +000002d0 14 03 03 00 01 01 16 03 03 00 40 00 00 00 00 00 |..........@.....| +000002e0 00 00 00 00 00 00 00 00 00 00 00 8b ef 24 70 ef |.............$p.| +000002f0 b9 b3 fd 59 49 62 b7 18 9c bc 7f 74 06 21 21 ab |...YIb.....t.!!.| +00000300 9c 4d cf 70 0c 6b 3b d3 d4 12 51 a7 f9 09 65 d1 |.M.p.k;...Q...e.| +00000310 ce b7 28 01 c1 e9 0b e4 41 55 b6 |..(.....AU.| >>> Flow 4 (server to client) -00000000 14 03 03 00 01 01 16 03 03 00 40 b8 19 a7 e2 35 |..........@....5| -00000010 8a be 5f 4c 91 d2 db 3f f3 42 90 8e b5 7f ea f7 |.._L...?.B......| -00000020 52 3e 61 2f 4d ef 25 8d ce 82 77 22 05 6f 0d b6 |R>a/M.%...w".o..| -00000030 04 c1 f0 f0 a2 9d bf 80 a9 f5 e1 62 5c e2 30 ef |...........b\.0.| -00000040 83 e7 c5 78 de 8f 17 4e d3 57 dd |...x...N.W.| +00000000 14 03 03 00 01 01 16 03 03 00 40 af 82 f6 ba d1 |..........@.....| +00000010 e8 f9 c5 58 0e ba 94 ec 98 68 b3 20 5b db 4e 8c |...X.....h. [.N.| +00000020 f5 00 29 e8 9a c1 34 35 ac 77 af e2 a0 6a 6f 45 |..)...45.w...joE| +00000030 05 56 d4 a0 5b 75 19 c6 1b 8a c8 65 04 ed 3d f3 |.V..[u.....e..=.| +00000040 84 e5 1b c0 26 31 5d 03 d5 b4 31 |....&1]...1| >>> Flow 5 (client to server) 00000000 17 03 03 00 30 00 00 00 00 00 00 00 00 00 00 00 |....0...........| -00000010 00 00 00 00 00 cb 79 13 ea 9e 7a 63 8f 5a 2b 8c |......y...zc.Z+.| -00000020 3a f3 bb 7e dc ff d1 2c 4a a6 4a aa ad bf 44 b8 |:..~...,J.J...D.| -00000030 cb 67 ce 3d f0 15 03 03 00 30 00 00 00 00 00 00 |.g.=.....0......| -00000040 00 00 00 00 00 00 00 00 00 00 f4 15 a0 f0 64 d1 |..............d.| -00000050 dd 5f 14 66 6c ce 51 95 bc b5 0b f8 4f 42 48 57 |._.fl.Q.....OBHW| -00000060 cf f3 09 62 75 0d 3e 64 64 e0 |...bu.>dd.| +00000010 00 00 00 00 00 2e a5 7c 6f cc f7 44 1a 38 99 4a |.......|o..D.8.J| +00000020 2e a4 4b 79 bf ee b6 c9 12 57 f5 2e 98 dd 1d 2a |..Ky.....W.....*| +00000030 5b 79 d5 ef 44 15 03 03 00 30 00 00 00 00 00 00 |[y..D....0......| +00000040 00 00 00 00 00 00 00 00 00 00 3d fe 37 cb 65 03 |..........=.7.e.| +00000050 13 ce 3d 3b c2 18 c2 27 f9 a4 b7 fc e6 37 eb 2a |..=;...'.....7.*| +00000060 27 6c 52 38 2f 3a 61 d4 a9 e6 |'lR8/:a...| diff --git a/crypto/tls/testdata/Client-TLSv12-ClientCert-ECDSA-RSA b/crypto/tls/testdata/Client-TLSv12-ClientCert-ECDSA-RSA index 692092e34f9..de57515d382 100644 --- a/crypto/tls/testdata/Client-TLSv12-ClientCert-ECDSA-RSA +++ b/crypto/tls/testdata/Client-TLSv12-ClientCert-ECDSA-RSA @@ -17,11 +17,11 @@ 000000f0 bb 29 07 30 ff f6 84 af c4 cf c2 ed 90 99 5f 58 |.).0.........._X| 00000100 cb 3b 74 |.;t| >>> Flow 2 (server to client) -00000000 16 03 03 00 5d 02 00 00 59 03 03 ed c3 6f 59 34 |....]...Y....oY4| -00000010 78 33 49 00 68 50 1f a5 aa 93 45 9a 87 34 c4 4e |x3I.hP....E..4.N| -00000020 71 b0 ab 5e 43 f7 a1 5c 89 e8 2f 20 f7 42 d7 2a |q..^C..\../ .B.*| -00000030 a5 fe 16 76 ac 6f cf 20 1d a6 bc d5 9d 27 9d 81 |...v.o. .....'..| -00000040 80 b4 0d 4e 12 89 de 7b 7a 5b a0 2b c0 2f 00 00 |...N...{z[.+./..| +00000000 16 03 03 00 5d 02 00 00 59 03 03 f8 3d 7c a4 a8 |....]...Y...=|..| +00000010 11 e3 56 0f 1c 7e 2e 7c 50 7e 75 5c de 1c 51 8e |..V..~.|P~u\..Q.| +00000020 de d3 8a 84 d2 90 84 f9 e9 07 d5 20 98 6a a8 c1 |........... .j..| +00000030 f4 28 bd 0f 6a 25 a5 26 3d 8d 35 b6 3e bb 77 c6 |.(..j%.&=.5.>.w.| +00000040 8e ab 36 bd 7d c8 a9 b1 5b 30 0f b2 c0 2f 00 00 |..6.}...[0.../..| 00000050 11 ff 01 00 01 00 00 0b 00 04 03 00 01 02 00 17 |................| 00000060 00 00 16 03 03 02 59 0b 00 02 55 00 02 52 00 02 |......Y...U..R..| 00000070 4f 30 82 02 4b 30 82 01 b4 a0 03 02 01 02 02 09 |O0..K0..........| @@ -61,18 +61,18 @@ 00000290 73 bb b3 43 77 8d 0c 1c f1 0f a1 d8 40 83 61 c9 |s..Cw.......@.a.| 000002a0 4c 72 2b 9d ae db 46 06 06 4d f4 c1 b3 3e c0 d1 |Lr+...F..M...>..| 000002b0 bd 42 d4 db fe 3d 13 60 84 5c 21 d3 3b e9 fa e7 |.B...=.`.\!.;...| -000002c0 16 03 03 00 ac 0c 00 00 a8 03 00 1d 20 d9 02 16 |............ ...| -000002d0 ba b6 db c0 c6 65 6d fb b8 69 dc 04 1a 8d 4a 36 |.....em..i....J6| -000002e0 27 6b f0 53 c3 72 56 1a 99 07 0f b9 79 08 04 00 |'k.S.rV.....y...| -000002f0 80 5d c6 1e a2 20 7d ae 35 e6 20 2f ff 7a 7b 8d |.]... }.5. /.z{.| -00000300 3f fd 49 d4 ce 26 f7 d0 c2 51 2a 42 03 26 b7 30 |?.I..&...Q*B.&.0| -00000310 af 5f 7d 34 1d 7d 37 ef 8a 1c e6 be 19 99 f1 30 |._}4.}7........0| -00000320 2e 26 6d 7b 83 cd 6e 26 c4 03 f5 7a 30 8f bf e3 |.&m{..n&...z0...| -00000330 c1 64 da 43 fc f0 32 c9 a2 68 e4 97 d0 34 8a c4 |.d.C..2..h...4..| -00000340 fc f8 bb 61 ec df 69 f7 d0 d7 1e 19 c0 5b 21 86 |...a..i......[!.| -00000350 eb 79 93 46 3b 7a 50 83 41 8b d3 7c 59 d5 34 8a |.y.F;zP.A..|Y.4.| -00000360 0c b5 70 e4 86 38 3a 6c 11 04 57 d6 94 c0 78 c7 |..p..8:l..W...x.| -00000370 91 16 03 03 00 3a 0d 00 00 36 03 01 02 40 00 2e |.....:...6...@..| +000002c0 16 03 03 00 ac 0c 00 00 a8 03 00 1d 20 5f c1 31 |............ _.1| +000002d0 d7 64 f0 0b 72 6a 66 2c 49 d7 d1 9c dd 6f e3 3a |.d..rjf,I....o.:| +000002e0 ab 2c 78 6d ca b0 ed 16 26 65 9f ff 66 08 04 00 |.,xm....&e..f...| +000002f0 80 a6 91 d0 03 b8 d2 67 48 69 16 8e 30 dc 5b 3f |.......gHi..0.[?| +00000300 ac 4d e4 33 5f 46 e7 0c 49 a0 71 9d 8c 60 63 f2 |.M.3_F..I.q..`c.| +00000310 2d ff 9e 89 21 7d af 71 ce 41 6b d2 22 fc 1f bd |-...!}.q.Ak."...| +00000320 a9 9e 15 2c d7 c3 cb 69 6d df 23 07 7c 13 e9 2b |...,...im.#.|..+| +00000330 7d 05 f0 18 1e 86 c8 37 ad cd 9e 39 26 0c 8a 9b |}......7...9&...| +00000340 12 90 60 12 95 06 e9 bb f2 46 41 20 10 f5 64 ea |..`......FA ..d.| +00000350 66 13 cb 8e 51 7e 41 78 2a 40 fa 15 e2 0d 5b 37 |f...Q~Ax*@....[7| +00000360 a7 a8 4a f6 8e 93 82 2a a2 91 06 66 4e 49 72 68 |..J....*...fNIrh| +00000370 f9 16 03 03 00 3a 0d 00 00 36 03 01 02 40 00 2e |.....:...6...@..| 00000380 04 03 05 03 06 03 08 07 08 08 08 09 08 0a 08 0b |................| 00000390 08 04 08 05 08 06 04 01 05 01 06 01 03 03 02 03 |................| 000003a0 03 01 02 01 03 02 02 02 04 02 05 02 06 02 00 00 |................| @@ -114,27 +114,27 @@ 00000210 03 03 00 25 10 00 00 21 20 2f e5 7d a3 47 cd 62 |...%...! /.}.G.b| 00000220 43 15 28 da ac 5f bb 29 07 30 ff f6 84 af c4 cf |C.(.._.).0......| 00000230 c2 ed 90 99 5f 58 cb 3b 74 16 03 03 00 93 0f 00 |...._X.;t.......| -00000240 00 8f 04 03 00 8b 30 81 88 02 42 01 a3 80 63 a4 |......0...B...c.| -00000250 49 60 35 5c 06 87 9f 7f ae 40 37 d0 64 58 b2 60 |I`5\.....@7.dX.`| -00000260 61 59 8b 6d a6 d9 55 67 81 f6 7e 9c de 40 69 00 |aY.m..Ug..~..@i.| -00000270 42 e1 2e 67 0d 48 cf 23 a7 28 f2 e0 9e 26 61 20 |B..g.H.#.(...&a | -00000280 4f 50 b6 e3 85 0b f5 f6 96 ec 03 32 35 02 42 01 |OP.........25.B.| -00000290 bd 3b 3e d6 03 98 b5 09 9a aa a9 73 1d 98 6a a5 |.;>........s..j.| -000002a0 29 ff e5 b3 f8 1f 58 2a a1 92 ea 2d 6d e9 71 d3 |).....X*...-m.q.| -000002b0 ce 04 46 ef d6 fb e4 5e e3 70 19 01 1a 22 85 70 |..F....^.p...".p| -000002c0 06 ec 15 bd ff ef af ed 2b 6d 00 97 cb 67 1f cb |........+m...g..| -000002d0 f6 14 03 03 00 01 01 16 03 03 00 28 00 00 00 00 |...........(....| -000002e0 00 00 00 00 80 da 31 59 19 74 39 ff a4 8c 30 1b |......1Y.t9...0.| -000002f0 25 fd 6b 25 57 ef 26 57 bd a1 df 90 4e d6 6a 47 |%.k%W.&W....N.jG| -00000300 96 08 54 92 |..T.| +00000240 00 8f 04 03 00 8b 30 81 88 02 42 01 0f 51 5e 59 |......0...B..Q^Y| +00000250 78 34 8f 99 03 da 07 66 3b 0d 48 b2 79 57 e2 d5 |x4.....f;.H.yW..| +00000260 d2 c2 f3 81 8e 25 98 81 e2 9a f7 1f 02 99 b0 7d |.....%.........}| +00000270 1c d1 1f e4 ef d7 bc a1 ad 67 c7 a9 cc 4f 67 58 |.........g...OgX| +00000280 8b 1e 8c 3f 04 73 31 53 60 aa 67 33 27 02 42 01 |...?.s1S`.g3'.B.| +00000290 f1 66 ba 8f ec 9e 3f 76 76 ac 7a e7 56 cb fb 46 |.f....?vv.z.V..F| +000002a0 f4 9b 64 03 3a 72 5a d7 cf 49 39 69 26 19 68 52 |..d.:rZ..I9i&.hR| +000002b0 8b 98 8e ea d3 8e d9 6d 93 f5 e8 23 cd 20 a8 5a |.......m...#. .Z| +000002c0 4c 24 10 70 bd a2 ae a3 b1 4f 38 17 dd b9 f5 93 |L$.p.....O8.....| +000002d0 4b 14 03 03 00 01 01 16 03 03 00 28 00 00 00 00 |K..........(....| +000002e0 00 00 00 00 e1 2b da c6 4a 5c d2 03 c0 7e f0 eb |.....+..J\...~..| +000002f0 a0 4b ed a1 7d e4 45 93 ec f9 37 a0 5b 7e bb 64 |.K..}.E...7.[~.d| +00000300 af d4 fc ac |....| >>> Flow 4 (server to client) -00000000 14 03 03 00 01 01 16 03 03 00 28 0e 1f ce 8a 46 |..........(....F| -00000010 77 28 6d e2 fc c4 e4 39 70 6b ab 6e 14 14 2a 34 |w(m....9pk.n..*4| -00000020 13 a0 5b c5 95 f5 fa a9 a2 f6 60 20 e3 1f c5 84 |..[.......` ....| -00000030 9c 00 5e |..^| +00000000 14 03 03 00 01 01 16 03 03 00 28 d3 4a 1e 2b ea |..........(.J.+.| +00000010 26 12 c9 fd b0 7b e6 bf e4 bb b6 d2 6b b4 3c 05 |&....{......k.<.| +00000020 1f 6c 46 44 5e 25 e6 f9 80 c8 b9 16 19 59 68 90 |.lFD^%.......Yh.| +00000030 5a 90 16 |Z..| >>> Flow 5 (client to server) -00000000 17 03 03 00 1e 00 00 00 00 00 00 00 01 88 d1 a4 |................| -00000010 c9 1b a6 a5 21 4d 93 e8 04 8b 3b 69 a9 2f bd 7d |....!M....;i./.}| -00000020 97 c1 3d 15 03 03 00 1a 00 00 00 00 00 00 00 02 |..=.............| -00000030 ba 48 db 22 9e ae d8 a9 24 b7 a6 52 13 92 68 d6 |.H."....$..R..h.| -00000040 aa b5 |..| +00000000 17 03 03 00 1e 00 00 00 00 00 00 00 01 35 25 df |.............5%.| +00000010 1f 16 81 00 e3 c4 9e 45 e2 a1 ef 54 72 66 99 3d |.......E...Trf.=| +00000020 30 13 25 15 03 03 00 1a 00 00 00 00 00 00 00 02 |0.%.............| +00000030 16 a5 e9 36 c1 fb 02 d7 c8 7a aa bc aa 77 7b 5c |...6.....z...w{\| +00000040 4f a1 |O.| diff --git a/crypto/tls/testdata/Client-TLSv13-ClientCert-ECDSA-RSA b/crypto/tls/testdata/Client-TLSv13-ClientCert-ECDSA-RSA index 8984790aab6..db13bd63876 100644 --- a/crypto/tls/testdata/Client-TLSv13-ClientCert-ECDSA-RSA +++ b/crypto/tls/testdata/Client-TLSv13-ClientCert-ECDSA-RSA @@ -17,124 +17,124 @@ 000000f0 bb 29 07 30 ff f6 84 af c4 cf c2 ed 90 99 5f 58 |.).0.........._X| 00000100 cb 3b 74 |.;t| >>> Flow 2 (server to client) -00000000 16 03 03 00 7a 02 00 00 76 03 03 ea 8b 99 cb 5b |....z...v......[| -00000010 d8 fb e9 14 7f 17 20 9c b8 41 01 dd ce 8a 90 4e |...... ..A.....N| -00000020 a9 f0 fb eb 71 37 24 02 d2 ee 96 20 00 00 00 00 |....q7$.... ....| +00000000 16 03 03 00 7a 02 00 00 76 03 03 37 0c 23 2f 26 |....z...v..7.#/&| +00000010 2a b0 8d 47 84 3b 9b 9c 7e 0f 0a cd 77 39 6c c2 |*..G.;..~...w9l.| +00000020 7b c0 56 a8 9d 07 a0 ec b6 e5 79 20 00 00 00 00 |{.V.......y ....| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 00 00 00 00 00 00 00 00 00 00 00 00 13 03 00 00 |................| -00000050 2e 00 2b 00 02 03 04 00 33 00 24 00 1d 00 20 c8 |..+.....3.$... .| -00000060 c6 52 8e 3c 6f 0d ab 2d be 4c e0 01 c8 af 1f 8e |.R........t| -00000100 69 88 5d 8e a4 fa 02 b0 6f f4 0e 38 cc 95 12 e2 |i.].....o..8....| -00000110 f1 e3 47 89 a6 1d 80 26 cb 23 6e f3 83 1c e4 85 |..G....&.#n.....| -00000120 7b 1a 4d 12 c8 bf ff 07 39 a9 4e 4e d7 45 35 23 |{.M.....9.NN.E5#| -00000130 9b f9 59 6d a5 b0 49 1b 5f e7 42 62 17 00 1e 57 |..Ym..I._.Bb...W| -00000140 53 c5 22 fb 05 89 fd fe 5d de 71 e8 26 fd 6d e3 |S.".....].q.&.m.| -00000150 fc b9 cb 1f d5 d4 84 d1 67 fe 8a a0 74 ff ad ff |........g...t...| -00000160 c8 35 fe c2 9a 17 33 18 51 c2 cd 19 7c dc 5d c5 |.5....3.Q...|.].| -00000170 7c e6 d0 38 ab 28 0b 8c 78 51 aa 7f ef f0 9a c3 ||..8.(..xQ......| -00000180 df 26 d2 bc 1b b6 98 b1 16 35 9d f0 73 b7 15 f7 |.&.......5..s...| -00000190 7d 9f 3e fe 4c 75 e7 c7 5d cb c2 e5 29 4a 30 32 |}.>.Lu..]...)J02| -000001a0 e2 da 3c 2c 16 ba 89 41 28 5c 33 75 b3 ed d1 e6 |..<,...A(\3u....| -000001b0 4f f6 bb 33 62 53 9d be fe d3 53 b5 bd 3e e3 b5 |O..3bS....S..>..| -000001c0 0a 37 67 60 33 c3 72 a8 ea 55 73 3c b2 7f ed 8b |.7g`3.r..Us<....| -000001d0 59 5e 44 e4 76 0d 1e 3c 3e 1c 9c 8c 86 3d 0a a3 |Y^D.v..<>....=..| -000001e0 78 bd 13 77 eb dc 22 e5 96 ff ae 44 94 cd ef ae |x..w.."....D....| -000001f0 ca 64 ec 06 a1 38 49 17 ce a5 c4 34 86 fd 55 1f |.d...8I....4..U.| -00000200 32 31 d5 b0 8c d6 b5 bc b8 29 29 97 b5 39 a0 f8 |21.......))..9..| -00000210 b1 b5 72 24 ff ce 6f 7d 6a 0d 18 26 8f 9a e8 d3 |..r$..o}j..&....| -00000220 e6 e0 7c 9c 56 45 ca 48 44 69 05 53 40 eb 96 c5 |..|.VE.HDi.S@...| -00000230 17 aa 28 56 20 ee 82 fd de d1 55 b8 e9 27 ae 3d |..(V .....U..'.=| -00000240 e1 44 d6 0b b9 7a 25 77 b0 f1 be 64 ae cc 0d 44 |.D...z%w...d...D| -00000250 af 57 32 9f cf bc c0 07 00 0b 19 97 08 0a d5 50 |.W2............P| -00000260 20 0e ef d5 1e 2e 68 82 ae 2d 84 47 3d 9b 5b 24 | .....h..-.G=.[$| -00000270 55 95 b2 93 e0 60 a2 cd e5 72 69 b3 e2 de da 70 |U....`...ri....p| -00000280 76 88 be 1f 5a 8e 7a d8 ff 94 db b0 92 b2 a1 a1 |v...Z.z.........| -00000290 26 5b 3b d5 5e 67 16 b7 6a 9f dc ab 21 7e df 6e |&[;.^g..j...!~.n| -000002a0 2a 73 e9 20 40 4b c8 34 fc 4b be f6 d8 ba 78 d7 |*s. @K.4.K....x.| -000002b0 a4 c2 ed a9 52 f9 ea 88 67 5e 10 92 8f ba 83 cd |....R...g^......| -000002c0 19 79 82 c5 76 06 d9 98 9f 68 2e 34 82 2f 9a 41 |.y..v....h.4./.A| -000002d0 fb 63 b6 8c 79 56 62 9d bb bc b5 22 ab 28 35 f7 |.c..yVb....".(5.| -000002e0 bc c6 a8 6b e7 86 01 c3 6c e5 88 28 57 09 65 31 |...k....l..(W.e1| -000002f0 b2 5c 0a 60 46 af 94 2c d4 37 49 9b 4c e4 4b bb |.\.`F..,.7I.L.K.| -00000300 fb 0d 87 94 82 11 09 26 04 6f ec e9 a6 f4 9c ca |.......&.o......| -00000310 79 a0 d0 48 32 5a 02 58 70 81 d0 b6 6d 77 2f 6f |y..H2Z.Xp...mw/o| -00000320 4e 9b db ca 82 38 ed a6 60 8e e2 b6 14 33 f5 02 |N....8..`....3..| -00000330 4b 97 a4 86 2f 43 9d ee 88 2c 1c 58 7e 47 30 cc |K.../C...,.X~G0.| -00000340 ec 0d aa 22 13 60 be e2 c9 c4 59 90 67 96 9b 2a |...".`....Y.g..*| -00000350 41 79 49 00 71 80 e9 0d 12 c3 17 03 03 00 99 ec |AyI.q...........| -00000360 f5 a1 45 64 61 fc 61 35 d5 2f bf 20 02 68 0b 10 |..Eda.a5./. .h..| -00000370 df c4 4b e7 2c 43 bc f5 3d 0b 7e 9f a4 71 09 2c |..K.,C..=.~..q.,| -00000380 a6 cf f4 f4 b4 2c 08 0c 03 50 ac 47 74 ad 24 f1 |.....,...P.Gt.$.| -00000390 04 f3 d4 83 42 3f 35 a5 57 ff ab 59 0c 9a a2 ca |....B?5.W..Y....| -000003a0 6c 30 b7 87 73 af 53 f9 1d 6b e7 44 ec d1 bd 14 |l0..s.S..k.D....| -000003b0 15 09 cf ff 82 5e a2 6d ba 00 53 b8 b3 7c 0e e5 |.....^.m..S..|..| -000003c0 d1 e2 a2 38 25 88 aa ee 93 c8 d9 d1 88 42 42 90 |...8%........BB.| -000003d0 43 8d 22 d8 48 02 57 22 6a f4 e9 23 71 f0 64 30 |C.".H.W"j..#q.d0| -000003e0 6a 68 12 a5 3c 8c 61 5e bc 73 91 6a 01 3a 14 14 |jh..<.a^.s.j.:..| -000003f0 86 7c 4d f5 aa cc 2f f5 17 03 03 00 35 7e f6 f5 |.|M.../.....5~..| -00000400 6b 75 e2 63 64 5a 6b 9b ce 6b 76 d7 47 bc 1b 47 |ku.cdZk..kv.G..G| -00000410 9e 68 25 fe 47 2b 06 a9 72 d0 a7 3f 23 3d 71 4a |.h%.G+..r..?#=qJ| -00000420 da 29 fb e3 dd ee e6 f6 a5 32 cc eb da 61 2b 52 |.).......2...a+R| -00000430 24 26 |$&| +00000050 2e 00 2b 00 02 03 04 00 33 00 24 00 1d 00 20 5e |..+.....3.$... ^| +00000060 9a da 1d cb 90 03 f2 d2 23 e3 54 fc 3d 9b 8c 92 |........#.T.=...| +00000070 42 df cf 7a 3d 47 3f 66 a4 a6 7a 07 44 76 5c 14 |B..z=G?f..z.Dv\.| +00000080 03 03 00 01 01 17 03 03 00 17 53 54 e6 de 6a af |..........ST..j.| +00000090 c2 d9 b8 39 b6 ae 3e 9f 54 60 e1 fa 29 5a fb 16 |...9..>.T`..)Z..| +000000a0 20 17 03 03 00 42 f4 a5 7f e3 e7 ba 6a 88 e1 f2 | ....B......j...| +000000b0 fd 25 3e ba 05 00 29 77 ff 69 6e e0 ac 50 99 50 |.%>...)w.in..P.P| +000000c0 ac 1e bd 8c 52 e6 28 5d 67 bb bb 20 61 69 5b 97 |....R.(]g.. ai[.| +000000d0 7f 29 79 97 bf 13 24 25 ad 3b 01 78 c4 a4 4e 9a |.)y...$%.;.x..N.| +000000e0 06 d6 20 da 63 27 97 8d 17 03 03 02 6d 79 7e 9e |.. .c'......my~.| +000000f0 01 98 a9 7c ba 63 43 2b 21 b1 bc 2c b2 17 c2 35 |...|.cC+!..,...5| +00000100 76 f2 30 01 69 45 d4 56 e0 5a 2c 62 aa 6f a8 1f |v.0.iE.V.Z,b.o..| +00000110 b8 31 df be 6f 3f 60 16 dc 61 5c 9f 99 a9 63 7d |.1..o?`..a\...c}| +00000120 7e 2b d2 ae 02 46 12 db be 51 9e 15 dd 1c 96 b0 |~+...F...Q......| +00000130 74 69 20 c0 e1 78 46 01 a6 23 72 28 ba c7 a3 48 |ti ..xF..#r(...H| +00000140 1e dc 0b 57 c9 b4 39 88 3d 39 c7 6c 38 c7 3a 29 |...W..9.=9.l8.:)| +00000150 a4 45 79 10 04 61 cd db df 5b 88 c1 35 4b 38 ea |.Ey..a...[..5K8.| +00000160 6d 72 57 9f e0 2e 37 61 3d c8 aa b2 25 a6 11 5c |mrW...7a=...%..\| +00000170 09 e2 3a 17 d3 c5 37 2f a7 b4 73 fe e2 61 df 1d |..:...7/..s..a..| +00000180 cd 4f 72 4a 67 c3 c7 e2 53 78 61 78 2c 37 44 12 |.OrJg...Sxax,7D.| +00000190 4d 0e 8c 14 0b de 3b a4 cf ad 8f d4 74 61 77 4b |M.....;.....tawK| +000001a0 36 2d f2 8f 68 95 38 9d e2 9f cf cc 03 15 89 b9 |6-..h.8.........| +000001b0 96 c4 47 e5 2f 65 0a 5c 5d 8f 5c 64 9d c8 76 d2 |..G./e.\].\d..v.| +000001c0 5d a7 90 4d f5 84 2d 31 2d 6c bd ee 0d 45 2b 50 |]..M..-1-l...E+P| +000001d0 79 f7 8c 34 40 f9 bc f4 38 b3 56 a9 6b ca 54 50 |y..4@...8.V.k.TP| +000001e0 19 f8 9a 73 74 9b 0a 92 ee 22 53 05 01 38 43 3a |...st...."S..8C:| +000001f0 49 fe 2d e9 39 c1 76 b0 04 df 8a 3d cc fc 9b 84 |I.-.9.v....=....| +00000200 cd 22 ba 40 24 69 93 b9 c5 b3 ed fd ad 94 1b 83 |.".@$i..........| +00000210 b5 07 a9 e7 94 14 4b c1 59 89 78 56 03 28 29 c3 |......K.Y.xV.().| +00000220 a8 a4 96 14 5a 51 9a 50 f9 34 3c 5a 76 8f 74 68 |....ZQ.P.4..K..k*D?.#..U| +000002f0 a0 11 51 04 0b 82 02 d9 24 85 13 2e d1 29 44 9a |..Q.....$....)D.| +00000300 15 7f a4 1b c4 f5 36 44 88 9a 6e 5a 1e 2f 14 fa |......6D..nZ./..| +00000310 d0 e7 fc 6a fa e5 f3 4a 55 20 73 9b e4 73 2e 47 |...j...JU s..s.G| +00000320 88 25 b7 69 d9 28 fe 50 8c fc f2 94 29 84 c4 7f |.%.i.(.P....)...| +00000330 d6 b2 eb 28 fa 51 8e ff 00 09 35 d3 b2 32 3a c6 |...(.Q....5..2:.| +00000340 bb 91 a7 c4 b7 88 df 4b f7 09 ef e7 e1 92 60 cd |.......K......`.| +00000350 de 34 4f 39 ee b8 ce 50 6a b9 17 03 03 00 99 64 |.4O9...Pj......d| +00000360 76 ab 48 eb 68 7d a6 68 60 aa c2 93 bd 31 81 c8 |v.H.h}.h`....1..| +00000370 b6 15 ba d1 54 94 04 1b 4b 29 86 e1 12 84 ad d5 |....T...K)......| +00000380 ba eb 4a 7a 7a a8 56 41 04 8c 84 c7 83 46 8c 50 |..Jzz.VA.....F.P| +00000390 c4 e3 02 d0 28 a4 fe 24 c4 b8 96 13 4f 87 27 ec |....(..$....O.'.| +000003a0 6b e3 84 4b 97 13 65 fa 1e 5e 9d ac 85 ea a0 3d |k..K..e..^.....=| +000003b0 67 96 e5 ec 88 84 6b 79 d9 16 55 c1 1c 72 17 aa |g.....ky..U..r..| +000003c0 9b 49 13 86 d4 39 0d 2a c8 88 b3 5f f5 11 cb 5f |.I...9.*..._..._| +000003d0 bd 22 57 2c bc c1 01 72 b9 c3 f5 d9 a2 3b 8e ff |."W,...r.....;..| +000003e0 44 b2 82 b6 5b 35 75 b5 7a 50 40 81 4e a7 2d 40 |D...[5u.zP@.N.-@| +000003f0 21 28 d9 c4 d9 e5 07 e1 17 03 03 00 35 23 ab b2 |!(..........5#..| +00000400 60 56 47 6a fe cb 0e 54 22 d5 8f 29 0a 34 e6 82 |`VGj...T"..).4..| +00000410 5b 10 35 ac 93 97 92 6b 39 5d b8 01 54 9c 86 b0 |[.5....k9]..T...| +00000420 41 70 52 88 92 cf dd b7 f8 5d d1 18 e1 1f 78 53 |ApR......]....xS| +00000430 e4 43 |.C| >>> Flow 3 (client to server) -00000000 14 03 03 00 01 01 17 03 03 02 1e 67 f8 08 c4 15 |...........g....| -00000010 47 a9 da 91 df f1 61 0f 50 21 f6 da dd a9 a6 e4 |G.....a.P!......| -00000020 00 05 29 62 d2 cc fe 14 57 a2 aa 46 16 b3 46 6e |..)b....W..F..Fn| -00000030 82 87 dc bb 1a d9 e7 c6 e9 1a 3b 7e a5 94 9b 7e |..........;~...~| -00000040 bb 07 b8 f3 de cc f1 85 d5 ee ff 0b 5a 19 c1 e3 |............Z...| -00000050 5f 47 f4 81 f4 d3 2d 85 f8 38 90 00 10 54 9a 3e |_G....-..8...T.>| -00000060 56 e4 99 a5 31 b1 dc d4 77 fe 28 3a b4 3e 63 42 |V...1...w.(:.>cB| -00000070 bc 05 c7 8a e5 d9 01 5c c9 18 39 1e 62 4f b4 58 |.......\..9.bO.X| -00000080 d3 9a 6a e1 a4 d3 ef 7f b8 0f 35 ac 2a 4a ba 77 |..j.......5.*J.w| -00000090 24 1e 24 6e 0e a2 8e f1 ba 5f fe 24 03 ed 1d e7 |$.$n....._.$....| -000000a0 43 2d fb bb 2e a9 a5 9a 18 11 39 99 a1 5a b7 92 |C-........9..Z..| -000000b0 37 91 f2 80 b2 f6 57 87 f9 a5 d9 da 36 b5 db 1a |7.....W.....6...| -000000c0 28 d8 9e f7 92 6f cf 0c 57 9a 95 42 ef 6a 50 f6 |(....o..W..B.jP.| -000000d0 38 4f 74 52 09 34 ca 8a d8 c7 a2 d2 69 bb db 13 |8OtR.4......i...| -000000e0 b1 ef f2 6d d3 f0 dd 5d b7 93 25 87 84 cd 87 6d |...m...]..%....m| -000000f0 c7 24 99 2b c8 02 25 1c 58 bc 98 03 33 15 ee 3e |.$.+..%.X...3..>| -00000100 96 d0 af 82 c7 74 71 ec ef a6 eb 88 20 55 1b fa |.....tq..... U..| -00000110 ea 01 38 5b 68 77 f1 3b 2a e6 d1 96 bc 28 b3 97 |..8[hw.;*....(..| -00000120 47 94 14 3f 6b 73 82 bf b0 ba fe 45 2a 45 45 1c |G..?ks.....E*EE.| -00000130 30 68 f5 74 07 15 18 00 a7 4f 62 df 7c ff 4c 0a |0h.t.....Ob.|.L.| -00000140 93 d6 60 8b 1f 0e 7f d3 88 43 90 f6 18 05 f1 ae |..`......C......| -00000150 ac 04 63 8b 43 b8 11 74 b1 87 e6 bc 2d 72 4a 2e |..c.C..t....-rJ.| -00000160 75 ab 16 5e 0e bc 76 6d d6 0e bc 39 0d 3c 76 01 |u..^..vm...9.fDcKC.%(D.-.| -000001b0 39 0e cd 15 a3 04 d9 db 4f a4 03 cb d2 84 da 41 |9.......O......A| -000001c0 5c 60 bf 00 77 19 68 b1 50 99 c2 88 d2 56 1e 29 |\`..w.h.P....V.)| -000001d0 c9 8c 83 98 7a 2f 04 e6 d6 09 5c 27 af a7 69 c4 |....z/....\'..i.| -000001e0 f7 4a ed b5 db d3 7a aa 75 f1 b2 8d 87 15 9a d1 |.J....z.u.......| -000001f0 00 fa 7f 58 06 2d 72 2b f7 27 b3 5b 76 57 21 35 |...X.-r+.'.[vW!5| -00000200 80 ce 8e bd e4 bb 45 01 c4 6b 43 f1 44 0a 5f 21 |......E..kC.D._!| -00000210 76 48 ae ce 8e 1d ba f7 7c 4f ae a2 d8 77 ce 9c |vH......|O...w..| -00000220 6d 69 b7 1e 78 ab 02 ed 15 17 03 03 00 a3 84 ed |mi..x...........| -00000230 ba 37 b6 6f 96 68 ef 9e b7 c6 23 5a 9f 1b ed ad |.7.o.h....#Z....| -00000240 3b 5d ab d4 22 d8 3e ab db db c5 b9 57 f8 68 d1 |;]..".>.....W.h.| -00000250 93 d5 36 fe 0c c0 fe 29 88 8b 63 ac 0e 06 4e bd |..6....)..c...N.| -00000260 9e b9 24 65 b5 9c e7 b9 58 4c 8a 07 10 9c 17 f7 |..$e....XL......| -00000270 c0 67 af ff c8 ff fc 87 1b fa c8 21 21 17 2d 43 |.g.........!!.-C| -00000280 f5 fc 4f 0d bf 01 58 b6 f1 58 08 39 f4 0d 94 69 |..O...X..X.9...i| -00000290 8f f0 c1 14 93 41 56 32 41 11 84 58 73 13 69 2a |.....AV2A..Xs.i*| -000002a0 ed 2a 34 61 73 8d 47 41 62 33 39 66 fa 3d 2a e5 |.*4as.GAb39f.=*.| -000002b0 bf 09 d6 c0 1e 3c 98 b3 86 a6 87 b5 a7 d2 cf d9 |.....<..........| -000002c0 dd f8 2e 86 f7 13 84 4a f7 3b ec 8e e5 06 f5 cd |.......J.;......| -000002d0 42 17 03 03 00 35 b2 38 87 30 58 9e 03 6e 44 dd |B....5.8.0X..nD.| -000002e0 fb 87 11 3a a0 e7 c1 2d 74 3b 35 d0 3f bc de cd |...:...-t;5.?...| -000002f0 71 61 8b 7c a5 7e c6 2d 76 67 44 9e 75 e5 9b 3b |qa.|.~.-vgD.u..;| -00000300 c5 2b 42 8a 4a 7f 0e 12 4c 2e 0f 17 03 03 00 17 |.+B.J...L.......| -00000310 d3 61 ed 75 dd 2e ee dd 79 fe d1 7c 4d 23 b1 95 |.a.u....y..|M#..| -00000320 ea 14 d6 27 d0 02 46 17 03 03 00 13 84 c1 07 6f |...'..F........o| -00000330 1c c6 22 a8 ae 6d a8 e8 62 54 ac b2 53 57 bb |.."..m..bT..SW.| +00000000 14 03 03 00 01 01 17 03 03 02 1e 19 c5 11 9d 64 |...............d| +00000010 8f f5 a9 21 53 fa cc 91 67 30 39 c0 77 d6 35 7b |...!S...g09.w.5{| +00000020 b8 a3 ae 44 8a 9a b1 68 5a 20 72 a6 ae 3a 1b 9f |...D...hZ r..:..| +00000030 03 eb d9 ed 91 20 49 ba 88 39 99 1c 4e 3a 2b 1b |..... I..9..N:+.| +00000040 42 b7 a3 97 a3 a3 6c 7e 3b 4c c1 74 dc 71 e6 14 |B.....l~;L.t.q..| +00000050 6c 5a 36 12 cb 87 a6 75 ce b3 e3 a8 f2 c8 36 12 |lZ6....u......6.| +00000060 3d c8 b8 2a 36 e4 40 38 3e 20 1d de 2a 31 b1 04 |=..*6.@8> ..*1..| +00000070 86 cb 9b c1 f3 fc 01 67 7e 40 0b b8 c4 fa 8f a0 |.......g~@......| +00000080 a7 5b 24 43 a9 d3 eb 55 99 ec 0b 19 cb ec 19 97 |.[$C...U........| +00000090 c8 0f c0 5e 8c b2 b1 93 80 70 7c 0b aa 7c 6c 44 |...^.....p|..|lD| +000000a0 1a 11 dc bd 0d 97 18 f3 ca c6 50 68 ca 77 ab 18 |..........Ph.w..| +000000b0 79 a9 8b 73 13 48 90 c3 4a cd ae f2 60 ea 0c 20 |y..s.H..J...`.. | +000000c0 eb ad 84 fb 2f 01 7e 2c f6 7d ea da 22 59 5f 88 |..../.~,.}.."Y_.| +000000d0 ff 10 19 81 d5 29 1b 0f 36 7b 84 66 eb bf e3 f9 |.....)..6{.f....| +000000e0 1c 68 fa 03 93 3e ba c6 58 e2 a9 57 94 8c a8 29 |.h...>..X..W...)| +000000f0 e2 f9 4b 6d 85 01 e8 f2 11 a2 04 38 73 8e 69 49 |..Km.......8s.iI| +00000100 4b 7f ca be aa 5f 50 ac 82 16 e6 92 78 87 13 f4 |K...._P.....x...| +00000110 fc 21 e8 2d 89 d0 f7 fb 73 0b f3 b7 6a 67 24 e8 |.!.-....s...jg$.| +00000120 d9 33 59 49 d6 88 24 a2 66 f5 c8 4d fe 88 93 77 |.3YI..$.f..M...w| +00000130 f9 3f ee ae 0b 6a 23 7a 8f b7 66 d4 68 7d 38 51 |.?...j#z..f.h}8Q| +00000140 85 0a a0 f5 03 f6 e8 2f cd 0b ac 58 64 82 38 20 |......./...Xd.8 | +00000150 f2 72 0a 85 83 55 cb db 62 59 f4 40 08 28 f9 8a |.r...U..bY.@.(..| +00000160 47 a2 ea a1 1b e1 4c 0a a4 74 cb a2 11 6f e5 68 |G.....L..t...o.h| +00000170 e4 ff 38 b0 a5 fc 21 9e eb de 43 b6 e7 27 cf 9a |..8...!...C..'..| +00000180 80 23 59 a2 e9 a8 12 ae 47 09 5c 48 c2 cb c8 e0 |.#Y.....G.\H....| +00000190 a6 fa 81 c5 49 a4 77 4b d3 83 0a ce 6e a5 2b 88 |....I.wK....n.+.| +000001a0 f2 f5 12 2f 0e 7e 10 20 5b c7 31 39 54 ed 19 33 |.../.~. [.19T..3| +000001b0 5c 94 b5 56 16 fa 0c b0 ec 28 76 fa 38 ca 08 c6 |\..V.....(v.8...| +000001c0 13 c3 1f 8a 20 35 73 4b bb bf d8 96 65 de cd f3 |.... 5sK....e...| +000001d0 44 d4 5b 3d 54 aa ac 53 a9 cc 31 99 86 22 5a f9 |D.[=T..S..1.."Z.| +000001e0 9e bd f1 f3 74 07 e4 fe f7 3a 35 44 e5 c6 48 3f |....t....:5D..H?| +000001f0 a3 81 1e 67 96 51 0f e6 7d 43 67 9c 12 6c dd 91 |...g.Q..}Cg..l..| +00000200 c4 f9 20 4d 88 41 fc 40 c5 ee c2 11 fb f1 67 da |.. M.A.@......g.| +00000210 7b b6 d0 1b f8 6e f7 8b 07 f2 9e 12 56 dc 75 31 |{....n......V.u1| +00000220 cd b9 53 62 3f 2f 72 cf ee 17 03 03 00 a4 f9 ec |..Sb?/r.........| +00000230 72 94 94 1c 52 ab 9e 6d 04 5a 26 07 15 3a f5 dd |r...R..m.Z&..:..| +00000240 f3 45 18 20 de 2e 97 f7 6a a4 7c 92 68 aa 71 55 |.E. ....j.|.h.qU| +00000250 b7 7a 3c 8f 54 e7 cc 31 e1 54 9c ad 8e b4 57 11 |.z<.T..1.T....W.| +00000260 1d 79 85 4a da 3f 1b ab fb f4 d4 d9 4d 8a 2e da |.y.J.?......M...| +00000270 68 3e f9 aa 16 52 cc 4e 49 7a 00 bf fc e8 b5 16 |h>...R.NIz......| +00000280 43 0c 6d aa 82 49 3c 16 43 56 55 35 ee 47 c3 1c |C.m..I<.CVU5.G..| +00000290 99 25 6d 30 89 64 5e 23 bf de fc cc 7c 40 94 28 |.%m0.d^#....|@.(| +000002a0 d8 ed ec c2 e2 8b 24 64 64 b6 e8 6c 29 82 b5 ba |......$dd..l)...| +000002b0 d5 59 7a 6f 11 6e cc 30 91 c8 c0 8b 9f dd 13 59 |.Yzo.n.0.......Y| +000002c0 a9 72 18 f6 6e ce 3a 47 6b 4f 26 55 61 bf 20 7a |.r..n.:GkO&Ua. z| +000002d0 d8 c3 17 03 03 00 35 d7 fc bc 10 29 75 c3 70 0a |......5....)u.p.| +000002e0 02 e4 cf 36 20 49 5d 64 78 e9 27 db 2d e3 1a 66 |...6 I]dx.'.-..f| +000002f0 f3 a8 82 7e 64 f0 29 27 81 6c bd b7 a7 86 a2 6a |...~d.)'.l.....j| +00000300 ac 4e 7b da 48 7c d8 9c 39 6c 95 45 17 03 03 00 |.N{.H|..9l.E....| +00000310 17 c9 13 54 e9 22 62 7e 89 17 de 98 52 93 26 76 |...T."b~....R.&v| +00000320 73 a0 7d 2c 60 68 c8 68 17 03 03 00 13 ad 3a 53 |s.},`h.h......:S| +00000330 d1 41 0e 99 26 c8 fb 22 8f e6 d3 a4 1d 83 ff 28 |.A..&..".......(| diff --git a/crypto/tls/testdata/Server-TLSv10-ECDHE-ECDSA-AES b/crypto/tls/testdata/Server-TLSv10-ECDHE-ECDSA-AES index c5d947c2ce8..3bc3a75b366 100644 --- a/crypto/tls/testdata/Server-TLSv10-ECDHE-ECDSA-AES +++ b/crypto/tls/testdata/Server-TLSv10-ECDHE-ECDSA-AES @@ -1,7 +1,7 @@ >>> Flow 1 (client to server) -00000000 16 03 01 00 51 01 00 00 4d 03 01 5e bf ff e7 c2 |....Q...M..^....| -00000010 c1 98 4a a3 cf 5a e8 8d 8f 19 9e 85 48 5b 92 cc |..J..Z......H[..| -00000020 7d 0c 14 1e 2e 50 5b d7 dd fe ef 00 00 04 c0 0a |}....P[.........| +00000000 16 03 01 00 51 01 00 00 4d 03 01 76 4b 58 ef 57 |....Q...M..vKX.W| +00000010 d5 8d ba b7 0b fe d4 b3 2b a7 76 7c f1 72 59 39 |........+.v|.rY9| +00000020 fa 02 66 88 4a 55 72 15 9e 42 8c 00 00 04 c0 0a |..f.JUr..B......| 00000030 00 ff 01 00 00 20 00 0b 00 04 03 00 01 02 00 0a |..... ..........| 00000040 00 0c 00 0a 00 1d 00 17 00 1e 00 19 00 18 00 16 |................| 00000050 00 00 00 17 00 00 |......| @@ -43,37 +43,37 @@ 00000220 c1 33 13 83 0d 94 06 bb d4 37 7a f6 ec 7a c9 86 |.3.......7z..z..| 00000230 2e dd d7 11 69 7f 85 7c 56 de fb 31 78 2b e4 c7 |....i..|V..1x+..| 00000240 78 0d ae cb be 9e 4e 36 24 31 7b 6a 0f 39 95 12 |x.....N6$1{j.9..| -00000250 07 8f 2a 16 03 01 00 b5 0c 00 00 b1 03 00 1d 20 |..*............ | +00000250 07 8f 2a 16 03 01 00 b3 0c 00 00 af 03 00 1d 20 |..*............ | 00000260 2f e5 7d a3 47 cd 62 43 15 28 da ac 5f bb 29 07 |/.}.G.bC.(.._.).| 00000270 30 ff f6 84 af c4 cf c2 ed 90 99 5f 58 cb 3b 74 |0.........._X.;t| -00000280 00 8b 30 81 88 02 42 00 c3 eb 60 b8 d3 af cb 2d |..0...B...`....-| -00000290 4f ca 46 6d e4 fe 47 41 82 1e d4 14 0f 08 ab b6 |O.Fm..GA........| -000002a0 b8 41 8b 46 f5 28 bb 87 28 73 a0 5c e9 ce f5 56 |.A.F.(..(s.\...V| -000002b0 11 02 17 2c 39 b6 28 6c ec de 12 bf 22 91 3d 06 |...,9.(l....".=.| -000002c0 ac 8e 0a 92 b1 46 69 86 44 02 42 01 fd 70 6e 63 |.....Fi.D.B..pnc| -000002d0 1b 2a 21 47 9b 42 9c d4 4a 38 20 dd 94 05 c4 0f |.*!G.B..J8 .....| -000002e0 5d b2 48 c8 17 90 01 4d 4f 7e 7a ef bb b2 5b 26 |].H....MO~z...[&| -000002f0 7e e1 24 f5 80 93 69 72 3f cf bb 5d 52 ee ec b4 |~.$...ir?..]R...| -00000300 cc 0c 96 1f 93 4c d6 a8 c7 b2 91 f5 6d 16 03 01 |.....L......m...| -00000310 00 04 0e 00 00 00 |......| +00000280 00 89 30 81 86 02 41 00 b5 7c a4 63 77 fa 75 cd |..0...A..|.cw.u.| +00000290 82 a5 75 15 08 09 e8 6d e9 ba 07 1f f9 9c 24 a5 |..u....m......$.| +000002a0 30 08 d0 51 3b d1 82 14 14 dd 5a 5d c9 2d 91 6f |0..Q;.....Z].-.o| +000002b0 b3 92 30 f1 38 36 e8 34 9e 99 50 a0 c4 29 04 ef |..0.86.4..P..)..| +000002c0 97 f3 cd dc be 22 86 b9 02 41 6a dd 3a 57 5b 61 |....."...Aj.:W[a| +000002d0 ff 68 7d 1e 5e bb 67 5f 76 44 7c f2 f2 03 95 f2 |.h}.^.g_vD|.....| +000002e0 e0 1a 53 70 ce b0 fa cc 7a f3 9a e3 2f 37 a3 cf |..Sp....z.../7..| +000002f0 b5 ca 1d fb fe a3 0d e2 d6 c1 d2 7d 48 80 5b 82 |...........}H.[.| +00000300 56 29 1b b7 43 2e b3 38 19 39 49 16 03 01 00 04 |V)..C..8.9I.....| +00000310 0e 00 00 00 |....| >>> Flow 3 (client to server) -00000000 16 03 01 00 25 10 00 00 21 20 ec f2 2d ca 02 ce |....%...! ..-...| -00000010 11 2d eb 26 d7 d9 fc b2 a7 2d 34 5b a9 3a 0b 2f |.-.&.....-4[.:./| -00000020 5c 49 a9 69 1a 3a 83 90 ec 5f 14 03 01 00 01 01 |\I.i.:..._......| -00000030 16 03 01 00 30 9f 06 c7 a7 a0 c3 a5 3d 60 6e fb |....0.......=`n.| -00000040 c6 18 a4 d2 80 2e ad 8f cf 92 84 94 36 f8 81 28 |............6..(| -00000050 c5 3f 37 e8 d6 e7 6d a3 f5 32 63 a0 ab 7a db 12 |.?7...m..2c..z..| -00000060 17 e1 e4 33 d6 |...3.| +00000000 16 03 01 00 25 10 00 00 21 20 82 03 ad 10 45 0a |....%...! ....E.| +00000010 b9 a4 85 0d 88 bb 9e 16 f1 6c 6a 17 c0 11 09 48 |.........lj....H| +00000020 b4 8b 27 4e 3a 45 a1 b7 a2 03 14 03 01 00 01 01 |..'N:E..........| +00000030 16 03 01 00 30 33 5d a1 85 df 96 6d cf a1 b3 c4 |....03]....m....| +00000040 3f 3c 40 aa 05 25 af 62 ee e9 ce 48 ba e8 08 88 |?<@..%.b...H....| +00000050 95 77 c7 f1 87 c6 ce 46 a2 50 2f 41 3c 8f bf 1a |.w.....F.P/A<...| +00000060 1e 1e 1b 39 9c |...9.| >>> Flow 4 (server to client) -00000000 14 03 01 00 01 01 16 03 01 00 30 18 29 35 d7 c5 |..........0.)5..| -00000010 a2 31 3b 26 85 de 50 26 39 4d 16 22 58 a2 17 bd |.1;&..P&9M."X...| -00000020 4b 73 33 8d dc 3f 92 20 f2 ca 22 00 f5 31 db a7 |Ks3..?. .."..1..| -00000030 18 79 fc 71 87 68 a5 1d a6 db 33 17 03 01 00 20 |.y.q.h....3.... | -00000040 0d be 57 e4 12 6d 2d 3a 33 24 a0 0c c4 9b 27 09 |..W..m-:3$....'.| -00000050 85 e0 0e 42 04 79 21 9a bf 47 fa 0b 38 1a ce 8f |...B.y!..G..8...| -00000060 17 03 01 00 30 6d 27 f1 9b cf 55 4d 65 48 38 1b |....0m'...UMeH8.| -00000070 d9 dd 1d 5b 81 2f 10 a5 65 28 83 93 b3 b1 3a 72 |...[./..e(....:r| -00000080 f0 15 9a e5 9f 21 80 f1 59 a5 0e f1 0c 2b d1 0c |.....!..Y....+..| -00000090 d4 27 73 f3 7e 15 03 01 00 20 6f 08 27 3a d2 60 |.'s.~.... o.':.`| -000000a0 c3 27 bc 73 55 bb 43 53 e2 e0 87 16 ca 8f 49 f0 |.'.sU.CS......I.| -000000b0 88 a8 20 30 9d 42 86 d9 c3 36 |.. 0.B...6| +00000000 14 03 01 00 01 01 16 03 01 00 30 7b 30 af df 92 |..........0{0...| +00000010 2b ee 4d 02 e3 6c 6f 8b 72 32 16 0e 4d ba 71 0d |+.M..lo.r2..M.q.| +00000020 86 0d f5 7d fe dd 07 05 3d fe 70 9b 7f d9 2b c6 |...}....=.p...+.| +00000030 7e 04 82 5f ef 0c 0b c1 e7 a5 18 17 03 01 00 20 |~.._........... | +00000040 ad bd 43 a6 10 e8 e2 41 39 35 69 af a0 00 5f 81 |..C....A95i..._.| +00000050 1e 0a 5e 78 45 2f 66 27 cb 4f db 06 22 c0 ea 0f |..^xE/f'.O.."...| +00000060 17 03 01 00 30 16 69 7f fa 1c 89 36 9f 99 c6 49 |....0.i....6...I| +00000070 e6 0d 9a b7 81 00 75 f5 2d cc 89 e8 be 47 64 76 |......u.-....Gdv| +00000080 ef 34 27 a2 46 bd 8c 14 5b 15 67 ab f1 d9 30 c3 |.4'.F...[.g...0.| +00000090 df 96 a1 59 17 15 03 01 00 20 77 ea 6f e2 ae 81 |...Y..... w.o...| +000000a0 80 4f 37 7b ee 37 3f 40 df a3 e4 be 80 1e 3e 83 |.O7{.7?@......>.| +000000b0 9b 42 f7 95 50 af ab a5 60 54 |.B..P...`T| diff --git a/crypto/tls/testdata/Server-TLSv12-ECDHE-ECDSA-AES b/crypto/tls/testdata/Server-TLSv12-ECDHE-ECDSA-AES index 697b8102dd3..f0d266bdc7a 100644 --- a/crypto/tls/testdata/Server-TLSv12-ECDHE-ECDSA-AES +++ b/crypto/tls/testdata/Server-TLSv12-ECDHE-ECDSA-AES @@ -1,7 +1,7 @@ >>> Flow 1 (client to server) -00000000 16 03 01 00 85 01 00 00 81 03 03 83 21 a6 e4 ea |............!...| -00000010 e9 7b 3a 7c 72 28 ee 68 c5 c7 fa f1 98 ed 4a be |.{:|r(.h......J.| -00000020 b8 42 13 fb d3 ab 63 16 d2 74 c8 00 00 04 c0 0a |.B....c..t......| +00000000 16 03 01 00 85 01 00 00 81 03 03 19 8a e1 c7 50 |...............P| +00000010 ba 63 15 9b d5 85 f1 8c 55 43 d3 ce 9c d6 35 20 |.c......UC....5 | +00000020 f3 49 3d 55 a5 11 57 6d db 42 1d 00 00 04 c0 0a |.I=U..Wm.B......| 00000030 00 ff 01 00 00 54 00 0b 00 04 03 00 01 02 00 0a |.....T..........| 00000040 00 0c 00 0a 00 1d 00 17 00 1e 00 19 00 18 00 16 |................| 00000050 00 00 00 17 00 00 00 0d 00 30 00 2e 04 03 05 03 |.........0......| @@ -46,39 +46,39 @@ 00000220 c1 33 13 83 0d 94 06 bb d4 37 7a f6 ec 7a c9 86 |.3.......7z..z..| 00000230 2e dd d7 11 69 7f 85 7c 56 de fb 31 78 2b e4 c7 |....i..|V..1x+..| 00000240 78 0d ae cb be 9e 4e 36 24 31 7b 6a 0f 39 95 12 |x.....N6$1{j.9..| -00000250 07 8f 2a 16 03 03 00 b7 0c 00 00 b3 03 00 1d 20 |..*............ | +00000250 07 8f 2a 16 03 03 00 b6 0c 00 00 b2 03 00 1d 20 |..*............ | 00000260 2f e5 7d a3 47 cd 62 43 15 28 da ac 5f bb 29 07 |/.}.G.bC.(.._.).| 00000270 30 ff f6 84 af c4 cf c2 ed 90 99 5f 58 cb 3b 74 |0.........._X.;t| -00000280 04 03 00 8b 30 81 88 02 42 00 b9 39 44 59 12 77 |....0...B..9DY.w| -00000290 8d e2 79 25 01 d1 6a 05 3d 53 ea f3 91 d6 c5 09 |..y%..j.=S......| -000002a0 24 bd 0c ad 24 cc 1c a7 fb 03 eb 0a 0d f4 30 96 |$...$.........0.| -000002b0 8d 28 a1 b3 64 ba 30 27 95 29 23 22 91 62 c3 1f |.(..d.0'.)#".b..| -000002c0 51 aa c8 be 17 85 31 8e f5 40 3e 02 42 00 ee a1 |Q.....1..@>.B...| -000002d0 64 14 a1 52 b3 e5 54 c9 24 53 94 5a 43 d8 4f 79 |d..R..T.$S.ZC.Oy| -000002e0 69 4b a8 51 ee de b3 b0 f7 1a 57 a3 28 72 d2 13 |iK.Q......W.(r..| -000002f0 a6 d3 17 0b c4 45 34 7f 10 3b 81 cb 0c 8d 51 b6 |.....E4..;....Q.| -00000300 0b 86 21 d0 ee 1d 7e 73 6b ea 77 8c 66 dc 65 16 |..!...~sk.w.f.e.| -00000310 03 03 00 04 0e 00 00 00 |........| +00000280 04 03 00 8a 30 81 87 02 42 01 f2 09 77 4a e7 f5 |....0...B...wJ..| +00000290 a8 35 3b dd 9d 62 5a 07 97 1e 76 93 b6 07 21 3e |.5;..bZ...v...!>| +000002a0 c8 fd 99 35 50 8a 8b ad e5 de 03 07 c8 5e fe 03 |...5P........^..| +000002b0 c1 99 04 ad 53 b6 76 67 eb 04 99 54 11 4d 4d e9 |....S.vg...T.MM.| +000002c0 74 3f 89 6e d9 c8 02 98 c5 3c cf 02 41 4e 64 21 |t?.n.....<..ANd!| +000002d0 1a 01 5f 2e 89 17 cc 65 33 d0 59 ed 17 59 c4 43 |.._....e3.Y..Y.C| +000002e0 0a fc 68 30 9c e2 c3 86 fb 2a c1 4a ae 32 ef 1d |..h0.....*.J.2..| +000002f0 06 27 36 7d d5 cd 68 23 4c e9 7e 64 b8 eb 42 05 |.'6}..h#L.~d..B.| +00000300 ef 83 36 b2 9e a7 ae 1a cd b0 3a 17 3a 46 16 03 |..6.......:.:F..| +00000310 03 00 04 0e 00 00 00 |.......| >>> Flow 3 (client to server) -00000000 16 03 03 00 25 10 00 00 21 20 ed 3e ba a7 43 53 |....%...! .>..CS| -00000010 5e e4 60 aa 31 3f e1 69 60 32 25 3d fd 8b 32 da |^.`.1?.i`2%=..2.| -00000020 f2 c5 db c7 02 e6 4d d0 de 15 14 03 03 00 01 01 |......M.........| -00000030 16 03 03 00 40 ee 28 f2 27 82 24 9d 17 d1 48 7a |....@.(.'.$...Hz| -00000040 74 2d dd 16 18 b7 70 97 2f 2b 91 47 eb c2 1d ae |t-....p./+.G....| -00000050 3f 48 52 cd ff e7 9e 0b 35 ad 1f 60 5e 07 b1 5e |?HR.....5..`^..^| -00000060 1c ba 6a 85 bb 6b 30 94 41 8a 59 81 cf 37 5f 26 |..j..k0.A.Y..7_&| -00000070 b1 52 36 5f df |.R6_.| +00000000 16 03 03 00 25 10 00 00 21 20 73 43 c2 08 92 f5 |....%...! sC....| +00000010 db bf 2f 8a eb 49 55 f7 5d 6b 80 64 f7 d9 75 1f |../..IU.]k.d..u.| +00000020 67 f6 35 21 3c 95 3f 1c 04 1a 14 03 03 00 01 01 |g.5!<.?.........| +00000030 16 03 03 00 40 59 bb 5a 5d 76 73 a5 30 0e 29 d3 |....@Y.Z]vs.0.).| +00000040 17 d8 2f 30 e6 ed 02 c6 83 12 44 42 d8 79 86 e0 |../0......DB.y..| +00000050 78 7b 43 8d 5b 7c 85 42 fb 7c 67 b0 d0 71 03 0e |x{C.[|.B.|g..q..| +00000060 d0 6b b6 06 f1 16 72 c0 16 66 cf 53 df ae 62 3b |.k....r..f.S..b;| +00000070 f3 57 52 4d 08 |.WRM.| >>> Flow 4 (server to client) 00000000 14 03 03 00 01 01 16 03 03 00 40 00 00 00 00 00 |..........@.....| -00000010 00 00 00 00 00 00 00 00 00 00 00 f5 05 5a a6 22 |.............Z."| -00000020 90 4e 8d d9 f1 55 c4 78 f2 ec 9d 97 cd fe af ae |.N...U.x........| -00000030 b7 62 00 67 2e b2 d9 1e 0c a3 c8 6a bf d2 3c 42 |.b.g.......j..u......| +00000080 cd 14 5b 4b 0a 7b a2 e6 54 b3 bd 3c f0 eb ca 78 |..[K.{..T..<...x| 00000090 15 03 03 00 30 00 00 00 00 00 00 00 00 00 00 00 |....0...........| -000000a0 00 00 00 00 00 13 3e 42 a5 61 84 ae 49 8b b9 91 |......>B.a..I...| -000000b0 c2 a3 76 74 1e 4f 53 0a fc 71 de 0d d2 44 c8 ac |..vt.OS..q...D..| -000000c0 2e 09 27 e6 ad |..'..| +000000a0 00 00 00 00 00 e6 4b 35 cc 69 58 89 49 67 99 f4 |......K5.iX.Ig..| +000000b0 c2 14 2a bb e7 21 2b fe fe b5 60 ae b2 2a 96 15 |..*..!+...`..*..| +000000c0 e0 65 d2 54 0b |.e.T.| diff --git a/crypto/tls/testdata/Server-TLSv13-ECDHE-ECDSA-AES b/crypto/tls/testdata/Server-TLSv13-ECDHE-ECDSA-AES index d2ed2ee64e9..d80662d169a 100644 --- a/crypto/tls/testdata/Server-TLSv13-ECDHE-ECDSA-AES +++ b/crypto/tls/testdata/Server-TLSv13-ECDHE-ECDSA-AES @@ -1,93 +1,93 @@ >>> Flow 1 (client to server) -00000000 16 03 01 00 ca 01 00 00 c6 03 03 be 5b 8c 08 2b |............[..+| -00000010 26 a0 41 0f e3 4e b6 5c 9f 5d 53 04 67 4a 1d a2 |&.A..N.\.]S.gJ..| -00000020 26 3b 83 ab b4 7b c6 ec f8 a6 41 20 a6 de ad e2 |&;...{....A ....| -00000030 0c fd 02 99 11 51 c6 be e8 52 df 0b e2 b3 6f fe |.....Q...R....o.| -00000040 33 3e 2f 90 ac d2 e8 a2 53 8b d9 05 00 04 13 01 |3>/.....S.......| +00000000 16 03 01 00 ca 01 00 00 c6 03 03 1d 95 21 d3 93 |.............!..| +00000010 6b 69 ad 44 69 28 2d 2e 74 c3 77 24 86 82 52 91 |ki.Di(-.t.w$..R.| +00000020 a8 15 64 82 15 2e 02 f8 41 3d c5 20 87 ff 55 4c |..d.....A=. ..UL| +00000030 00 16 80 c2 f7 44 15 18 bc 00 81 d8 7b d8 2c 88 |.....D......{.,.| +00000040 cb 19 31 89 23 d0 82 be d8 7f a4 26 00 04 13 01 |..1.#......&....| 00000050 00 ff 01 00 00 79 00 0b 00 04 03 00 01 02 00 0a |.....y..........| 00000060 00 0c 00 0a 00 1d 00 17 00 1e 00 19 00 18 00 16 |................| 00000070 00 00 00 17 00 00 00 0d 00 1e 00 1c 04 03 05 03 |................| 00000080 06 03 08 07 08 08 08 09 08 0a 08 0b 08 04 08 05 |................| 00000090 08 06 04 01 05 01 06 01 00 2b 00 03 02 03 04 00 |.........+......| -000000a0 2d 00 02 01 01 00 33 00 26 00 24 00 1d 00 20 31 |-.....3.&.$... 1| -000000b0 8e dd f4 7c cf 22 04 c1 c3 04 5c 24 49 db ae ab |...|."....\$I...| -000000c0 0a d0 42 e8 70 51 c7 4f 88 e2 4e 2e 0b 80 65 |..B.pQ.O..N...e| +000000a0 2d 00 02 01 01 00 33 00 26 00 24 00 1d 00 20 8d |-.....3.&.$... .| +000000b0 18 6e 7e 5a 97 58 25 0d 07 9e af 9c 9b bd 6f 92 |.n~Z.X%.......o.| +000000c0 e9 08 8f 92 55 28 d2 90 3f fe bc dd db b7 00 |....U(..?......| >>> Flow 2 (server to client) 00000000 16 03 03 00 7a 02 00 00 76 03 03 00 00 00 00 00 |....z...v.......| 00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| -00000020 00 00 00 00 00 00 00 00 00 00 00 20 a6 de ad e2 |........... ....| -00000030 0c fd 02 99 11 51 c6 be e8 52 df 0b e2 b3 6f fe |.....Q...R....o.| -00000040 33 3e 2f 90 ac d2 e8 a2 53 8b d9 05 13 01 00 00 |3>/.....S.......| +00000020 00 00 00 00 00 00 00 00 00 00 00 20 87 ff 55 4c |........... ..UL| +00000030 00 16 80 c2 f7 44 15 18 bc 00 81 d8 7b d8 2c 88 |.....D......{.,.| +00000040 cb 19 31 89 23 d0 82 be d8 7f a4 26 13 01 00 00 |..1.#......&....| 00000050 2e 00 2b 00 02 03 04 00 33 00 24 00 1d 00 20 2f |..+.....3.$... /| 00000060 e5 7d a3 47 cd 62 43 15 28 da ac 5f bb 29 07 30 |.}.G.bC.(.._.).0| 00000070 ff f6 84 af c4 cf c2 ed 90 99 5f 58 cb 3b 74 14 |.........._X.;t.| -00000080 03 03 00 01 01 17 03 03 00 17 cc d0 60 a1 dc d6 |............`...| -00000090 46 57 69 3d df 0e 0f 7f a8 34 2d 87 71 84 16 54 |FWi=.....4-.q..T| -000000a0 9d 17 03 03 02 22 cb 9c f9 3e a0 fd bf 07 03 7c |....."...>.....|| -000000b0 53 0c 15 22 0b 78 e5 02 36 e6 e7 6c 5b f9 aa 8d |S..".x..6..l[...| -000000c0 54 8e b1 15 d4 23 05 12 5e 6e 0f 0f 65 77 bf b5 |T....#..^n..ew..| -000000d0 32 28 0e 32 ca 9f 61 c3 37 23 87 e8 ec 19 1d ba |2(.2..a.7#......| -000000e0 ef f5 18 eb ba 49 2d 86 a6 d1 f7 c1 9e 67 10 9f |.....I-......g..| -000000f0 a1 d2 62 bd 4c 6c 5e a4 41 f6 1e fa fd e7 55 bc |..b.Ll^.A.....U.| -00000100 16 ad 91 91 de 03 86 d7 e1 88 87 ab 0e f4 f5 bb |................| -00000110 16 da 37 bb a4 ce 4e 6c 5f 88 41 f9 a2 90 9a 2d |..7...Nl_.A....-| -00000120 5c 14 d5 01 28 06 a9 20 a4 ae 92 17 c5 95 b1 dc |\...(.. ........| -00000130 02 a8 3f 3b a7 97 91 5a 4f 56 bb db b6 30 0d 80 |..?;...ZOV...0..| -00000140 35 ac 91 6f 4f ba 1e 10 c6 fc d2 ca 96 e4 9d 1f |5..oO...........| -00000150 2f 29 00 3a 11 b7 77 dd d5 ed 76 9f 67 a1 b1 0c |/).:..w...v.g...| -00000160 5d 34 34 eb 42 49 23 15 49 12 24 24 73 be a9 65 |]44.BI#.I.$$s..e| -00000170 99 b6 b4 3f 18 0c d1 32 26 eb 86 93 5c 0e e8 06 |...?...2&...\...| -00000180 fb d7 9f 0e d9 26 14 47 b8 e5 67 8c c8 cb 0c 55 |.....&.G..g....U| -00000190 61 70 a9 ce 0d 4e bf ca 40 9c e7 0d 2b 5d 54 b7 |ap...N..@...+]T.| -000001a0 5a 64 50 e6 a1 c9 fc 47 7d e7 0a 13 36 8d 70 eb |ZdP....G}...6.p.| -000001b0 68 65 e4 9b 9d 12 d1 d9 0d 32 72 59 0f 46 b2 e2 |he.......2rY.F..| -000001c0 21 ab 13 d4 ab f3 6e b6 44 16 b8 85 bb dc f4 f7 |!.....n.D.......| -000001d0 d6 ce 5c 9f c0 4c 28 8f 36 39 ec 29 c7 33 bd ea |..\..L(.69.).3..| -000001e0 2d 10 16 84 50 c5 18 5b 2c a3 99 bb 3b 0b 70 66 |-...P..[,...;.pf| -000001f0 72 9a 83 01 06 2a bf 4a 60 c5 5d 41 a1 f0 92 bb |r....*.J`.]A....| -00000200 3b 2a 1a 41 3a 57 c3 22 13 2c b4 7b 3e 47 52 ea |;*.A:W.".,.{>GR.| -00000210 79 8a bf ef 2e 2c f7 89 c7 36 5a df 38 c2 04 b6 |y....,...6Z.8...| -00000220 6f 96 cd 7c 01 b3 e3 cd 4a 83 56 40 06 58 8a 7c |o..|....J.V@.X.|| -00000230 8c 75 df b6 b8 76 63 71 89 72 0a 64 de 23 7d 50 |.u...vcq.r.d.#}P| -00000240 77 a8 f6 a0 81 9d e9 ed 81 5e 20 c8 9f 65 3c 95 |w........^ ..e<.| -00000250 cf ed 99 80 71 06 5e 00 46 0d 0c 22 b3 88 f0 c5 |....q.^.F.."....| -00000260 33 3e 13 6b f2 07 9c db 20 31 9c 8d ea d7 73 e8 |3>.k.... 1....s.| -00000270 00 e1 2b f4 c8 d7 34 37 4a 98 b9 4d 28 db 15 8a |..+...47J..M(...| -00000280 af 53 14 3b 02 54 a3 0b 5f 10 ff 5d 20 1c 19 ae |.S.;.T.._..] ...| -00000290 6b 8a 99 a5 8f e0 ac dd c1 ba 1f 85 56 a3 94 bc |k...........V...| -000002a0 79 03 5f d5 dd e1 8e 62 b7 82 fa 92 c3 d5 8a fc |y._....b........| -000002b0 6b 17 24 d9 af db 3d 9c 0f 51 82 3d a2 ec 5f 9c |k.$...=..Q.=.._.| -000002c0 dc 69 a5 ce db d8 8b 87 17 03 03 00 a3 69 cd 7b |.i...........i.{| -000002d0 9f ac ad 72 11 b2 5d ee 19 63 d0 35 12 6d 5e 3f |...r..]..c.5.m^?| -000002e0 81 a8 18 4a d4 09 f3 80 38 4a 31 08 3e a0 4c 78 |...J....8J1.>.Lx| -000002f0 48 08 e9 90 ba e7 2a b4 73 2e 2b 2b 15 60 ce 09 |H.....*.s.++.`..| -00000300 7d df 49 31 e1 9d ff 92 1d b4 af 2e 8c f8 a6 2e |}.I1............| -00000310 93 d7 b9 10 69 10 7f 04 0d 8d e2 37 09 a7 d0 2a |....i......7...*| -00000320 ac ea 51 49 50 1d 1c 54 7f b9 15 ad 8c 77 ef 1d |..QIP..T.....w..| -00000330 a6 59 a3 bf b2 53 f7 6c 21 92 e0 36 c5 0d 61 94 |.Y...S.l!..6..a.| -00000340 be 61 5e 77 25 35 df e4 5f 67 c1 c6 af 51 e4 ce |.a^w%5.._g...Q..| -00000350 c4 28 c5 4e bc f6 c6 ba 32 dc 8e c7 45 f3 4d a1 |.(.N....2...E.M.| -00000360 70 53 98 46 8f 39 c2 cc b7 fc f7 24 11 97 72 b3 |pS.F.9.....$..r.| -00000370 17 03 03 00 35 76 be b6 7a 3f e3 08 7a a2 65 25 |....5v..z?..z.e%| -00000380 fd 0b c3 87 be ba eb ca cb 3d c1 25 10 e0 7b 00 |.........=.%..{.| -00000390 37 7a 52 9e d6 b2 e7 ba 8e 51 de 15 c4 e8 16 eb |7zR......Q......| -000003a0 c6 21 92 42 b1 62 f4 ce 27 ba 17 03 03 00 8b 54 |.!.B.b..'......T| -000003b0 03 de d7 a7 85 2f 4b 23 2d d5 3a b4 3d 3d f6 00 |...../K#-.:.==..| -000003c0 ac ab bd 6f dd bf 9f 24 fb 1b d4 01 39 3e c0 87 |...o...$....9>..| -000003d0 bb 32 ca f6 61 b2 ef 5d 9c 2c 1b a5 10 66 7b fd |.2..a..].,...f{.| -000003e0 4b d0 03 dc 53 a9 0d 5a d5 c4 4c 25 9c 55 e6 f8 |K...S..Z..L%.U..| -000003f0 d1 d8 49 dc 36 a1 92 ae f1 3e 2f 11 66 87 93 69 |..I.6....>/.f..i| -00000400 24 2e 5d 6c f6 79 15 68 a8 99 2e 1a 9c e2 85 4e |$.]l.y.h.......N| -00000410 5f d6 a8 3c 70 e1 67 cb df b2 1b ab 2b ed dc b6 |_....b...O| +000000a0 e8 17 03 03 02 22 3f 87 53 63 dc 59 f7 32 60 4b |....."?.Sc.Y.2`K| +000000b0 bd 9f e1 fc 4c 9a 98 18 94 e1 c1 07 ab 11 33 dc |....L.........3.| +000000c0 f1 48 67 e6 66 83 3c 88 53 c7 dc af e2 87 bc 0b |.Hg.f.<.S.......| +000000d0 d7 60 99 83 29 a1 1c 30 09 ba 4a e1 a9 c2 0e 34 |.`..)..0..J....4| +000000e0 cb a6 f2 8b 1b a0 b0 e6 21 27 3d b8 b4 90 0c 61 |........!'=....a| +000000f0 af 38 db fe fe 9c 34 09 1e 1a c8 f2 e9 05 68 ee |.8....4.......h.| +00000100 9c ec 74 b8 10 25 29 3d 52 71 87 c6 88 22 5a e9 |..t..%)=Rq..."Z.| +00000110 33 d0 d3 75 a8 94 b2 6d 48 4f 63 d1 32 f2 a3 70 |3..u...mHOc.2..p| +00000120 f1 a5 0b 4c 5d 7c 91 9b 04 d4 c3 9e 37 dd 67 a1 |...L]|......7.g.| +00000130 aa 23 6f 2b d0 42 b9 30 5c ed ae 12 36 f1 7c b3 |.#o+.B.0\...6.|.| +00000140 92 de 02 3a 99 c3 98 91 a3 09 43 ef 24 8d 67 e7 |...:......C.$.g.| +00000150 0d 68 22 e1 cc 99 8e 8e 64 09 be 50 f7 4a 37 0a |.h".....d..P.J7.| +00000160 02 af 88 db 8b a0 68 0d 7e 97 d9 9c 48 c3 bd aa |......h.~...H...| +00000170 db 01 69 2b 2b e6 f5 4b 66 c0 7a 8c fe 4d 8f 7b |..i++..Kf.z..M.{| +00000180 94 be 58 b5 44 67 df 26 3f 79 ee 55 bf bf aa 52 |..X.Dg.&?y.U...R| +00000190 95 ec 6b 7f 2b 68 f0 5a 81 4e 13 25 91 bd 9a df |..k.+h.Z.N.%....| +000001a0 dd 2c ae 6d c3 47 27 c2 3f 51 98 a3 b7 06 ec 2f |.,.m.G'.?Q...../| +000001b0 d6 c0 7f 1f e5 5e 3c 50 d3 6e 82 33 be 07 48 0b |.....^..}.....HUB..| +00000260 d0 b2 8e 4d c6 26 bb 77 9e 3f e0 0a 90 a4 3b eb |...M.&.w.?....;.| +00000270 37 94 c4 e8 39 12 82 24 b3 8b 6d 0d ed 9c 31 f0 |7...9..$..m...1.| +00000280 d0 5a cb b0 79 9b d2 ed ab 08 8b 9d ad 25 7a ce |.Z..y........%z.| +00000290 d7 6d c8 11 0a 60 f4 81 e9 e3 e3 42 7b 3d 95 67 |.m...`.....B{=.g| +000002a0 c2 4e 3e 80 11 2e 09 53 94 03 c0 88 cb 23 7e d2 |.N>....S.....#~.| +000002b0 ad e2 dc e7 e2 0b ba 74 9c 04 ad 75 e6 7f 5a fb |.......t...u..Z.| +000002c0 53 5a 98 14 18 4f 1d 2b 17 03 03 00 a4 7a ce c7 |SZ...O.+.....z..| +000002d0 9c de bc 27 04 f7 8b 4b a1 73 7d 0d fa b5 a1 e2 |...'...K.s}.....| +000002e0 fe 8b 33 8d 48 64 65 13 68 e2 5d e2 d7 3e 67 f2 |..3.Hde.h.]..>g.| +000002f0 db bd ff f9 e5 3e 4c b1 56 e3 22 95 88 23 48 fe |.....>L.V."..#H.| +00000300 0f 80 4c 5c 1c 74 0e 26 d4 7c 17 83 65 d6 a3 51 |..L\.t.&.|..e..Q| +00000310 5a 01 a5 12 9c db 0b c9 0b 8b 53 c7 03 75 b9 04 |Z.........S..u..| +00000320 a0 62 df 11 75 ae ff 33 7b 98 6b 7b 35 3e 41 4c |.b..u..3{.k{5>AL| +00000330 9b 16 12 b4 39 ce 9a d5 e9 83 78 b3 4b 3e d6 82 |....9.....x.K>..| +00000340 75 66 bf 73 e4 26 e6 22 8e 2f fe 4d 49 e4 b5 03 |uf.s.&."./.MI...| +00000350 04 a6 65 59 c2 aa e2 e6 ec f0 e2 99 b5 c4 55 75 |..eY..........Uu| +00000360 e1 90 a4 73 cb 21 78 df 4e 96 e2 99 75 15 77 59 |...s.!x.N...u.wY| +00000370 db 17 03 03 00 35 bc 1c 15 d7 b0 62 21 d4 dd 09 |.....5.....b!...| +00000380 1d aa 05 3c e3 ea 0a 9d 89 1f aa 2f f7 75 93 86 |...<......./.u..| +00000390 35 ee 5f 06 20 99 17 ca 4c 05 65 07 f7 56 9f 62 |5._. ...L.e..V.b| +000003a0 2a ea e2 05 f0 be fe bf d6 09 46 17 03 03 00 8b |*.........F.....| +000003b0 37 1a 6c e5 ea 27 e7 b2 d7 87 9a 1a a1 41 b5 64 |7.l..'.......A.d| +000003c0 61 8b bb 1c 64 a2 37 de 39 b3 5b f4 5b 9f bf d8 |a...d.7.9.[.[...| +000003d0 e7 3d be ad 96 6c 69 19 ce 8e a8 12 14 5d 1e 79 |.=...li......].y| +000003e0 c5 12 53 c3 13 81 5a 22 44 e5 6e c4 97 cc 18 19 |..S...Z"D.n.....| +000003f0 c4 04 08 cf 16 dd df 3d 4f 13 40 5f 33 38 f5 0f |.......=O.@_38..| +00000400 4f bb 41 e2 85 85 43 de d0 b5 7a 61 d8 3a 53 41 |O.A...C...za.:SA| +00000410 d2 ad 7b e4 bf 02 d2 14 7d f7 0c 05 b8 bb 21 90 |..{.....}.....!.| +00000420 a5 61 76 7e 07 5d bf e2 a1 f8 1a a6 77 42 2a 7c |.av~.]......wB*|| +00000430 7a 41 a7 5e 04 c2 49 02 45 a8 f5 |zA.^..I.E..| >>> Flow 3 (client to server) -00000000 14 03 03 00 01 01 17 03 03 00 35 46 8b fa 42 0d |..........5F..B.| -00000010 fa 3e 9e 80 76 12 ce 73 ae 85 67 ee af 1e 25 6e |.>..v..s..g...%n| -00000020 0b 46 4c bd 5a 46 8e 5c 27 7a 0a 8d d3 9c 3c 29 |.FL.ZF.\'z....<)| -00000030 4c c8 08 78 ac 9f f4 7a 38 8d 49 6a 01 b6 f5 83 |L..x...z8.Ij....| +00000000 14 03 03 00 01 01 17 03 03 00 35 3d f2 27 fe 81 |..........5=.'..| +00000010 4c 6e 61 1f 34 f8 3d 25 1f 33 d6 22 aa 7f ab 08 |Lna.4.=%.3."....| +00000020 2c 48 44 39 74 2c e9 be 78 7f c7 db 27 b0 b0 6d |,HD9t,..x...'..m| +00000030 b2 8b 87 6c e5 5c 38 79 9f ed 3d 4f 92 81 dc ea |...l.\8y..=O....| >>> Flow 4 (server to client) -00000000 17 03 03 00 1e c2 1a dc 0a cb 9a 11 f7 a1 c2 1f |................| -00000010 54 7d 32 6f 0e 13 b6 6b 9f e1 c6 14 63 fc 18 b9 |T}2o...k....c...| -00000020 81 53 44 17 03 03 00 13 c9 72 ae 5e 2b c1 6f 64 |.SD......r.^+.od| -00000030 e0 70 47 15 b1 ec c3 25 00 7f 4e |.pG....%..N| +00000000 17 03 03 00 1e f7 99 f9 d8 a2 00 d9 e3 48 d9 b2 |.............H..| +00000010 35 37 93 6f b0 1f d5 81 b1 16 b1 e4 d8 b4 40 ce |57.o..........@.| +00000020 97 9f 16 17 03 03 00 13 a4 cb 62 61 70 e9 67 c3 |..........bap.g.| +00000030 21 02 19 bc 01 01 5d 9b 15 d4 84 |!.....]....| diff --git a/crypto/tls/tls.go b/crypto/tls/tls.go index 357378f4436..c11d5be6558 100644 --- a/crypto/tls/tls.go +++ b/crypto/tls/tls.go @@ -4,6 +4,15 @@ // Package tls partially implements TLS 1.2, as specified in RFC 5246, // and TLS 1.3, as specified in RFC 8446. +// +// # FIPS 140-3 mode +// +// When the program is in [FIPS 140-3 mode], this package behaves as if +// only protocol versions, cipher suites, signature algorithms, and +// key exchange algorithms approved by NIST SP 800-52r2 are implemented. +// Others are silently ignored and not negotiated. +// +// [FIPS 140-3 mode]: https://go.dev/doc/security/fips140 package tls // BUG(agl): The crypto/tls package only implements some countermeasures diff --git a/crypto/tls/tls_test.go b/crypto/tls/tls_test.go index ade7f1bf106..5520d36b8e8 100644 --- a/crypto/tls/tls_test.go +++ b/crypto/tls/tls_test.go @@ -7,11 +7,12 @@ package tls import ( "bytes" "context" - "crypto/rand" + "crypto" "encoding/json" "encoding/pem" "errors" "fmt" + "internal/testenv" "io" "math" "math/big" @@ -23,14 +24,20 @@ import ( "testing" "time" - "github.com/runZeroInc/excrypto/crypto" + "github.com/runZeroInc/excrypto/encoding/asn1" + + "crypto/rand" + + "github.com/runZeroInc/excrypto/crypto/ecdh" "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/elliptic" + "github.com/runZeroInc/excrypto/crypto/internal/hpke" + "github.com/runZeroInc/excrypto/crypto/tls/internal/fips140tls" "github.com/runZeroInc/excrypto/crypto/x509" "github.com/runZeroInc/excrypto/crypto/x509/pkix" - "github.com/runZeroInc/excrypto/encoding/asn1" "github.com/runZeroInc/excrypto/internal/godebug" - "github.com/runZeroInc/excrypto/internal/testenv" + + "golang.org/x/crypto/cryptobyte" ) var rsaCertPEM = `-----BEGIN CERTIFICATE----- @@ -176,6 +183,40 @@ func newLocalListener(t testing.TB) net.Listener { return ln } +func runWithFIPSEnabled(t *testing.T, testFunc func(t *testing.T)) { + originalFIPS := fips140tls.Required() + defer func() { + if originalFIPS { + fips140tls.Force() + } else { + fips140tls.TestingOnlyAbandon() + } + }() + + fips140tls.Force() + t.Run("fips140tls", testFunc) +} + +func runWithFIPSDisabled(t *testing.T, testFunc func(t *testing.T)) { + originalFIPS := fips140tls.Required() + defer func() { + if originalFIPS { + fips140tls.Force() + } else { + fips140tls.TestingOnlyAbandon() + } + }() + + fips140tls.TestingOnlyAbandon() + t.Run("no-fips140tls", testFunc) +} + +func skipFIPS(t *testing.T) { + if fips140tls.Required() { + t.Skip("skipping test in FIPS mode") + } +} + func TestDialTimeout(t *testing.T) { if testing.Short() { t.Skip("skipping in short mode") @@ -882,6 +923,10 @@ func TestCloneNonFuncFields(t *testing.T) { f.Set(reflect.ValueOf(RenegotiateOnceAsClient)) case "EncryptedClientHelloConfigList": f.Set(reflect.ValueOf([]byte{'x'})) + case "EncryptedClientHelloKeys": + f.Set(reflect.ValueOf([]EncryptedClientHelloKey{ + {Config: []byte{1}, PrivateKey: []byte{1}}, + })) case "mutex", "autoSessionTicketKeys", "sessionTicketKeys": continue // these are unexported fields that are handled separately case "ExplicitCurvePreferences", "SupportedPoints", "NoOcspStapling", "CompressionMethods", @@ -1113,6 +1158,8 @@ func TestConnectionStateMarshal(t *testing.T) { } func TestConnectionState(t *testing.T) { + skipFIPS(t) // Test certificates not FIPS compatible. + issuer, err := x509.ParseCertificate(testRSACertificateIssuer) if err != nil { panic(err) @@ -1120,8 +1167,6 @@ func TestConnectionState(t *testing.T) { rootCAs := x509.NewCertPool() rootCAs.AddCert(issuer) - now := func() time.Time { return time.Unix(1476984729, 0) } - const alpnProtocol = "golang" const serverName = "example.golang" var scts = [][]byte{[]byte("dummy sct 1"), []byte("dummy sct 2")} @@ -1137,7 +1182,7 @@ func TestConnectionState(t *testing.T) { } t.Run(name, func(t *testing.T) { config := &Config{ - Time: now, + Time: testTime, Rand: zeroSource{}, Certificates: make([]Certificate, 1), MaxVersion: v, @@ -1253,6 +1298,8 @@ func TestBuildNameToCertificate_doesntModifyCertificates(t *testing.T) { func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") } func TestClientHelloInfo_SupportsCertificate(t *testing.T) { + skipFIPS(t) // Test certificates not FIPS compatible. + rsaCert := &Certificate{ Certificate: [][]byte{testRSACertificate}, PrivateKey: testRSAPrivateKey, @@ -1702,6 +1749,8 @@ func TestPKCS1OnlyCert(t *testing.T) { } func TestVerifyCertificates(t *testing.T) { + skipFIPS(t) // Test certificates not FIPS compatible. + // See https://go.dev/issue/31641. t.Run("TLSv12", func(t *testing.T) { testVerifyCertificates(t, VersionTLS12) }) t.Run("TLSv13", func(t *testing.T) { testVerifyCertificates(t, VersionTLS13) }) @@ -1768,7 +1817,7 @@ func testVerifyCertificates(t *testing.T, version uint16) { var serverVerifyPeerCertificates, clientVerifyPeerCertificates bool clientConfig := testConfig.Clone() - clientConfig.Time = func() time.Time { return time.Unix(1476984729, 0) } + clientConfig.Time = testTime clientConfig.MaxVersion = version clientConfig.MinVersion = version clientConfig.RootCAs = rootCAs @@ -1845,25 +1894,21 @@ func testVerifyCertificates(t *testing.T, version uint16) { } } -func TestHandshakeKyber(t *testing.T) { - - if x25519Kyber768Draft00.String() != "X25519Kyber768Draft00" { - t.Fatalf("unexpected CurveID string: %v", x25519Kyber768Draft00.String()) - } - +func TestHandshakeMLKEM(t *testing.T) { + skipFIPS(t) // No X25519MLKEM768 in FIPS var tests = []struct { name string clientConfig func(*Config) serverConfig func(*Config) preparation func(*testing.T) expectClientSupport bool - expectKyber bool + expectMLKEM bool expectHRR bool }{ { name: "Default", expectClientSupport: true, - expectKyber: true, + expectMLKEM: true, expectHRR: false, }, { @@ -1879,7 +1924,7 @@ func TestHandshakeKyber(t *testing.T) { config.CurvePreferences = []CurveID{X25519} }, expectClientSupport: true, - expectKyber: false, + expectMLKEM: false, expectHRR: false, }, { @@ -1888,9 +1933,25 @@ func TestHandshakeKyber(t *testing.T) { config.CurvePreferences = []CurveID{CurveP256} }, expectClientSupport: true, - expectKyber: false, + expectMLKEM: false, expectHRR: true, }, + { + name: "ClientMLKEMOnly", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{X25519MLKEM768} + }, + expectClientSupport: true, + expectMLKEM: true, + }, + { + name: "ClientSortedCurvePreferences", + clientConfig: func(config *Config) { + config.CurvePreferences = []CurveID{CurveP256, X25519MLKEM768} + }, + expectClientSupport: true, + expectMLKEM: true, + }, { name: "ClientTLSv12", clientConfig: func(config *Config) { @@ -1904,13 +1965,12 @@ func TestHandshakeKyber(t *testing.T) { config.MaxVersion = VersionTLS12 }, expectClientSupport: true, - expectKyber: false, + expectMLKEM: false, }, { name: "GODEBUG", preparation: func(t *testing.T) { - godebug.SetEnv("GODEBUG", "tlskyber=0") - t.Cleanup(func() { godebug.ResetEnv() }) + t.Setenv("GODEBUG", "tlsmlkem=0") }, expectClientSupport: false, }, @@ -1930,10 +1990,10 @@ func TestHandshakeKyber(t *testing.T) { test.serverConfig(serverConfig) } serverConfig.GetConfigForClient = func(hello *ClientHelloInfo) (*Config, error) { - if !test.expectClientSupport && slices.Contains(hello.SupportedCurves, x25519Kyber768Draft00) { - return nil, errors.New("client supports Kyber768Draft00") - } else if test.expectClientSupport && !slices.Contains(hello.SupportedCurves, x25519Kyber768Draft00) { - return nil, fmt.Errorf("client does not support Kyber768Draft00: %v", hello.SupportedCurves) + if !test.expectClientSupport && slices.Contains(hello.SupportedCurves, X25519MLKEM768) { + return nil, errors.New("client supports X25519MLKEM768") + } else if test.expectClientSupport && !slices.Contains(hello.SupportedCurves, X25519MLKEM768) { + return nil, errors.New("client does not support X25519MLKEM768") } return nil, nil } @@ -1945,19 +2005,19 @@ func TestHandshakeKyber(t *testing.T) { if err != nil { t.Fatal(err) } - if test.expectKyber { - if ss.testingOnlyCurveID != x25519Kyber768Draft00 { - t.Errorf("got CurveID %v (server), expected %v", ss.testingOnlyCurveID, x25519Kyber768Draft00) + if test.expectMLKEM { + if ss.testingOnlyCurveID != X25519MLKEM768 { + t.Errorf("got CurveID %v (server), expected %v", ss.testingOnlyCurveID, X25519MLKEM768) } - if cs.testingOnlyCurveID != x25519Kyber768Draft00 { - t.Errorf("got CurveID %v (client), expected %v", cs.testingOnlyCurveID, x25519Kyber768Draft00) + if cs.testingOnlyCurveID != X25519MLKEM768 { + t.Errorf("got CurveID %v (client), expected %v", cs.testingOnlyCurveID, X25519MLKEM768) } } else { - if ss.testingOnlyCurveID == x25519Kyber768Draft00 { - t.Errorf("got CurveID %v (server), expected not Kyber", ss.testingOnlyCurveID) + if ss.testingOnlyCurveID == X25519MLKEM768 { + t.Errorf("got CurveID %v (server), expected not X25519MLKEM768", ss.testingOnlyCurveID) } - if cs.testingOnlyCurveID == x25519Kyber768Draft00 { - t.Errorf("got CurveID %v (client), expected not Kyber", cs.testingOnlyCurveID) + if cs.testingOnlyCurveID == X25519MLKEM768 { + t.Errorf("got CurveID %v (client), expected not X25519MLKEM768", cs.testingOnlyCurveID) } } if test.expectHRR { @@ -2083,6 +2143,120 @@ func TestLargeCertMsg(t *testing.T) { }, } if _, _, err := testHandshake(t, clientConfig, serverConfig); err != nil { - t.Fatalf("unexpected failure :%s", err) + t.Fatalf("unexpected failure: %s", err) + } +} + +func TestECH(t *testing.T) { + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + DNSNames: []string{"public.example"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(time.Hour), + } + publicCertDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k) + if err != nil { + t.Fatal(err) + } + publicCert, err := x509.ParseCertificate(publicCertDER) + if err != nil { + t.Fatal(err) + } + tmpl.DNSNames[0] = "secret.example" + secretCertDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, k.Public(), k) + if err != nil { + t.Fatal(err) + } + secretCert, err := x509.ParseCertificate(secretCertDER) + if err != nil { + t.Fatal(err) + } + + marshalECHConfig := func(id uint8, pubKey []byte, publicName string, maxNameLen uint8) []byte { + builder := cryptobyte.NewBuilder(nil) + builder.AddUint16(extensionEncryptedClientHello) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddUint8(id) + builder.AddUint16(hpke.DHKEM_X25519_HKDF_SHA256) // The only DHKEM we support + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes(pubKey) + }) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + for _, aeadID := range sortedSupportedAEADs { + builder.AddUint16(hpke.KDF_HKDF_SHA256) // The only KDF we support + builder.AddUint16(aeadID) + } + }) + builder.AddUint8(maxNameLen) + builder.AddUint8LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes([]byte(publicName)) + }) + builder.AddUint16(0) // extensions + }) + + return builder.BytesOrPanic() + } + + echKey, err := ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + echConfig := marshalECHConfig(123, echKey.PublicKey().Bytes(), "public.example", 32) + + builder := cryptobyte.NewBuilder(nil) + builder.AddUint16LengthPrefixed(func(builder *cryptobyte.Builder) { + builder.AddBytes(echConfig) + }) + echConfigList := builder.BytesOrPanic() + + clientConfig, serverConfig := testConfig.Clone(), testConfig.Clone() + clientConfig.InsecureSkipVerify = false + clientConfig.Rand = rand.Reader + clientConfig.Time = nil + clientConfig.MinVersion = VersionTLS13 + clientConfig.ServerName = "secret.example" + clientConfig.RootCAs = x509.NewCertPool() + clientConfig.RootCAs.AddCert(secretCert) + clientConfig.RootCAs.AddCert(publicCert) + clientConfig.EncryptedClientHelloConfigList = echConfigList + serverConfig.InsecureSkipVerify = false + serverConfig.Rand = rand.Reader + serverConfig.Time = nil + serverConfig.MinVersion = VersionTLS13 + serverConfig.ServerName = "public.example" + serverConfig.Certificates = []Certificate{ + {Certificate: [][]byte{publicCertDER}, PrivateKey: k}, + {Certificate: [][]byte{secretCertDER}, PrivateKey: k}, + } + serverConfig.EncryptedClientHelloKeys = []EncryptedClientHelloKey{ + {Config: echConfig, PrivateKey: echKey.Bytes(), SendAsRetry: true}, + } + + ss, cs, err := testHandshake(t, clientConfig, serverConfig) + if err != nil { + t.Fatalf("unexpected failure: %s", err) + } + if !ss.ECHAccepted { + t.Fatal("server ConnectionState shows ECH not accepted") + } + if !cs.ECHAccepted { + t.Fatal("client ConnectionState shows ECH not accepted") + } + if cs.ServerName != "secret.example" || ss.ServerName != "secret.example" { + t.Fatalf("unexpected ConnectionState.ServerName, want %q, got server:%q, client: %q", "secret.example", ss.ServerName, cs.ServerName) + } + if len(cs.VerifiedChains) != 1 { + t.Fatal("unexpect number of certificate chains") + } + if len(cs.VerifiedChains[0]) != 1 { + t.Fatal("unexpect number of certificates") + } + if !cs.VerifiedChains[0][0].Equal(secretCert) { + t.Fatal("unexpected certificate") } } diff --git a/crypto/x509/boring.go b/crypto/x509/boring.go deleted file mode 100644 index 8cde0c97d30..00000000000 --- a/crypto/x509/boring.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build boringcryptoForced - -package x509 - -import ( - "github.com/runZeroInc/excrypto/crypto/ecdsa" - "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/crypto/rsa" -) - -// boringAllowCert reports whether c is allowed to be used -// in a certificate chain by the current fipstls enforcement setting. -// It is called for each leaf, intermediate, and root certificate. -func boringAllowCert(c *Certificate) bool { - /* - if !fipstls.Required() { - return true - } - */ - return true - - // The key must be RSA 2048, RSA 3072, RSA 4096, - // or ECDSA P-256, P-384, P-521. - switch k := c.PublicKey.(type) { - default: - return false - case *rsa.PublicKey: - if size := k.N.BitLen(); size != 2048 && size != 3072 && size != 4096 { - return false - } - case *ecdsa.PublicKey: - if k.Curve != elliptic.P256() && k.Curve != elliptic.P384() && k.Curve != elliptic.P521() { - return false - } - } - return true -} diff --git a/crypto/x509/boring_test.go b/crypto/x509/boring_test.go deleted file mode 100644 index 3d6d2bb0659..00000000000 --- a/crypto/x509/boring_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build boringcryptoForced - -package x509 - -import ( - "fmt" - "math/big" - "strings" - "testing" - "time" - - "crypto/rand" - - "github.com/runZeroInc/excrypto/crypto/ecdsa" - "github.com/runZeroInc/excrypto/crypto/elliptic" - "github.com/runZeroInc/excrypto/crypto/rsa" - "github.com/runZeroInc/excrypto/crypto/x509/pkix" -) - -const ( - boringCertCA = iota - boringCertLeaf - boringCertFIPSOK = 0x80 -) - -func boringRSAKey(t *testing.T, size int) *rsa.PrivateKey { - t.Helper() - k, err := rsa.GenerateKey(rand.Reader, size) - if err != nil { - t.Fatal(err) - } - return k -} - -func boringECDSAKey(t *testing.T, curve elliptic.Curve) *ecdsa.PrivateKey { - t.Helper() - k, err := ecdsa.GenerateKey(curve, rand.Reader) - if err != nil { - t.Fatal(err) - } - return k -} - -type boringCertificate struct { - name string - org string - parentOrg string - der []byte - cert *Certificate - key interface{} - fipsOK bool -} - -func TestBoringAllowCert(t *testing.T) { - R1 := testBoringCert(t, "R1", boringRSAKey(t, 2048), nil, boringCertCA|boringCertFIPSOK) - R2 := testBoringCert(t, "R2", boringRSAKey(t, 512), nil, boringCertCA) - R3 := testBoringCert(t, "R3", boringRSAKey(t, 4096), nil, boringCertCA|boringCertFIPSOK) - - M1_R1 := testBoringCert(t, "M1_R1", boringECDSAKey(t, elliptic.P256()), R1, boringCertCA|boringCertFIPSOK) - M2_R1 := testBoringCert(t, "M2_R1", boringECDSAKey(t, elliptic.P224()), R1, boringCertCA) - - I_R1 := testBoringCert(t, "I_R1", boringRSAKey(t, 3072), R1, boringCertCA|boringCertFIPSOK) - testBoringCert(t, "I_R2", I_R1.key, R2, boringCertCA|boringCertFIPSOK) - testBoringCert(t, "I_M1", I_R1.key, M1_R1, boringCertCA|boringCertFIPSOK) - testBoringCert(t, "I_M2", I_R1.key, M2_R1, boringCertCA|boringCertFIPSOK) - - I_R3 := testBoringCert(t, "I_R3", boringRSAKey(t, 3072), R3, boringCertCA|boringCertFIPSOK) - testBoringCert(t, "I_R3", I_R3.key, R3, boringCertCA|boringCertFIPSOK) - - testBoringCert(t, "L1_I", boringECDSAKey(t, elliptic.P384()), I_R1, boringCertLeaf|boringCertFIPSOK) - testBoringCert(t, "L2_I", boringRSAKey(t, 1024), I_R1, boringCertLeaf) -} - -func testBoringCert(t *testing.T, name string, key interface{}, parent *boringCertificate, mode int) *boringCertificate { - org := name - parentOrg := "" - if i := strings.Index(org, "_"); i >= 0 { - org = org[:i] - parentOrg = name[i+1:] - } - tmpl := &Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{org}, - }, - NotBefore: time.Unix(0, 0), - NotAfter: time.Unix(0, 0), - - KeyUsage: KeyUsageKeyEncipherment | KeyUsageDigitalSignature, - ExtKeyUsage: []ExtKeyUsage{ExtKeyUsageServerAuth, ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - } - if mode&^boringCertFIPSOK == boringCertLeaf { - tmpl.DNSNames = []string{"example.com"} - } else { - tmpl.IsCA = true - tmpl.KeyUsage |= KeyUsageCertSign - } - - var pcert *Certificate - var pkey interface{} - if parent != nil { - pcert = parent.cert - pkey = parent.key - } else { - pcert = tmpl - pkey = key - } - - var pub interface{} - var desc string - switch k := key.(type) { - case *rsa.PrivateKey: - pub = &k.PublicKey - desc = fmt.Sprintf("RSA-%d", k.N.BitLen()) - case *ecdsa.PrivateKey: - pub = &k.PublicKey - desc = "ECDSA-" + k.Curve.Params().Name - default: - t.Fatalf("invalid key %T", key) - } - - der, err := CreateCertificate(rand.Reader, tmpl, pcert, pub, pkey) - if err != nil { - t.Fatal(err) - } - cert, err := ParseCertificate(der) - if err != nil { - t.Fatal(err) - } - - /* - // Tell isBoringCertificate to enforce FIPS restrictions for this check. - fipstls.Force() - defer fipstls.Abandon() - */ - - fipsOK := mode&boringCertFIPSOK != 0 - if boringAllowCert(cert) != fipsOK { - t.Errorf("boringAllowCert(cert with %s key) = %v, want %v", desc, !fipsOK, fipsOK) - } - return &boringCertificate{name, org, parentOrg, der, cert, key, fipsOK} -} diff --git a/crypto/x509/cert_pool_test.go b/crypto/x509/cert_pool_test.go index 479e745e7fb..36e2e4a366e 100644 --- a/crypto/x509/cert_pool_test.go +++ b/crypto/x509/cert_pool_test.go @@ -6,8 +6,9 @@ package x509 import ( "bytes" - "crypto/rand" "testing" + + "crypto/rand" ) func TestCertPoolEqual(t *testing.T) { diff --git a/crypto/x509/name_constraints_test.go b/crypto/x509/name_constraints_test.go index 2b542b0ea6b..b6a217f4f3a 100644 --- a/crypto/x509/name_constraints_test.go +++ b/crypto/x509/name_constraints_test.go @@ -6,7 +6,6 @@ package x509 import ( "bytes" - "crypto/rand" "encoding/hex" "encoding/pem" "fmt" @@ -20,6 +19,8 @@ import ( "testing" "time" + "crypto/rand" + "github.com/runZeroInc/excrypto/crypto/ecdsa" "github.com/runZeroInc/excrypto/crypto/elliptic" "github.com/runZeroInc/excrypto/crypto/x509/pkix" @@ -1618,6 +1619,23 @@ var nameConstraintsTests = []nameConstraintsTest{ leaf: leafSpec{sans: []string{"dns:.example.com"}}, expectedError: "cannot parse dnsName \".example.com\"", }, + // #86: URIs with IPv6 addresses with zones and ports are rejected + { + roots: []constraintsSpec{ + { + ok: []string{"uri:example.com"}, + }, + }, + intermediates: [][]constraintsSpec{ + { + {}, + }, + }, + leaf: leafSpec{ + sans: []string{"uri:http://[2006:abcd::1%25.example.com]:16/"}, + }, + expectedError: "URI with IP", + }, } func makeConstraintsCACert(constraints constraintsSpec, name string, key *ecdsa.PrivateKey, parent *Certificate, parentKey *ecdsa.PrivateKey) (*Certificate, error) { diff --git a/crypto/x509/notboring.go b/crypto/x509/notboring.go deleted file mode 100644 index c0989958eb4..00000000000 --- a/crypto/x509/notboring.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2022 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build !boringcryptoForced - -package x509 - -func boringAllowCert(_ *Certificate) bool { return true } diff --git a/crypto/x509/oid_test.go b/crypto/x509/oid_test.go index a1d823eb1e9..2ffa6af0c5a 100644 --- a/crypto/x509/oid_test.go +++ b/crypto/x509/oid_test.go @@ -134,12 +134,12 @@ func TestOIDEqual(t *testing.T) { oid2 OID eq bool }{ - {oid: mustNewOIDFromInts(t, []uint64{1, 2, 3}), oid2: mustNewOIDFromInts(t, []uint64{1, 2, 3}), eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{1, 2, 3}), oid2: mustNewOIDFromInts(t, []uint64{1, 2, 4}), eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 2, 3}), oid2: mustNewOIDFromInts(t, []uint64{1, 2, 3, 4}), eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{2, 33, 22}), oid2: mustNewOIDFromInts(t, []uint64{2, 33, 23}), eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: mustNewOIDFromInts([]uint64{1, 2, 3}), eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: mustNewOIDFromInts([]uint64{1, 2, 4}), eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: mustNewOIDFromInts([]uint64{1, 2, 3, 4}), eq: false}, + {oid: mustNewOIDFromInts([]uint64{2, 33, 22}), oid2: mustNewOIDFromInts([]uint64{2, 33, 23}), eq: false}, {oid: OID{}, oid2: OID{}, eq: true}, - {oid: OID{}, oid2: mustNewOIDFromInts(t, []uint64{2, 33, 23}), eq: false}, + {oid: OID{}, oid2: mustNewOIDFromInts([]uint64{2, 33, 23}), eq: false}, } for _, tt := range cases { @@ -278,32 +278,32 @@ func TestOIDEqualASN1OID(t *testing.T) { oid2 asn1.ObjectIdentifier eq bool }{ - {oid: mustNewOIDFromInts(t, []uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 3}, eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 4}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 3, 4}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 22}), oid2: asn1.ObjectIdentifier{1, 33, 23}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 23}), oid2: asn1.ObjectIdentifier{1, 33, 22}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 127}), oid2: asn1.ObjectIdentifier{1, 33, 127}, eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 127}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 128}, eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 129}), oid2: asn1.ObjectIdentifier{1, 33, 129}, eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 129}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 129}), oid2: asn1.ObjectIdentifier{1, 33, 128}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 255}), oid2: asn1.ObjectIdentifier{1, 33, 255}, eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 256}), oid2: asn1.ObjectIdentifier{1, 33, 256}, eq: true}, - {oid: mustNewOIDFromInts(t, []uint64{2, 33, 257}), oid2: asn1.ObjectIdentifier{2, 33, 256}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{2, 33, 256}), oid2: asn1.ObjectIdentifier{2, 33, 257}, eq: false}, - - {oid: mustNewOIDFromInts(t, []uint64{1, 33}), oid2: asn1.ObjectIdentifier{1, 33, math.MaxInt32}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, math.MaxInt32}), oid2: asn1.ObjectIdentifier{1, 33}, eq: false}, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, math.MaxInt32}), oid2: asn1.ObjectIdentifier{1, 33, math.MaxInt32}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 3}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 4}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 2, 3}), oid2: asn1.ObjectIdentifier{1, 2, 3, 4}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 22}), oid2: asn1.ObjectIdentifier{1, 33, 23}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 23}), oid2: asn1.ObjectIdentifier{1, 33, 22}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 127}), oid2: asn1.ObjectIdentifier{1, 33, 127}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 127}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 128}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 129}), oid2: asn1.ObjectIdentifier{1, 33, 129}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 128}), oid2: asn1.ObjectIdentifier{1, 33, 129}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 129}), oid2: asn1.ObjectIdentifier{1, 33, 128}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 255}), oid2: asn1.ObjectIdentifier{1, 33, 255}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 256}), oid2: asn1.ObjectIdentifier{1, 33, 256}, eq: true}, + {oid: mustNewOIDFromInts([]uint64{2, 33, 257}), oid2: asn1.ObjectIdentifier{2, 33, 256}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{2, 33, 256}), oid2: asn1.ObjectIdentifier{2, 33, 257}, eq: false}, + + {oid: mustNewOIDFromInts([]uint64{1, 33}), oid2: asn1.ObjectIdentifier{1, 33, math.MaxInt32}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, math.MaxInt32}), oid2: asn1.ObjectIdentifier{1, 33}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, math.MaxInt32}), oid2: asn1.ObjectIdentifier{1, 33, math.MaxInt32}, eq: true}, { - oid: mustNewOIDFromInts(t, []uint64{1, 33, math.MaxInt32 + 1}), + oid: mustNewOIDFromInts([]uint64{1, 33, math.MaxInt32 + 1}), oid2: asn1.ObjectIdentifier{1, 33 /*convert to int, so that it compiles on 32bit*/, int(maxInt32PlusOne)}, eq: false, }, - {oid: mustNewOIDFromInts(t, []uint64{1, 33, 256}), oid2: asn1.ObjectIdentifier{}, eq: false}, + {oid: mustNewOIDFromInts([]uint64{1, 33, 256}), oid2: asn1.ObjectIdentifier{}, eq: false}, {oid: OID{}, oid2: asn1.ObjectIdentifier{1, 33, 256}, eq: false}, {oid: OID{}, oid2: asn1.ObjectIdentifier{}, eq: false}, } @@ -332,7 +332,7 @@ func TestOIDUnmarshalBinary(t *testing.T) { } func BenchmarkOIDMarshalUnmarshalText(b *testing.B) { - oid := mustNewOIDFromInts(b, []uint64{1, 2, 3, 9999, 1024}) + oid := mustNewOIDFromInts([]uint64{1, 2, 3, 9999, 1024}) for range b.N { text, err := oid.MarshalText() if err != nil { @@ -344,12 +344,3 @@ func BenchmarkOIDMarshalUnmarshalText(b *testing.B) { } } } - -func mustNewOIDFromInts(t testing.TB, ints []uint64) OID { - t.Helper() - oid, err := OIDFromInts(ints) - if err != nil { - t.Fatalf("OIDFromInts(%v) unexpected error: %v", ints, err) - } - return oid -} diff --git a/crypto/x509/parser.go b/crypto/x509/parser.go index 8e2cbfa2940..7ae5c65fa72 100644 --- a/crypto/x509/parser.go +++ b/crypto/x509/parser.go @@ -450,98 +450,29 @@ func parseExtKeyUsageExtension(der cryptobyte.String) ([]ExtKeyUsage, []asn1.Obj return extKeyUsages, unknownUsages, nil } -func parseCertificatePoliciesExtension(out *Certificate, der cryptobyte.String) error { - var err error - // RFC 5280 4.2.1.4: Certificate Policies - var policies []policyInformation - if _, err = asn1.Unmarshal(der, &policies); err != nil { - return err - } - - out.PolicyIdentifiers = make([]asn1.ObjectIdentifier, len(policies)) - out.QualifierId = make([][]asn1.ObjectIdentifier, len(policies)) - out.ExplicitTexts = make([][]asn1.RawValue, len(policies)) - out.NoticeRefOrgnization = make([][]asn1.RawValue, len(policies)) - out.NoticeRefNumbers = make([][]NoticeNumber, len(policies)) - out.ParsedExplicitTexts = make([][]string, len(policies)) - out.ParsedNoticeRefOrganization = make([][]string, len(policies)) - out.CPSuri = make([][]string, len(policies)) - out.UserNotices = make([][]UserNotice, len(policies)) - - for i, policy := range policies { - - // TODO: Avoid double marshal by adding a conversion function - if oidVal, err := ParseOID(policy.Policy.String()); err == nil { - out.Policies = append(out.Policies, oidVal) +func parseCertificatePoliciesExtension(der cryptobyte.String) ([]OID, error) { + var oids []OID + seenOIDs := map[string]bool{} + if !der.ReadASN1(&der, cryptobyte_asn1.SEQUENCE) { + return nil, errors.New("x509: invalid certificate policies") + } + for !der.Empty() { + var cp cryptobyte.String + var OIDBytes cryptobyte.String + if !der.ReadASN1(&cp, cryptobyte_asn1.SEQUENCE) || !cp.ReadASN1(&OIDBytes, cryptobyte_asn1.OBJECT_IDENTIFIER) { + return nil, errors.New("x509: invalid certificate policies") } - - out.PolicyIdentifiers[i] = policy.Policy - // parse optional Qualifier for zlint - for _, qualifier := range policy.Qualifiers { - out.QualifierId[i] = append(out.QualifierId[i], qualifier.PolicyQualifierId) - userNoticeOID := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 2} - cpsURIOID := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 2, 1} - - if qualifier.PolicyQualifierId.Equal(userNoticeOID) { - var un userNotice - _, err := asn1.Unmarshal(qualifier.Qualifier.FullBytes, &un) - if err != nil && !asn1.AllowPermissiveParsing { - return err - } - if err == nil { - groupUserNotice := UserNotice{} - if len(un.ExplicitText.Bytes) != 0 { - out.ExplicitTexts[i] = append(out.ExplicitTexts[i], un.ExplicitText) - parsed := string(un.ExplicitText.Bytes) - out.ParsedExplicitTexts[i] = append(out.ParsedExplicitTexts[i], parsed) - - groupUserNotice.ExplicitText = &parsed - } - - if un.NoticeRef.Organization.Bytes != nil || un.NoticeRef.NoticeNumbers != nil { - out.NoticeRefOrgnization[i] = append(out.NoticeRefOrgnization[i], un.NoticeRef.Organization) - out.NoticeRefNumbers[i] = append(out.NoticeRefNumbers[i], un.NoticeRef.NoticeNumbers) - out.ParsedNoticeRefOrganization[i] = append(out.ParsedNoticeRefOrganization[i], string(un.NoticeRef.Organization.Bytes)) - - groupUserNotice.NoticeReference = &NoticeReference{ - Organization: string(un.NoticeRef.Organization.Bytes), - NoticeNumbers: un.NoticeRef.NoticeNumbers, - } - } - - out.UserNotices[i] = append(out.UserNotices[i], groupUserNotice) - } - } - if qualifier.PolicyQualifierId.Equal(cpsURIOID) { - var cpsURIRaw asn1.RawValue - _, err = asn1.Unmarshal(qualifier.Qualifier.FullBytes, &cpsURIRaw) - if err != nil && !asn1.AllowPermissiveParsing { - return err - } - if err == nil { - out.CPSuri[i] = append(out.CPSuri[i], string(cpsURIRaw.Bytes)) - } - } + if seenOIDs[string(OIDBytes)] { + return nil, errors.New("x509: invalid certificate policies") } - } - if out.SelfSigned { - out.ValidationLevel = UnknownValidationLevel - } else { - // See http://unmitigatedrisk.com/?p=203 - validationLevel := getMaxCertValidationLevel(out.PolicyIdentifiers) - if validationLevel == UnknownValidationLevel { - if (len(out.Subject.Organization) > 0 && out.Subject.Organization[0] == out.Subject.CommonName) || (len(out.Subject.OrganizationalUnit) > 0 && strings.Contains(out.Subject.OrganizationalUnit[0], "Domain Control Validated")) { - if len(out.Subject.Locality) == 0 && len(out.Subject.Province) == 0 && len(out.Subject.PostalCode) == 0 { - validationLevel = DV - } - } else if len(out.Subject.Organization) > 0 && out.Subject.Organization[0] == "Persona Not Validated" && strings.Contains(out.Issuer.CommonName, "StartCom") { - validationLevel = DV - } + seenOIDs[string(OIDBytes)] = true + oid, ok := newOIDFromDER(OIDBytes) + if !ok { + return nil, errors.New("x509: invalid certificate policies") } - out.ValidationLevel = validationLevel + oids = append(oids, oid) } - - return nil + return oids, nil } // isValidIPMask reports whether mask consists of zero or more 1 bits, followed by zero bits. @@ -922,23 +853,43 @@ func processExtensions(out *Certificate) error { case 35: out.AuthorityKeyId, err = parseAuthorityKeyIdentifier(e) if err != nil { - if !asn1.AllowPermissiveParsing { - return err - } else { - out.PermissiveErrors = append(out.PermissiveErrors, fmt.Errorf("extension %d/%s: %w", extIdx, oidStr, err)) + return err + } + case 36: + val := cryptobyte.String(e.Value) + if !val.ReadASN1(&val, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid policy constraints extension") + } + if val.PeekASN1Tag(cryptobyte_asn1.Tag(0).ContextSpecific()) { + var v int64 + if !val.ReadASN1Int64WithTag(&v, cryptobyte_asn1.Tag(0).ContextSpecific()) { + return errors.New("x509: invalid policy constraints extension") } + out.RequireExplicitPolicy = int(v) + // Check for overflow. + if int64(out.RequireExplicitPolicy) != v { + return errors.New("x509: policy constraints requireExplicitPolicy field overflows int") + } + out.RequireExplicitPolicyZero = out.RequireExplicitPolicy == 0 + } + if val.PeekASN1Tag(cryptobyte_asn1.Tag(1).ContextSpecific()) { + var v int64 + if !val.ReadASN1Int64WithTag(&v, cryptobyte_asn1.Tag(1).ContextSpecific()) { + return errors.New("x509: invalid policy constraints extension") + } + out.InhibitPolicyMapping = int(v) + // Check for overflow. + if int64(out.InhibitPolicyMapping) != v { + return errors.New("x509: policy constraints inhibitPolicyMapping field overflows int") + } + out.InhibitPolicyMappingZero = out.InhibitPolicyMapping == 0 } case 37: out.ExtKeyUsage, out.UnknownExtKeyUsage, err = parseExtKeyUsageExtension(e.Value) if err != nil { - if !asn1.AllowPermissiveParsing { - return err - } else { - out.PermissiveErrors = append(out.PermissiveErrors, fmt.Errorf("extension %d/%s: %w", extIdx, oidStr, err)) - } + return err } - case 14: - // RFC 5280, 4.2.1.2 + case 14: // RFC 5280, 4.2.1.2 if e.Critical { // Conforming CAs MUST mark this extension as non-critical return errors.New("x509: subject key identifier incorrectly marked critical") @@ -958,6 +909,27 @@ func processExtensions(out *Certificate) error { out.PermissiveErrors = append(out.PermissiveErrors, fmt.Errorf("extension %d/%s: %w", extIdx, oidStr, err)) } } + case 33: + val := cryptobyte.String(e.Value) + if !val.ReadASN1(&val, cryptobyte_asn1.SEQUENCE) { + return errors.New("x509: invalid policy mappings extension") + } + for !val.Empty() { + var s cryptobyte.String + var issuer, subject cryptobyte.String + if !val.ReadASN1(&s, cryptobyte_asn1.SEQUENCE) || + !s.ReadASN1(&issuer, cryptobyte_asn1.OBJECT_IDENTIFIER) || + !s.ReadASN1(&subject, cryptobyte_asn1.OBJECT_IDENTIFIER) { + return errors.New("x509: invalid policy mappings extension") + } + out.PolicyMappings = append(out.PolicyMappings, PolicyMapping{OID{issuer}, OID{subject}}) + } + case 54: + val := cryptobyte.String(e.Value) + if !val.ReadASN1Integer(&out.InhibitAnyPolicy) { + return errors.New("x509: invalid inhibit any policy extension") + } + out.InhibitAnyPolicyZero = out.InhibitAnyPolicy == 0 default: // Unknown extensions are recorded if critical. unhandled = true diff --git a/crypto/x509/parser_test.go b/crypto/x509/parser_test.go index 81fdebe34ae..b4d0baecbb2 100644 --- a/crypto/x509/parser_test.go +++ b/crypto/x509/parser_test.go @@ -5,11 +5,13 @@ package x509 import ( + "encoding/pem" + "os" "testing" "github.com/runZeroInc/excrypto/encoding/asn1" - cryptobyte_asn1 "github.com/runZeroInc/excrypto/x/crypto/cryptobyte/asn1" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" ) func TestParseASN1String(t *testing.T) { @@ -102,3 +104,84 @@ func TestParseASN1String(t *testing.T) { }) } } + +const policyPEM = `-----BEGIN CERTIFICATE----- +MIIGeDCCBWCgAwIBAgIUED9KQBi0ScBDoufB2mgAJ63G5uIwDQYJKoZIhvcNAQEL +BQAwVTELMAkGA1UEBhMCVVMxGDAWBgNVBAoTD1UuUy4gR292ZXJubWVudDENMAsG +A1UECxMERlBLSTEdMBsGA1UEAxMURmVkZXJhbCBCcmlkZ2UgQ0EgRzQwHhcNMjAx +MDIyMTcwNDE5WhcNMjMxMDIyMTcwNDE5WjCBgTELMAkGA1UEBhMCVVMxHTAbBgNV +BAoTFFN5bWFudGVjIENvcnBvcmF0aW9uMR8wHQYDVQQLExZTeW1hbnRlYyBUcnVz +dCBOZXR3b3JrMTIwMAYDVQQDEylTeW1hbnRlYyBDbGFzcyAzIFNTUCBJbnRlcm1l +ZGlhdGUgQ0EgLSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2p +75cMpx86sS2aH4r+0o8r+m/KTrPrknWP0RA9Kp6sewAzkNa7BVwg0jOhyamiv1iP +Cns10usoH93nxYbXLWF54vOLRdYU/53KEPNmgkj2ipMaTLuaReBghNibikWSnAmy +S8RItaDMs8tdF2goKPI4xWiamNwqe92VC+pic2tq0Nva3Y4kvMDJjtyje3uduTtL +oyoaaHkrX7i7gE67psnMKj1THUtre1JV1ohl9+oOuyot4p3eSxVlrMWiiwb11bnk +CakecOz/mP2DHMGg6pZ/BeJ+ThaLUylAXECARIqHc9UwRPKC9BfLaCX4edIoeYiB +loRs4KdqLdg/I9eTwKkCAwEAAaOCAxEwggMNMB0GA1UdDgQWBBQ1Jn1QleGhwb0F +1cOdd0LHDBOWjDAfBgNVHSMEGDAWgBR58ABJ6393wl1BAmU0ipAjmx4HbzAOBgNV +HQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zCBiAYDVR0gBIGAMH4wDAYKYIZI +AWUDAgEDAzAMBgpghkgBZQMCAQMMMAwGCmCGSAFlAwIBAw4wDAYKYIZIAWUDAgED +DzAMBgpghkgBZQMCAQMSMAwGCmCGSAFlAwIBAxMwDAYKYIZIAWUDAgEDFDAMBgpg +hkgBZQMCAQMlMAwGCmCGSAFlAwIBAyYwggESBgNVHSEEggEJMIIBBTAbBgpghkgB +ZQMCAQMDBg1ghkgBhvhFAQcXAwEGMBsGCmCGSAFlAwIBAwwGDWCGSAGG+EUBBxcD +AQcwGwYKYIZIAWUDAgEDDgYNYIZIAYb4RQEHFwMBDjAbBgpghkgBZQMCAQMPBg1g +hkgBhvhFAQcXAwEPMBsGCmCGSAFlAwIBAxIGDWCGSAGG+EUBBxcDARIwGwYKYIZI +AWUDAgEDEwYNYIZIAYb4RQEHFwMBETAbBgpghkgBZQMCAQMUBg1ghkgBhvhFAQcX +AwEUMBsGCmCGSAFlAwIBAyUGDWCGSAGG+EUBBxcDAQgwGwYKYIZIAWUDAgEDJgYN +YIZIAYb4RQEHFwMBJDBgBggrBgEFBQcBCwRUMFIwUAYIKwYBBQUHMAWGRGh0dHA6 +Ly9zc3Atc2lhLnN5bWF1dGguY29tL1NUTlNTUC9DZXJ0c19Jc3N1ZWRfYnlfQ2xh +c3MzU1NQQ0EtRzMucDdjMA8GA1UdJAQIMAaAAQCBAQAwCgYDVR02BAMCAQAwUQYI +KwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVodHRwOi8vcmVwby5mcGtpLmdvdi9i +cmlkZ2UvY2FDZXJ0c0lzc3VlZFRvZmJjYWc0LnA3YzA3BgNVHR8EMDAuMCygKqAo +hiZodHRwOi8vcmVwby5mcGtpLmdvdi9icmlkZ2UvZmJjYWc0LmNybDANBgkqhkiG +9w0BAQsFAAOCAQEAA751TycC1f/WTkHmedF9ZWxP58Jstmwvkyo8bKueJ0eF7LTG +BgQlzE2B9vke4sFhd4V+BdgOPGE1dsGzllYKCWg0BhkCBs5kIJ7F6Ay6G1TBuGU1 +Ie8247GL+P9pcC5TVvXHC/62R2w3DuD/vAPLbYEbSQjobXlsqt8Kmtd6yK/jVuDV +BTZMdZmvoNtjemqmgcBXHsf0ctVm0m6tH5uYqyVxu8tfyUis6Cf303PHj+spWP1k +gc5PYnVF0ot7qAmNFENIpbKg3BdusBkF9rGxLaDSUBvSc7+s9iQz9d/iRuAebrYu ++eqUlJ2lsjS1U8qyPmlH+spfPNbAEQEsuP32Aw== +-----END CERTIFICATE----- +` + +func TestPolicyParse(t *testing.T) { + b, _ := pem.Decode([]byte(policyPEM)) + c, err := ParseCertificate(b.Bytes) + if err != nil { + t.Fatal(err) + } + if len(c.Policies) != 9 { + t.Errorf("unexpected number of policies: got %d, want %d", len(c.Policies), 9) + } + if len(c.PolicyMappings) != 9 { + t.Errorf("unexpected number of policy mappings: got %d, want %d", len(c.PolicyMappings), 9) + } + if !c.RequireExplicitPolicyZero { + t.Error("expected RequireExplicitPolicyZero to be set") + } + if !c.InhibitPolicyMappingZero { + t.Error("expected InhibitPolicyMappingZero to be set") + } + if !c.InhibitAnyPolicyZero { + t.Error("expected InhibitAnyPolicyZero to be set") + } +} + +func TestParsePolicies(t *testing.T) { + for _, tc := range []string{ + "testdata/policy_leaf_duplicate.pem", + "testdata/policy_leaf_invalid.pem", + } { + t.Run(tc, func(t *testing.T) { + b, err := os.ReadFile(tc) + if err != nil { + t.Fatal(err) + } + p, _ := pem.Decode(b) + _, err = ParseCertificate(p.Bytes) + if err == nil { + t.Error("parsing should've failed") + } + }) + } +} diff --git a/crypto/x509/pkcs1.go b/crypto/x509/pkcs1.go index 86d99e6f938..f45771608b4 100644 --- a/crypto/x509/pkcs1.go +++ b/crypto/x509/pkcs1.go @@ -6,10 +6,12 @@ package x509 import ( "errors" + "internal/godebug" "math/big" - "github.com/runZeroInc/excrypto/crypto/rsa" "github.com/runZeroInc/excrypto/encoding/asn1" + + "github.com/runZeroInc/excrypto/crypto/rsa" ) // pkcs1PrivateKey is a structure which mirrors the PKCS #1 ASN.1 for an RSA private key. @@ -20,10 +22,9 @@ type pkcs1PrivateKey struct { D *big.Int P *big.Int Q *big.Int - // We ignore these values, if present, because rsa will calculate them. - Dp *big.Int `asn1:"optional"` - Dq *big.Int `asn1:"optional"` - Qinv *big.Int `asn1:"optional"` + Dp *big.Int `asn1:"optional"` + Dq *big.Int `asn1:"optional"` + Qinv *big.Int `asn1:"optional"` AdditionalPrimes []pkcs1AdditionalRSAPrime `asn1:"optional,omitempty"` } @@ -42,9 +43,16 @@ type pkcs1PublicKey struct { E int } +// x509rsacrt, if zero, makes ParsePKCS1PrivateKey ignore and recompute invalid +// CRT values in the RSA private key. +var x509rsacrt = godebug.New("x509rsacrt") + // ParsePKCS1PrivateKey parses an [RSA] private key in PKCS #1, ASN.1 DER form. // // This kind of key is commonly encoded in PEM blocks of type "RSA PRIVATE KEY". +// +// Before Go 1.24, the CRT parameters were ignored and recomputed. To restore +// the old behavior, use the GODEBUG=x509rsacrt=0 environment variable. func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { var priv pkcs1PrivateKey rest, err := asn1.Unmarshal(der, &priv) @@ -65,7 +73,10 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { return nil, errors.New("x509: unsupported private key version") } - if priv.N.Sign() <= 0 || priv.D.Sign() <= 0 || priv.P.Sign() <= 0 || priv.Q.Sign() <= 0 { + if priv.N.Sign() <= 0 || priv.D.Sign() <= 0 || priv.P.Sign() <= 0 || priv.Q.Sign() <= 0 || + priv.Dp != nil && priv.Dp.Sign() <= 0 || + priv.Dq != nil && priv.Dq.Sign() <= 0 || + priv.Qinv != nil && priv.Qinv.Sign() <= 0 { return nil, errors.New("x509: private key contains zero or negative value") } @@ -79,6 +90,9 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { key.Primes = make([]*big.Int, 2+len(priv.AdditionalPrimes)) key.Primes[0] = priv.P key.Primes[1] = priv.Q + key.Precomputed.Dp = priv.Dp + key.Precomputed.Dq = priv.Dq + key.Precomputed.Qinv = priv.Qinv for i, a := range priv.AdditionalPrimes { if a.Prime.Sign() <= 0 { return nil, errors.New("x509: private key contains zero or negative prime") @@ -88,11 +102,23 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { // them as needed. } - err = key.Validate() - if err != nil { + key.Precompute() + if err := key.Validate(); err != nil { + // If x509rsacrt=0 is set, try dropping the CRT values and + // rerunning precomputation and key validation. + if x509rsacrt.Value() == "0" { + key.Precomputed.Dp = nil + key.Precomputed.Dq = nil + key.Precomputed.Qinv = nil + key.Precompute() + if err := key.Validate(); err == nil { + x509rsacrt.IncNonDefault() + return key, nil + } + } + return nil, err } - key.Precompute() return key, nil } @@ -102,6 +128,10 @@ func ParsePKCS1PrivateKey(der []byte) (*rsa.PrivateKey, error) { // This kind of key is commonly encoded in PEM blocks of type "RSA PRIVATE KEY". // For a more flexible key format which is not [RSA] specific, use // [MarshalPKCS8PrivateKey]. +// +// The key must have passed validation by calling [rsa.PrivateKey.Validate] +// first. MarshalPKCS1PrivateKey calls [rsa.PrivateKey.Precompute], which may +// modify the key if not already precomputed. func MarshalPKCS1PrivateKey(key *rsa.PrivateKey) []byte { key.Precompute() diff --git a/crypto/x509/pkcs8.go b/crypto/x509/pkcs8.go index fd88501f7d2..8b40bbb8316 100644 --- a/crypto/x509/pkcs8.go +++ b/crypto/x509/pkcs8.go @@ -33,6 +33,9 @@ type pkcs8 struct { // in the future. // // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". +// +// Before Go 1.24, the CRT parameters of RSA keys were ignored and recomputed. +// To restore the old behavior, use the GODEBUG=x509rsacrt=0 environment variable. func ParsePKCS8PrivateKey(der []byte) (key any, err error) { var privKey pkcs8 if _, err := asn1.Unmarshal(der, &privKey); err != nil { @@ -99,6 +102,8 @@ func ParsePKCS8PrivateKey(der []byte) (key any, err error) { // Unsupported key types result in an error. // // This kind of key is commonly encoded in PEM blocks of type "PRIVATE KEY". +// +// MarshalPKCS8PrivateKey runs [rsa.PrivateKey.Precompute] on RSA keys. func MarshalPKCS8PrivateKey(key any) ([]byte, error) { var privKey pkcs8 @@ -108,6 +113,10 @@ func MarshalPKCS8PrivateKey(key any) ([]byte, error) { Algorithm: oidPublicKeyRSA, Parameters: asn1.NullRawValue, } + k.Precompute() + if err := k.Validate(); err != nil { + return nil, err + } privKey.PrivateKey = MarshalPKCS1PrivateKey(k) case *ecdsa.PrivateKey: diff --git a/crypto/x509/pkits_test.go b/crypto/x509/pkits_test.go new file mode 100644 index 00000000000..b1139bbf9c6 --- /dev/null +++ b/crypto/x509/pkits_test.go @@ -0,0 +1,186 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package x509 + +import ( + "encoding/json" + "os" + "path/filepath" + "slices" + "testing" +) + +var nistTestPolicies = map[string]OID{ + "anyPolicy": anyPolicyOID, + "NIST-test-policy-1": mustNewOIDFromInts([]uint64{2, 16, 840, 1, 101, 3, 2, 1, 48, 1}), + "NIST-test-policy-2": mustNewOIDFromInts([]uint64{2, 16, 840, 1, 101, 3, 2, 1, 48, 2}), + "NIST-test-policy-3": mustNewOIDFromInts([]uint64{2, 16, 840, 1, 101, 3, 2, 1, 48, 3}), + "NIST-test-policy-6": mustNewOIDFromInts([]uint64{2, 16, 840, 1, 101, 3, 2, 1, 48, 6}), +} + +func TestNISTPKITSPolicy(t *testing.T) { + // This test runs a subset of the NIST PKI path validation test suite that + // focuses of policy validation, rather than the entire suite. Since the + // suite assumes you are only validating the path, rather than building + // _and_ validating the path, we take the path as given and run + // policiesValid on it. + + certDir := "testdata/nist-pkits/certs" + + var testcases []struct { + Name string + CertPath []string + InitialPolicySet []string + InitialPolicyMappingInhibit bool + InitialExplicitPolicy bool + InitialAnyPolicyInhibit bool + ShouldValidate bool + Skipped bool + } + b, err := os.ReadFile("testdata/nist-pkits/vectors.json") + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(b, &testcases); err != nil { + t.Fatal(err) + } + + policyTests := map[string]bool{ + "4.8.1 All Certificates Same Policy Test1 (Subpart 1)": true, + "4.8.1 All Certificates Same Policy Test1 (Subpart 2)": true, + "4.8.1 All Certificates Same Policy Test1 (Subpart 3)": true, + "4.8.1 All Certificates Same Policy Test1 (Subpart 4)": true, + "4.8.2 All Certificates No Policies Test2 (Subpart 1)": true, + "4.8.2 All Certificates No Policies Test2 (Subpart 2)": true, + "4.8.3 Different Policies Test3 (Subpart 1)": true, + "4.8.3 Different Policies Test3 (Subpart 2)": true, + "4.8.3 Different Policies Test3 (Subpart 3)": true, + "4.8.4 Different Policies Test4": true, + "4.8.5 Different Policies Test5": true, + "4.8.6 Overlapping Policies Test6 (Subpart 1)": true, + "4.8.6 Overlapping Policies Test6 (Subpart 2)": true, + "4.8.6 Overlapping Policies Test6 (Subpart 3)": true, + "4.8.7 Different Policies Test7": true, + "4.8.8 Different Policies Test8": true, + "4.8.9 Different Policies Test9": true, + "4.8.10 All Certificates Same Policies Test10 (Subpart 1)": true, + "4.8.10 All Certificates Same Policies Test10 (Subpart 2)": true, + "4.8.10 All Certificates Same Policies Test10 (Subpart 3)": true, + "4.8.11 All Certificates AnyPolicy Test11 (Subpart 1)": true, + "4.8.11 All Certificates AnyPolicy Test11 (Subpart 2)": true, + "4.8.12 Different Policies Test12": true, + "4.8.13 All Certificates Same Policies Test13 (Subpart 1)": true, + "4.8.13 All Certificates Same Policies Test13 (Subpart 2)": true, + "4.8.13 All Certificates Same Policies Test13 (Subpart 3)": true, + "4.8.14 AnyPolicy Test14 (Subpart 1)": true, + "4.8.14 AnyPolicy Test14 (Subpart 2)": true, + "4.8.15 User Notice Qualifier Test15": true, + "4.8.16 User Notice Qualifier Test16": true, + "4.8.17 User Notice Qualifier Test17": true, + "4.8.18 User Notice Qualifier Test18 (Subpart 1)": true, + "4.8.18 User Notice Qualifier Test18 (Subpart 2)": true, + "4.8.19 User Notice Qualifier Test19": true, + "4.8.20 CPS Pointer Qualifier Test20": true, + "4.9.1 Valid RequireExplicitPolicy Test1": true, + "4.9.2 Valid RequireExplicitPolicy Test2": true, + "4.9.3 Invalid RequireExplicitPolicy Test3": true, + "4.9.4 Valid RequireExplicitPolicy Test4": true, + "4.9.5 Invalid RequireExplicitPolicy Test5": true, + "4.9.6 Valid Self-Issued requireExplicitPolicy Test6": true, + "4.9.7 Invalid Self-Issued requireExplicitPolicy Test7": true, + "4.9.8 Invalid Self-Issued requireExplicitPolicy Test8": true, + "4.10.1.1 Valid Policy Mapping Test1 (Subpart 1)": true, + "4.10.1.2 Valid Policy Mapping Test1 (Subpart 2)": true, + "4.10.1.3 Valid Policy Mapping Test1 (Subpart 3)": true, + "4.10.2 Invalid Policy Mapping Test2 (Subpart 1)": true, + "4.10.2 Invalid Policy Mapping Test2 (Subpart 2)": true, + "4.10.3 Valid Policy Mapping Test3 (Subpart 1)": true, + "4.10.3 Valid Policy Mapping Test3 (Subpart 2)": true, + "4.10.4 Invalid Policy Mapping Test4": true, + "4.10.5 Valid Policy Mapping Test5 (Subpart 1)": true, + "4.10.5 Valid Policy Mapping Test5 (Subpart 2)": true, + "4.10.6 Valid Policy Mapping Test6 (Subpart 1)": true, + "4.10.6 Valid Policy Mapping Test6 (Subpart 2)": true, + "4.10.7 Invalid Mapping From anyPolicy Test7": true, + "4.10.8 Invalid Mapping To anyPolicy Test8": true, + "4.10.9 Valid Policy Mapping Test9": true, + "4.10.10 Invalid Policy Mapping Test10": true, + "4.10.11 Valid Policy Mapping Test11": true, + "4.10.12 Valid Policy Mapping Test12 (Subpart 1)": true, + "4.10.12 Valid Policy Mapping Test12 (Subpart 2)": true, + "4.10.13 Valid Policy Mapping Test13 (Subpart 1)": true, + "4.10.13 Valid Policy Mapping Test13 (Subpart 2)": true, + "4.10.13 Valid Policy Mapping Test13 (Subpart 3)": true, + "4.10.14 Valid Policy Mapping Test14": true, + "4.11.1 Invalid inhibitPolicyMapping Test1": true, + "4.11.2 Valid inhibitPolicyMapping Test2": true, + "4.11.3 Invalid inhibitPolicyMapping Test3": true, + "4.11.4 Valid inhibitPolicyMapping Test4": true, + "4.11.5 Invalid inhibitPolicyMapping Test5": true, + "4.11.6 Invalid inhibitPolicyMapping Test6": true, + "4.11.7 Valid Self-Issued inhibitPolicyMapping Test7": true, + "4.11.8 Invalid Self-Issued inhibitPolicyMapping Test8": true, + "4.11.9 Invalid Self-Issued inhibitPolicyMapping Test9": true, + "4.11.10 Invalid Self-Issued inhibitPolicyMapping Test10": true, + "4.11.11 Invalid Self-Issued inhibitPolicyMapping Test11": true, + "4.12.1 Invalid inhibitAnyPolicy Test1": true, + "4.12.2 Valid inhibitAnyPolicy Test2": true, + "4.12.3 inhibitAnyPolicy Test3 (Subpart 1)": true, + "4.12.3 inhibitAnyPolicy Test3 (Subpart 2)": true, + "4.12.4 Invalid inhibitAnyPolicy Test4": true, + "4.12.5 Invalid inhibitAnyPolicy Test5": true, + "4.12.6 Invalid inhibitAnyPolicy Test6": true, + "4.12.7 Valid Self-Issued inhibitAnyPolicy Test7": true, + "4.12.8 Invalid Self-Issued inhibitAnyPolicy Test8": true, + "4.12.9 Valid Self-Issued inhibitAnyPolicy Test9": true, + "4.12.10 Invalid Self-Issued inhibitAnyPolicy Test10": true, + } + + for _, tc := range testcases { + if !policyTests[tc.Name] { + continue + } + t.Run(tc.Name, func(t *testing.T) { + var chain []*Certificate + for _, c := range tc.CertPath { + certDER, err := os.ReadFile(filepath.Join(certDir, c)) + if err != nil { + t.Fatal(err) + } + cert, err := ParseCertificate(certDER) + if err != nil { + t.Fatal(err) + } + chain = append(chain, cert) + } + slices.Reverse(chain) + + var initialPolicies []OID + for _, pstr := range tc.InitialPolicySet { + policy, ok := nistTestPolicies[pstr] + if !ok { + t.Fatalf("unknown test policy: %s", pstr) + } + initialPolicies = append(initialPolicies, policy) + } + + valid := policiesValid(chain, VerifyOptions{ + CertificatePolicies: initialPolicies, + inhibitPolicyMapping: tc.InitialPolicyMappingInhibit, + requireExplicitPolicy: tc.InitialExplicitPolicy, + inhibitAnyPolicy: tc.InitialAnyPolicyInhibit, + }) + if !valid { + if !tc.ShouldValidate { + return + } + t.Fatalf("Failed to validate: %s", err) + } + if !tc.ShouldValidate { + t.Fatal("Expected path validation to fail") + } + }) + } +} diff --git a/crypto/x509/revocation/crl/test_crl b/crypto/x509/revocation/crl/test_crl deleted file mode 100644 index 67eedf91bd00b3fe581bff8d1249143df4cd1194..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1029676 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 jnq@Z=T{s{CP)wPKeRZpCy1Gz_-9twqxxW;H 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�mu=X5zw@&|P-1-PINRJ60 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 e8-@Y;l!;AzUS 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 z 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%0APM3CTob+N#Ko!$f5>kW3|{*n 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
bdxj{ZzXP=E`B?;AW`P-P=**Y+`PgID2PG(U?X( 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˔(Ua-3qzzO50Ys-!{FaNlSr?h%YV6n^@ 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�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
SkS>{dW7@m%y8ktPw*wMFyVw27rSu!fESxtx8 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