From 45bcbd6b69308b6469f1489f4ccbcd12c018c359 Mon Sep 17 00:00:00 2001 From: "Tony Arcieri (iqlusion)" Date: Wed, 23 Sep 2020 11:51:55 -0700 Subject: [PATCH] Upgrade k256/p256/p384/ed25519-dalek; remove ring/signatory/secp256k1 (#91) This commit removes Signatory and its associated wrapper crates (for *ring* and the `secp256k1` crates, which use native code bindings). They can now be completely replaced by the `k256`, `p256`, and `p384` crates, along with `ed25519-dalek`, which natively implement the traits from the `signature` crate that Signatory originally provided. The result is fewer overall dependencies and faster compile times (owing to the elimination of C/ASM dependencies) Additionally, this PR pulls in the `ccm` crate and begins to properly implement key/object wrapping (i.e. encryption) using the same algorithms as the YubiHSM2 itself. --- Cargo.lock | 463 +++++++++++++++---------- Cargo.toml | 24 +- src/asymmetric/public_key.rs | 46 ++- src/authentication/key.rs | 4 +- src/client.rs | 29 +- src/ecdsa.rs | 14 +- src/ecdsa/algorithm.rs | 6 +- src/ecdsa/commands.rs | 7 +- src/ecdsa/nistp256.rs | 20 ++ src/ecdsa/nistp384.rs | 16 + src/ecdsa/secp256k1.rs | 21 ++ src/ecdsa/signature.rs | 37 -- src/ecdsa/signer.rs | 199 ++++------- src/ed25519.rs | 5 +- src/ed25519/public_key.rs | 58 ++++ src/ed25519/signer.rs | 31 +- src/lib.rs | 5 +- src/mockhsm.rs | 1 + src/mockhsm/command.rs | 71 +++- src/mockhsm/digest.rs | 45 +++ src/mockhsm/object.rs | 2 +- src/mockhsm/object/objects.rs | 175 +++++----- src/mockhsm/object/payload.rs | 109 +++--- src/session/securechannel/challenge.rs | 4 +- src/uuid.rs | 4 +- src/wrap/key.rs | 4 +- src/wrap/nonce.rs | 4 +- tests/command/sign_ecdsa.rs | 34 +- tests/command/sign_eddsa.rs | 17 +- tests/ecdsa/mod.rs | 59 ++-- tests/ed25519/mod.rs | 5 +- 31 files changed, 890 insertions(+), 629 deletions(-) create mode 100644 src/ecdsa/nistp256.rs create mode 100644 src/ecdsa/nistp384.rs create mode 100644 src/ecdsa/secp256k1.rs delete mode 100644 src/ecdsa/signature.rs create mode 100644 src/ed25519/public_key.rs create mode 100644 src/mockhsm/digest.rs diff --git a/Cargo.lock b/Cargo.lock index e8f09a98..15734e13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +[[package]] +name = "aead" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" +dependencies = [ + "generic-array", +] + [[package]] name = "aes" version = "0.5.0" @@ -81,9 +90,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" @@ -105,12 +114,24 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64143ed23541fe0c6e2235905eaae37ceae1ca69e14dce4afcd6ec9643359d00" +dependencies = [ + "funty", + "radium", + "wyz", +] + [[package]] name = "block-buffer" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ + "block-padding", "generic-array", ] @@ -174,9 +195,20 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.41" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" +checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c" + +[[package]] +name = "ccm" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbba800a6a55058ecb75c7a42e3d16a715a2b1f1afa9acf07365d6ab30d62ce1" +dependencies = [ + "aead", + "block-cipher", + "subtle", +] [[package]] name = "cfg-if" @@ -223,6 +255,12 @@ dependencies = [ "dbl", ] +[[package]] +name = "const-oid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d9162b7289a46e86208d6af2c686ca5bfde445878c41a458a9fac706252d0b" + [[package]] name = "cpuid-bool" version = "0.1.2" @@ -274,6 +312,16 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -300,17 +348,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "crossbeam-queue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "maybe-uninit", -] - [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -355,6 +392,19 @@ dependencies = [ "memchr", ] +[[package]] +name = "curve25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8492de420e9e60bc9a1d66e2dbb91825390b738a388606600663fc529b4b307" +dependencies = [ + "byteorder", + "digest", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "dbl" version = "0.3.0" @@ -375,27 +425,38 @@ dependencies = [ [[package]] name = "ecdsa" -version = "0.6.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb6ba8a681d3a9c48875d277f2b11c81934ad690a60a20bcc4eef500ff68e25d" +checksum = "4cda7dd00e9ace9867955ef64a176447b45f3e385f827bc686c832213bb7ec0e" dependencies = [ "elliptic-curve", - "k256", - "p256", - "p384", - "sha2", + "hmac", "signature", ] [[package]] name = "ed25519" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf038a7b6fd7ef78ad3348b63f3a17550877b0e28f8d68bcc94894d1412158bc" +checksum = "07dfc993ea376e864fe29a4099a61ca0bb994c6d7745a61bf60ddb3d64e05237" dependencies = [ "signature", ] +[[package]] +name = "ed25519-dalek" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d2e93f837d749c16d118e7ddf7a4dfd0ac8f452cf51e46e9348824e5ef6851" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand", + "serde", + "sha2", + "zeroize", +] + [[package]] name = "either" version = "1.6.0" @@ -404,16 +465,32 @@ checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" [[package]] name = "elliptic-curve" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2298f66754d859f4c099b0e645cfd258d2dfdfd1bac9496fd514d603ff6a5c6b" +checksum = "d9cc9ec2c11b93118b4f8dc0665a3741bc90121d44dc0410f899274c5745454c" dependencies = [ + "bitvec", + "const-oid", + "digest", + "ff", "generic-array", - "getrandom", + "group", + "rand_core", "subtle", "zeroize", ] +[[package]] +name = "ff" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01646e077d4ebda82b73f1bca002ea1e91561a77df2431a9e79729bcc31950ef" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + [[package]] name = "filetime" version = "0.2.12" @@ -426,6 +503,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "funty" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8" + [[package]] name = "generic-array" version = "0.14.4" @@ -438,13 +521,13 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] @@ -453,6 +536,17 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +[[package]] +name = "group" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11f9f5fbf1943b48ae7c2bf6846e7d827a512d1be4f23af708f5ca5d01dde1" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "half" version = "1.6.0" @@ -512,22 +606,32 @@ checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" [[package]] name = "js-sys" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a7e2c92a4804dd459b86c339278d0fe87cf93757fae222c3fa3ae75458bc73" +checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8" dependencies = [ "wasm-bindgen", ] [[package]] name = "k256" -version = "0.3.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f5cb67f9625a99c159fc0776e75d2e37f988cdf31c115e9c25225e61d858c" +checksum = "6e163af5c3d73a042455736aab29d9e98c7592f59ea5d3faf4a715fc4b8d36c2" dependencies = [ + "cfg-if", + "ecdsa", "elliptic-curve", + "sha2", + "sha3", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + [[package]] name = "lazy_static" version = "1.4.0" @@ -536,27 +640,33 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.74" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" [[package]] name = "libflate" -version = "0.1.27" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" +checksum = "e9bac9023e1db29c084f9f8cd9d3852e5e8fddf98fb47c4964a0ea4663d95949" dependencies = [ "adler32", "crc32fast", + "libflate_lz77", "rle-decode-fast", - "take_mut", ] +[[package]] +name = "libflate_lz77" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3286f09f7d4926fc486334f28d8d2e6ebe4f7f9994494b6dab27ddfad2c9b11b" + [[package]] name = "libusb1-sys" -version = "0.3.7" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d9ddd446b6f233a79ef7e6f73de63a58f3a9047d60c46f15cda31452a8f86e" +checksum = "ca5c2a82e2c56537de7d1a163575049593667af689122fafbccc117bbaa59079" dependencies = [ "cc", "libc", @@ -604,11 +714,12 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +checksum = "c60c0dfe32c10b43a144bad8fc83538c52f58302c92300ea7ec7bf7b38d5a7b9" dependencies = [ "adler", + "autocfg", ] [[package]] @@ -660,19 +771,22 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "p256" -version = "0.3.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a425ecb71515663b2735fd1e65bb638fd134c5ad23857eb197c2f157784476cf" +checksum = "1909a5a44a469f7d21c8dec2c634721ba15379b6cad80040d16abfaa8609b37c" dependencies = [ + "ecdsa", "elliptic-curve", + "sha2", ] [[package]] name = "p384" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae962014118ae1621d13f011e14902223013b1b4151922c3a682a233a967ddb" +checksum = "51ce2b1182d022bfbe2af57aeca44fe7832356dd2d998e98e423cd9b94fae4f7" dependencies = [ + "ecdsa", "elliptic-curve", ] @@ -709,11 +823,17 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ppv-lite86" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" + [[package]] name = "proc-macro2" -version = "1.0.19" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" dependencies = [ "unicode-xid", ] @@ -727,11 +847,58 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" +checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" dependencies = [ "autocfg", "crossbeam-deque", @@ -741,12 +908,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" +checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" dependencies = [ + "crossbeam-channel", "crossbeam-deque", - "crossbeam-queue", "crossbeam-utils", "lazy_static", "num_cpus", @@ -782,20 +949,6 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" -[[package]] -name = "ring" -version = "0.16.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c" -dependencies = [ - "cc", - "libc", - "spin", - "untrusted", - "web-sys", - "winapi", -] - [[package]] name = "rle-decode-fast" version = "1.0.1" @@ -804,9 +957,9 @@ checksum = "cabe4fa914dec5870285fa7f71f602645da47c486e68486d2b4ceb4a343e90ac" [[package]] name = "rusb" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac7576c07e31f5885d67ec09fd8509e4b730cea4266fecfe7f068f3a5d1f3b3" +checksum = "67fa037368ee577fca9ef237c5ec129084c18e7e3e5987cc611fb8b2d78cf84a" dependencies = [ "libc", "libusb1-sys", @@ -848,24 +1001,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "secp256k1" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2932dc07acd2066ff2e3921a4419606b220ba6cd03a9935123856cc534877056" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab2c26f0d3552a0f12e639ae8a64afc2e3db9c52fe32f5fc6c289d38519f220" -dependencies = [ - "cc", -] - [[package]] name = "semver" version = "0.9.0" @@ -883,9 +1018,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" dependencies = [ "serde_derive", ] @@ -902,9 +1037,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.115" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" dependencies = [ "proc-macro2", "quote", @@ -936,48 +1071,25 @@ dependencies = [ ] [[package]] -name = "signatory" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c20cbcf205bedce2ce5944ab41dd2e10b1dcee2831a0a2a071806761ed6eaba" -dependencies = [ - "ecdsa", - "ed25519", - "getrandom", - "sha2", - "signature", - "subtle-encoding", - "zeroize", -] - -[[package]] -name = "signatory-ring" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733ce7ee3754a6bc9cd7e015a32c0fd7034bbb85070418f831edc60124f81284" -dependencies = [ - "ring", - "signatory", -] - -[[package]] -name = "signatory-secp256k1" -version = "0.20.0" +name = "sha3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a50b18b66b81b859f8ee57b0e85658e1777fe2683ac37b7e756380f0a28f6e" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" dependencies = [ - "secp256k1", - "signatory", - "signature", + "block-buffer", + "digest", + "keccak", + "opaque-debug", ] [[package]] name = "signature" -version = "1.1.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65211b7b6fc3f14ff9fc7a2011a434e3e6880585bd2e9e9396315ae24cbf7852" +checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" dependencies = [ "digest", + "rand_core", "signature_derive", ] @@ -993,32 +1105,17 @@ dependencies = [ "synstructure", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "subtle" -version = "2.2.3" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] +checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" [[package]] name = "syn" -version = "1.0.38" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" +checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" dependencies = [ "proc-macro2", "quote", @@ -1037,17 +1134,11 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "tar" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8a4c1d0bee3230179544336c15eefb563cf0302955d962e456542323e8c2e8a" +checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290" dependencies = [ "filetime", "libc", @@ -1086,11 +1177,12 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] @@ -1159,12 +1251,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "url" version = "2.1.1" @@ -1214,11 +1300,17 @@ version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasm-bindgen" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0563a9a4b071746dd5aedbc3a28c6fe9be4586fb3fbadb67c400d4f53c6b16c" +checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1226,9 +1318,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc71e4c5efa60fb9e74160e89b93353bc24059999c0ae0fb03affc39770310b0" +checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68" dependencies = [ "bumpalo", "lazy_static", @@ -1241,9 +1333,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97c57cefa5fa80e2ba15641578b44d36e7a64279bc5ed43c6dbaf329457a2ed2" +checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1251,9 +1343,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841a6d1c35c6f596ccea1f82504a192a60378f64b3bb0261904ad8f2f5657556" +checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe" dependencies = [ "proc-macro2", "quote", @@ -1264,15 +1356,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93b162580e34310e5931c4b792560108b10fd14d64915d7fff8ff00180e70092" +checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307" [[package]] name = "web-sys" -version = "0.3.44" +version = "0.3.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda38f4e5ca63eda02c059d243aa25b5f35ab98451e518c51612cd0f1bd19a47" +checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d" dependencies = [ "js-sys", "wasm-bindgen", @@ -1309,6 +1401,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "xattr" version = "0.2.2" @@ -1326,24 +1424,27 @@ dependencies = [ "anomaly", "bitflags", "block-modes", + "ccm", "chrono", "cmac", "criterion", - "getrandom", + "digest", + "ecdsa", + "ed25519", + "ed25519-dalek", "harp", "hmac", + "k256", "lazy_static", "log", + "p256", + "p384", "pbkdf2", - "ring", + "rand_core", "rusb", - "secp256k1", "serde", "serde_json", "sha2", - "signatory", - "signatory-ring", - "signatory-secp256k1", "signature", "subtle", "thiserror", @@ -1354,18 +1455,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" +checksum = "05f33972566adbd2d3588b0491eb94b98b43695c4ef897903470ede4f3f5a28a" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" +checksum = "c3f369ddb18862aba61aa49bf31e74d29f0f162dec753063200e1dc084345d16" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 8fc30a4b..aeab7b19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,21 +20,26 @@ aes = "0.5" anomaly = "0.2" bitflags = "1" block-modes = "0.6" +ccm = { version = "0.2", optional = true, features = ["std"] } chrono = { version = "0.4", features = ["serde"], optional = true } cmac = "0.4" -getrandom = "0.1" +digest = { version = "0.9", optional = true, default-features = false } +ecdsa = { version = "0.8", default-features = false } +ed25519 = "1" +ed25519-dalek = { version = "1", optional = true } harp = { version = "0.1", optional = true } hmac = { version = "0.9", optional = true } +k256 = { version = "0.5", optional = true, features = ["ecdsa", "keccak256", "sha256"] } log = "0.4" +p256 = { version = "0.5", default-features = false, features = ["ecdsa-core"] } +p384 = { version = "0.4", default-features = false, features = ["ecdsa"] } pbkdf2 = { version = "0.5", optional = true, default-features = false } serde = { version = "1", features = ["serde_derive"] } serde_json = { version = "1", optional = true } -ring = { version = "0.16", optional = true, default-features = false } +rand_core = { version = "0.5", features = ["std"] } rusb = { version = "0.6", optional = true } -secp256k1 = { version = "0.17", optional = true } sha2 = { version = "0.9", optional = true } -signatory = { version = "0.20", features = ["digest", "ecdsa", "ed25519", "k256", "p256", "p384"] } -signature = { version = "1.1.0", features = ["derive-preview"] } +signature = { version = "1", features = ["derive-preview"] } subtle = "2" thiserror = "1" tiny_http = { version = "0.7", optional = true } @@ -43,23 +48,24 @@ zeroize = { version = "1", features = ["zeroize_derive"] } [dev-dependencies] criterion = "0.3" +ed25519-dalek = "1" lazy_static = "1" -ring = { version = "0.16", default-features = false } -signatory-ring = "0.20" -signatory-secp256k1 = "0.20" +p256 = { version = "0.5", features = ["ecdsa"] } [features] default = ["http", "passwords", "setup"] http-server = ["tiny_http"] http = ["harp"] -mockhsm = ["passwords", "ring"] +mockhsm = ["ccm", "digest", "ed25519-dalek", "p256/ecdsa", "secp256k1"] passwords = ["hmac", "pbkdf2", "sha2"] +secp256k1 = ["k256"] setup = ["chrono", "passwords", "serde_json", "uuid/serde"] untested = ["sha2"] usb = ["rusb"] [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--cfg", "docsrs"] [[bench]] name = "ed25519" diff --git a/src/asymmetric/public_key.rs b/src/asymmetric/public_key.rs index 69640b79..750066b2 100644 --- a/src/asymmetric/public_key.rs +++ b/src/asymmetric/public_key.rs @@ -1,16 +1,17 @@ //! Public keys for use with asymmetric cryptography / signatures -use crate::{ - asymmetric, - ecdsa::{self, algorithm::CurveAlgorithm}, - ed25519, +use crate::{asymmetric, ecdsa::algorithm::CurveAlgorithm, ed25519}; +use ::ecdsa::{ + elliptic_curve::{ + sec1::{self, UncompressedPointSize, UntaggedPointSize}, + weierstrass::{point, Curve}, + }, + generic_array::{ + typenum::{Unsigned, U1}, + ArrayLength, GenericArray, + }, }; use serde::{Deserialize, Serialize}; -use signatory::ecdsa::{ - curve::{CompressedPointSize, UncompressedPointSize}, - generic_array::{typenum::U1, ArrayLength, GenericArray}, - Curve, -}; use std::ops::Add; /// Response from `command::get_public_key` @@ -52,19 +53,26 @@ impl PublicKey { } /// Return the ECDSA public key of the given curve type if applicable - pub fn ecdsa(&self) -> Option> + pub fn ecdsa(&self) -> Option> where - C: Curve + CurveAlgorithm, - ::Output: Add + ArrayLength, - CompressedPointSize: ArrayLength, - UncompressedPointSize: ArrayLength, + C: Curve + CurveAlgorithm + point::Compression, + UntaggedPointSize: Add + ArrayLength, + UncompressedPointSize: ArrayLength, { - if self.algorithm == C::asymmetric_algorithm() { - Some(ecdsa::PublicKey::from_untagged_point( - GenericArray::from_slice(&self.bytes), - )) + if self.algorithm != C::asymmetric_algorithm() + || self.bytes.len() != C::FieldSize::to_usize() * 2 + { + return None; + } + + let mut bytes = GenericArray::default(); + bytes.copy_from_slice(&self.bytes); + let result = sec1::EncodedPoint::from_untagged_bytes(&bytes); + + if C::COMPRESS_POINTS { + Some(result.compress()) } else { - None + Some(result) } } diff --git a/src/authentication/key.rs b/src/authentication/key.rs index b1e1f58b..d5aa41a4 100644 --- a/src/authentication/key.rs +++ b/src/authentication/key.rs @@ -2,11 +2,11 @@ use super::{Error, ErrorKind}; use anomaly::ensure; -use getrandom::getrandom; #[cfg(feature = "hmac")] use hmac::Hmac; #[cfg(feature = "pbkdf2")] use pbkdf2::pbkdf2; +use rand_core::{OsRng, RngCore}; #[cfg(feature = "sha2")] use sha2::Sha256; use std::fmt::{self, Debug}; @@ -36,7 +36,7 @@ impl Key { /// Generate a random `Key` using `OsRng`. pub fn random() -> Self { let mut challenge = [0u8; SIZE]; - getrandom(&mut challenge).expect("RNG failure!"); + OsRng.fill_bytes(&mut challenge); Key(challenge) } diff --git a/src/client.rs b/src/client.rs index 1656c37f..0d8b1cc2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -21,7 +21,7 @@ use crate::{ connector::Connector, device::{self, commands::*, StorageInfo}, domain::Domain, - ecdsa::{self, commands::*}, + ecdsa::commands::*, ed25519::{self, commands::*}, hmac::{self, commands::*}, object::{self, commands::*, generate}, @@ -903,27 +903,22 @@ impl Client { /// /// /// - /// # secp256k1 notes + /// # Security Warning /// - /// The YubiHSM 2 does not produce signatures in "low S" form, which is expected - /// for most cryptocurrency applications (the typical use case for secp256k1). + /// This is a low-level ECDSA API, and if used incorrectly could potentially + /// result in forgeable signatures. /// - /// If your application demands this (e.g. Bitcoin), you'll need to normalize - /// the signatures. One option for this is the `secp256k1` crate's - /// [Signature::normalize_s] function. - /// - /// Normalization functionality is built into the `yubihsm::signatory` API - /// found in this crate (when the `secp256k1` feature is enabled). - pub fn sign_ecdsa(&self, key_id: object::Id, digest: T) -> Result + /// We recommend using the [`ecdsa::Signer`] type instead, which provides a + /// high-level, well-typed, misuse resistant API. + pub fn sign_ecdsa_prehash_raw(&self, key_id: object::Id, digest: T) -> Result, Error> where T: Into>, { - Ok(self - .send_command(SignEcdsaCommand { - key_id, - digest: digest.into(), - })? - .into()) + self.send_command(SignEcdsaCommand { + key_id, + digest: digest.into(), + }) + .map(Into::into) } /// Compute an Ed25519 signature with the given key ID. diff --git a/src/ecdsa.rs b/src/ecdsa.rs index 1c9a7c67..659cfed3 100644 --- a/src/ecdsa.rs +++ b/src/ecdsa.rs @@ -1,9 +1,17 @@ //! Elliptic Curve Digital Signature Algorithm (ECDSA) support pub mod algorithm; +pub mod nistp256; +pub mod nistp384; + +#[cfg(feature = "secp256k1")] +pub mod secp256k1; + pub(crate) mod commands; -mod signature; mod signer; -pub use self::{algorithm::Algorithm, signature::Signature, signer::Signer}; -pub use signatory::ecdsa::{curve, PublicKey}; +pub use self::{algorithm::Algorithm, nistp256::NistP256, nistp384::NistP384, signer::Signer}; +pub use ::ecdsa::{asn1, elliptic_curve::sec1, signature, Signature}; + +#[cfg(feature = "secp256k1")] +pub use self::secp256k1::Secp256k1; diff --git a/src/ecdsa/algorithm.rs b/src/ecdsa/algorithm.rs index 7173a548..368befdd 100644 --- a/src/ecdsa/algorithm.rs +++ b/src/ecdsa/algorithm.rs @@ -1,8 +1,11 @@ //! ECDSA algorithms (i.e. hash functions) +use super::{NistP256, NistP384}; use crate::{algorithm, asymmetric}; use anomaly::fail; -use signatory::ecdsa::curve::{NistP256, NistP384, Secp256k1}; + +#[cfg(feature = "secp256k1")] +use super::Secp256k1; /// Valid algorithms for asymmetric keys #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -63,6 +66,7 @@ impl CurveAlgorithm for NistP384 { } } +#[cfg(feature = "secp256k1")] impl CurveAlgorithm for Secp256k1 { fn asymmetric_algorithm() -> asymmetric::Algorithm { asymmetric::Algorithm::EcK256 diff --git a/src/ecdsa/commands.rs b/src/ecdsa/commands.rs index a32e986c..68199620 100644 --- a/src/ecdsa/commands.rs +++ b/src/ecdsa/commands.rs @@ -2,7 +2,6 @@ //! //! -use super::Signature; use crate::{ command::{self, Command}, object, @@ -26,14 +25,14 @@ impl Command for SignEcdsaCommand { /// Response from ECDSA signing request #[derive(Serialize, Deserialize, Debug)] -pub struct SignEcdsaResponse(pub(crate) Signature); +pub struct SignEcdsaResponse(pub Vec); impl Response for SignEcdsaResponse { const COMMAND_CODE: command::Code = command::Code::SignEcdsa; } -impl From for Signature { - fn from(response: SignEcdsaResponse) -> Signature { +impl From for Vec { + fn from(response: SignEcdsaResponse) -> Vec { response.0 } } diff --git a/src/ecdsa/nistp256.rs b/src/ecdsa/nistp256.rs new file mode 100644 index 00000000..e9f2a842 --- /dev/null +++ b/src/ecdsa/nistp256.rs @@ -0,0 +1,20 @@ +//! NIST P-256 elliptic curve (a.k.a. prime256v1, secp256r1) +//! +//! ## About +//! +//! NIST P-256 is a Weierstrass curve specified in FIPS 186-4: Digital Signature +//! Standard (DSS): +//! +//! +//! +//! Also known as prime256v1 (ANSI X9.62) and secp256r1 (SECG), it's included in +//! the US National Security Agency's "Suite B" and is widely used in protocols +//! like TLS and the associated X.509 PKI. + +pub use p256::NistP256; + +/// ECDSA/P-256 signature (fixed-size) +pub type Signature = super::Signature; + +/// ECDSA/P-256 signer +pub type Signer = super::Signer; diff --git a/src/ecdsa/nistp384.rs b/src/ecdsa/nistp384.rs new file mode 100644 index 00000000..0781ae59 --- /dev/null +++ b/src/ecdsa/nistp384.rs @@ -0,0 +1,16 @@ +//! NIST P-384 elliptic curve. +//! +//! ## About +//! +//! NIST P-384 is a Weierstrass curve specified in FIPS 186-4: Digital Signature +//! Standard (DSS): +//! +//! + +pub use p384::NistP384; + +/// ECDSA/P-384 signature (fixed-size) +pub type Signature = super::Signature; + +/// ECDSA/P-384 signer +pub type Signer = super::Signer; diff --git a/src/ecdsa/secp256k1.rs b/src/ecdsa/secp256k1.rs new file mode 100644 index 00000000..d9e6d061 --- /dev/null +++ b/src/ecdsa/secp256k1.rs @@ -0,0 +1,21 @@ +//! secp256k1 elliptic curve +//! +//! ## About +//! +//! The secp256k1 elliptic curve is specified by Certicom's SECG in +//! "SEC 2: Recommended Elliptic Curve Domain Parameters": +//! +//! +//! +//! It's primarily notable for usage in Bitcoin and other cryptocurrencies. + +pub use k256::{ecdsa::recoverable, Secp256k1}; + +/// ECDSA/secp256k1 signature (fixed-size) +pub type Signature = super::Signature; + +/// ECDSA/secp256k1 signature with public key recovery support (ala Ethereum) +pub type RecoverableSignature = k256::ecdsa::recoverable::Signature; + +/// ECDSA/secp256k1 signer +pub type Signer = super::Signer; diff --git a/src/ecdsa/signature.rs b/src/ecdsa/signature.rs deleted file mode 100644 index a05aa518..00000000 --- a/src/ecdsa/signature.rs +++ /dev/null @@ -1,37 +0,0 @@ -//! ECDSA signatures - -use serde::{Deserialize, Serialize}; - -/// ECDSA signatures (ASN.1 DER encoded) -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct Signature(pub Vec); - -#[allow(clippy::len_without_is_empty)] -impl Signature { - /// Unwrap inner byte vector - pub fn into_vec(self) -> Vec { - self.into() - } - - /// Get length of the signature - pub fn len(&self) -> usize { - self.0.len() - } - - /// Get slice of the inner byte vector - pub fn as_slice(&self) -> &[u8] { - self.as_ref() - } -} - -impl AsRef<[u8]> for Signature { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } -} - -impl Into> for Signature { - fn into(self) -> Vec { - self.0 - } -} diff --git a/src/ecdsa/signer.rs b/src/ecdsa/signer.rs index d198f1e2..89ebccea 100644 --- a/src/ecdsa/signer.rs +++ b/src/ecdsa/signer.rs @@ -2,190 +2,123 @@ //! //! To enable secp256k1 support, build with the `secp256k1` cargo feature enabled. -use crate::{ecdsa::algorithm::CurveAlgorithm, object, Client}; -use signatory::{ - ecdsa::{ - curve::{CompressedPointSize, Curve, NistP256, NistP384, UncompressedPointSize}, - generic_array::{ - typenum::{U1, U32, U48}, - ArrayLength, - }, - Asn1Signature, FixedSignature, PublicKey, +use super::{algorithm::CurveAlgorithm, NistP256, NistP384}; +use crate::{object, Client}; +use ::ecdsa::{ + elliptic_curve::{ + consts::{U1, U32}, + generic_array::ArrayLength, + sec1::{self, UncompressedPointSize, UntaggedPointSize}, + weierstrass::{point, Curve}, }, - public_key::PublicKeyed, - signature::{DigestSigner, Error, Signature}, + Signature, }; use signature::digest::Digest; -use std::{marker::PhantomData, ops::Add}; +use signature::{DigestSigner, Error}; +use std::ops::Add; #[cfg(feature = "secp256k1")] -use signatory::ecdsa::{curve::Secp256k1, generic_array::GenericArray}; +use super::Secp256k1; /// ECDSA signature provider for yubihsm-client #[derive(signature::Signer)] -pub struct Signer { +pub struct Signer +where + C: Curve + CurveAlgorithm + point::Compression, + UntaggedPointSize: Add + ArrayLength, + UncompressedPointSize: ArrayLength, +{ /// YubiHSM client client: Client, /// ID of an ECDSA key to perform signatures with signing_key_id: object::Id, - /// Placeholder for elliptic curve type - curve: PhantomData, + /// Public key associated with the private key in the YubiHSM + public_key: sec1::EncodedPoint, } impl Signer where - C: Curve + CurveAlgorithm, - ::Output: Add + ArrayLength, - CompressedPointSize: ArrayLength, - UncompressedPointSize: ArrayLength, + C: Curve + CurveAlgorithm + point::Compression, + UntaggedPointSize: Add + ArrayLength, + UncompressedPointSize: ArrayLength, { /// Create a new YubiHSM-backed ECDSA signer pub fn create(client: Client, signing_key_id: object::Id) -> Result { - let signer = Self { + let public_key = client + .get_public_key(signing_key_id)? + .ecdsa() + .ok_or_else(Error::new)?; + + Ok(Self { client, signing_key_id, - curve: PhantomData, - }; - - // Ensure the signing_key_id slot contains a valid ECDSA public key - signer.public_key()?; + public_key: public_key, + }) + } - Ok(signer) + /// Create an ECDSA signature from the provided digest + fn sign_ecdsa_digest(&self, digest: D) -> Result, Error> { + self.client + .sign_ecdsa_prehash_raw(self.signing_key_id, digest.finalize().as_slice()) + .map_err(Error::from_source) } -} -impl PublicKeyed> for Signer -where - C: Curve + CurveAlgorithm, - ::Output: Add + ArrayLength, - CompressedPointSize: ArrayLength, - UncompressedPointSize: ArrayLength, -{ - /// Obtain the public key which identifies this signer - fn public_key(&self) -> Result, Error> { - let public_key = self.client.get_public_key(self.signing_key_id)?; - public_key.ecdsa().ok_or_else(Error::new) + /// Get the public key for the YubiHSM-backed private key. + pub fn public_key(&self) -> &sec1::EncodedPoint { + &self.public_key } } -impl DigestSigner> for Signer +impl From<&Signer> for sec1::EncodedPoint where - D: Digest + Default, + Self: Clone, + C: Curve + CurveAlgorithm + point::Compression, + UntaggedPointSize: Add + ArrayLength, + UncompressedPointSize: ArrayLength, { - /// Compute an ASN.1 DER-encoded P-256 ECDSA signature of the given digest - fn try_sign_digest(&self, digest: D) -> Result, Error> { - self.sign_nistp256_asn1(digest) + fn from(signer: &Signer) -> sec1::EncodedPoint { + signer.public_key().clone() } } -impl DigestSigner> for Signer +impl DigestSigner> for Signer where D: Digest + Default, { /// Compute a fixed-sized P-256 ECDSA signature of the given digest - fn try_sign_digest(&self, digest: D) -> Result, Error> { - Ok(FixedSignature::from(&self.sign_nistp256_asn1(digest)?)) - } -} - -impl DigestSigner> for Signer -where - D: Digest + Default, -{ - /// Compute an ASN.1 DER-encoded P-384 ECDSA signature of the given digest - fn try_sign_digest(&self, digest: D) -> Result, Error> { - self.sign_nistp384_asn1(digest) - } -} - -impl DigestSigner> for Signer -where - D: Digest + Default, -{ - /// Compute a fixed-sized P-384 ECDSA signature of the given digest - fn try_sign_digest(&self, digest: D) -> Result, Error> { - Ok(FixedSignature::from(&self.sign_nistp384_asn1(digest)?)) + fn try_sign_digest(&self, digest: D) -> Result, Error> { + let sig = self.sign_ecdsa_digest(digest)?; + Signature::from_asn1(&sig) } } -#[cfg(feature = "secp256k1")] -impl DigestSigner> for Signer +impl DigestSigner> for Signer where D: Digest + Default, { - /// Compute an ASN.1 DER-encoded secp256k1 ECDSA signature of the given digest - fn try_sign_digest(&self, digest: D) -> Result, Error> { - let asn1_sig = self.sign_secp256k1(digest)?.serialize_der(); - Ok(Asn1Signature::from_bytes(&asn1_sig).unwrap()) + /// Compute a fixed-sized P-384 ECDSA signature of the given digest + fn try_sign_digest(&self, digest: D) -> Result, Error> { + let sig = self.sign_ecdsa_digest(digest)?; + Signature::from_asn1(&sig) } } #[cfg(feature = "secp256k1")] -impl DigestSigner> for Signer +impl DigestSigner> for Signer where D: Digest + Default, { /// Compute a fixed-size secp256k1 ECDSA signature of the given digest - fn try_sign_digest(&self, digest: D) -> Result, Error> { - let fixed_sig = - GenericArray::clone_from_slice(&self.sign_secp256k1(digest)?.serialize_compact()); - - Ok(FixedSignature::from(fixed_sig)) - } -} - -impl Signer { - /// Compute an ASN.1 DER signature over P-256 - fn sign_nistp256_asn1(&self, digest: D) -> Result, Error> - where - D: Digest + Default, - { - let signature = self - .client - .sign_ecdsa(self.signing_key_id, digest.finalize().as_slice())?; - - Asn1Signature::from_bytes(signature.as_ref()) - } -} - -impl Signer { - /// Compute an ASN.1 DER signature over P-384 - fn sign_nistp384_asn1(&self, digest: D) -> Result, Error> - where - D: Digest + Default, - { - let signature = self - .client - .sign_ecdsa(self.signing_key_id, digest.finalize().as_slice())?; - - Asn1Signature::from_bytes(signature.as_ref()) - } -} - -#[cfg(feature = "secp256k1")] -impl Signer { - /// Compute either an ASN.1 DER or fixed-sized signature using libsecp256k1 - fn sign_secp256k1(&self, digest: D) -> Result - where - D: Digest + Default, - { - // Sign the data using the YubiHSM, producing an ASN.1 DER encoded signature - let raw_sig = self - .client - .sign_ecdsa(self.signing_key_id, digest.finalize().as_slice())?; - - // Parse the signature using libsecp256k1 - let mut sig = secp256k1::Signature::from_der_lax(raw_sig.as_ref()).unwrap(); - - // Normalize the signature to a "low S" form. libsecp256k1 will only - // accept signatures for which s is in the lower half of the field range. - // The signatures produced by the YubiHSM do not have this property, so - // we normalize them to maximize compatibility with secp256k1 - // applications (e.g. Bitcoin). - sig.normalize_s(); - - Ok(sig) + fn try_sign_digest(&self, digest: D) -> Result, Error> { + let mut signature = self + .sign_ecdsa_digest(digest) + .and_then(|sig| Signature::from_asn1(&sig))?; + + // Low-S normalize per BIP 0062: Dealing with Malleability: + // + signature.normalize_s()?; + Ok(signature) } } diff --git a/src/ed25519.rs b/src/ed25519.rs index 6d51590c..289dd471 100644 --- a/src/ed25519.rs +++ b/src/ed25519.rs @@ -1,7 +1,8 @@ //! Ed25519 digital signature algorithm support pub(crate) mod commands; +mod public_key; mod signer; -pub use self::signer::Signer; -pub use signatory::ed25519::{PublicKey, Signature, SIGNATURE_SIZE}; +pub use self::{public_key::PublicKey, signer::Signer}; +pub use ::ed25519::{Signature, SIGNATURE_LENGTH as SIGNATURE_SIZE}; diff --git a/src/ed25519/public_key.rs b/src/ed25519/public_key.rs new file mode 100644 index 00000000..a16e1099 --- /dev/null +++ b/src/ed25519/public_key.rs @@ -0,0 +1,58 @@ +//! Ed25519 public keys + +// TODO(tarcieri): move this upstream into the `ed25519` crate + +use std::fmt::{self, Debug}; + +/// Size of an Ed25519 public key in bytes (256-bits) +pub const PUBLIC_KEY_SIZE: usize = 32; + +/// Ed25519 public keys +#[derive(Copy, Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct PublicKey(pub [u8; PUBLIC_KEY_SIZE]); + +impl PublicKey { + /// Create an Ed25519 public key from a 32-byte array + pub fn new(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self { + PublicKey(bytes) + } + + /// Create an Ed25519 public key from its serialized (compressed Edwards-y) form + pub fn from_bytes(bytes: B) -> Option + where + B: AsRef<[u8]>, + { + if bytes.as_ref().len() == PUBLIC_KEY_SIZE { + let mut public_key = [0u8; PUBLIC_KEY_SIZE]; + public_key.copy_from_slice(bytes.as_ref()); + Some(PublicKey(public_key)) + } else { + None + } + } + + /// Obtain public key as a byte array reference + #[inline] + pub fn as_bytes(&self) -> &[u8; PUBLIC_KEY_SIZE] { + &self.0 + } + + /// Convert public key into owned byte array + #[inline] + pub fn into_bytes(self) -> [u8; PUBLIC_KEY_SIZE] { + self.0 + } +} + +impl AsRef<[u8]> for PublicKey { + #[inline] + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl Debug for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ed25519::PublicKey({:?})", self.as_ref()) + } +} diff --git a/src/ed25519/signer.rs b/src/ed25519/signer.rs index d2b9d213..71c56a98 100644 --- a/src/ed25519/signer.rs +++ b/src/ed25519/signer.rs @@ -3,8 +3,7 @@ //! To use this provider, first establish a session with the `YubiHSM 2`, then //! call the appropriate signer methods to obtain signers. -use crate::{object, Client}; -use signatory::{ed25519, public_key::PublicKeyed}; +use crate::{ed25519::PublicKey, object, Client}; use signature::Error; /// Ed25519 signature provider for yubihsm-client @@ -14,27 +13,35 @@ pub struct Signer { /// ID of an Ed25519 key to perform signatures with signing_key_id: object::Id, + + /// Public key + public_key: PublicKey, } impl Signer { /// Create a new YubiHSM-backed Ed25519 signer pub fn create(client: Client, signing_key_id: object::Id) -> Result { - let signer = Self { + let public_key = client + .get_public_key(signing_key_id)? + .ed25519() + .ok_or_else(Error::new)?; + + Ok(Self { client, signing_key_id, - }; - - // Ensure the signing_key_id slot contains a valid Ed25519 public key - signer.public_key()?; + public_key, + }) + } - Ok(signer) + /// Get the public key for the YubiHSM-backed Ed25519 private key + pub fn public_key(&self) -> &PublicKey { + &self.public_key } } -impl PublicKeyed for Signer { - fn public_key(&self) -> Result { - let public_key = self.client.get_public_key(self.signing_key_id)?; - public_key.ed25519().ok_or_else(Error::new) +impl From<&Signer> for PublicKey { + fn from(signer: &Signer) -> PublicKey { + signer.public_key } } diff --git a/src/lib.rs b/src/lib.rs index 5c58a22e..e2068928 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,12 +51,13 @@ //! [commands]: https://developers.yubico.com/YubiHSM2/Commands/ //! [yubihsm-connector]: https://developers.yubico.com/YubiHSM2/Component_Reference/yubihsm-connector/ -#![forbid(unsafe_code)] -#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] #![doc( html_logo_url = "https://raw.githubusercontent.com/iqlusioninc/yubihsm.rs/develop/img/logo.png", html_root_url = "https://docs.rs/yubihsm/0.34.0" )] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![forbid(unsafe_code)] +#![warn(missing_docs, rust_2018_idioms, unused_qualifications)] #[macro_use] extern crate log; diff --git a/src/mockhsm.rs b/src/mockhsm.rs index bc04c8bb..5c1e39b3 100644 --- a/src/mockhsm.rs +++ b/src/mockhsm.rs @@ -9,6 +9,7 @@ use std::sync::{Arc, Mutex}; mod audit; mod command; mod connection; +mod digest; mod error; mod object; mod session; diff --git a/src/mockhsm/command.rs b/src/mockhsm/command.rs index 3424828d..035eb157 100644 --- a/src/mockhsm/command.rs +++ b/src/mockhsm/command.rs @@ -1,6 +1,6 @@ //! Commands supported by the `MockHsm` -use super::{object::Payload, state::State, MOCK_SERIAL_NUMBER}; +use super::{digest::MockDigest256, object::Payload, state::State, MOCK_SERIAL_NUMBER}; use crate::{ algorithm::*, asymmetric::{self, commands::*, PublicKey}, @@ -9,7 +9,8 @@ use crate::{ command::{Code, Message}, connector, device::{self, commands::*, SerialNumber, StorageInfo}, - ecdh, ecdsa, + ecdh, + ecdsa::{self, commands::*}, ed25519::commands::*, hmac::{self, commands::*}, object::{self, commands::*}, @@ -23,10 +24,14 @@ use crate::{ wrap::{self, commands::*}, Capability, }; +use ::ecdsa::{ + elliptic_curve::{Field, FromDigest}, + hazmat::SignPrimitive, +}; use ::hmac::{Hmac, Mac}; use cmac::crypto_mac::NewMac; -use getrandom::getrandom; -use ring::signature::Ed25519KeyPair; +use ed25519_dalek as ed25519; +use rand_core::{OsRng, RngCore}; use sha2::Sha256; use std::{io::Cursor, str::FromStr}; use subtle::ConstantTimeEq; @@ -111,6 +116,7 @@ pub(crate) fn session_message( Code::PutWrapKey => put_wrap_key(state, &command.data), Code::ResetDevice => return Ok(reset_device(state, session_id)), Code::SetLogIndex => SetLogIndexResponse {}.serialize(), + Code::SignEcdsa => sign_ecdsa(state, &command.data), Code::SignEddsa => sign_eddsa(state, &command.data), Code::GetStorageInfo => get_storage_info(), Code::VerifyHmac => verify_hmac(state, &command.data), @@ -231,7 +237,7 @@ fn export_wrapped(state: &mut State, cmd_data: &[u8]) -> response::Message { match state .objects - .wrap(wrap_key_id, object_id, object_type, &nonce) + .wrap_obj(wrap_key_id, object_id, object_type, &nonce) { Ok(ciphertext) => ExportWrappedResponse(wrap::Message { nonce, ciphertext }).serialize(), Err(e) => { @@ -340,7 +346,7 @@ fn get_opaque(state: &State, cmd_data: &[u8]) -> response::Message { .unwrap_or_else(|e| panic!("error parsing Code::GetOpaqueObject: {:?}", e)); if let Some(obj) = state.objects.get(command.object_id, object::Type::Opaque) { - GetOpaqueResponse(obj.payload.as_ref().into()).serialize() + GetOpaqueResponse(obj.payload.to_bytes()).serialize() } else { debug!("no such opaque object ID: {:?}", command.object_id); device::ErrorKind::ObjectNotFound.into() @@ -366,7 +372,7 @@ fn get_pseudo_random(_state: &State, cmd_data: &[u8]) -> response::Message { .unwrap_or_else(|e| panic!("error parsing Code::GetPseudoRandom: {:?}", e)); let mut bytes = vec![0u8; command.bytes as usize]; - getrandom(&mut bytes).expect("RNG failure!"); + OsRng.fill_bytes(&mut bytes); GetPseudoRandomResponse { bytes }.serialize() } @@ -414,7 +420,7 @@ fn import_wrapped(state: &mut State, cmd_data: &[u8]) -> response::Message { } = deserialize(cmd_data) .unwrap_or_else(|e| panic!("error parsing Code::ImportWrapped: {:?}", e)); - match state.objects.unwrap(wrap_key_id, &nonce, ciphertext) { + match state.objects.unwrap_obj(wrap_key_id, &nonce, ciphertext) { Ok(obj) => ImportWrappedResponse { object_type: obj.object_type, object_id: obj.object_id, @@ -606,6 +612,47 @@ fn reset_device(state: &mut State, session_id: session::Id) -> Vec { response } +/// Sign a message using the ECDSA signature algorithm +fn sign_ecdsa(state: &State, cmd_data: &[u8]) -> response::Message { + let command: SignEcdsaCommand = + deserialize(cmd_data).unwrap_or_else(|e| panic!("error parsing Code::SignEcdsa: {:?}", e)); + + if let Some(obj) = state + .objects + .get(command.key_id, object::Type::AsymmetricKey) + { + match &obj.payload { + Payload::EcdsaNistP256(secret_key) => { + let k = p256::Scalar::random(&mut OsRng); + let z = p256::Scalar::from_digest(MockDigest256::from(&command)); + let signature = secret_key + .secret_scalar() + .try_sign_prehashed(&k, &z) + .expect("ECDSA failure!"); + + SignEcdsaResponse(signature.to_asn1().as_ref().into()).serialize() + } + Payload::EcdsaSecp256k1(secret_key) => { + let k = k256::Scalar::random(&mut OsRng); + let z = k256::Scalar::from_digest(MockDigest256::from(&command)); + let signature = secret_key + .secret_scalar() + .try_sign_prehashed(&k, &z) + .expect("ECDSA failure!"); + + SignEcdsaResponse(signature.to_asn1().as_ref().into()).serialize() + } + _ => { + debug!("not an ECDSA key: {:?}", obj.algorithm()); + device::ErrorKind::InvalidCommand.into() + } + } + } else { + debug!("no such object ID: {:?}", command.key_id); + device::ErrorKind::ObjectNotFound.into() + } +} + /// Sign a message using the Ed25519 signature algorithm fn sign_eddsa(state: &State, cmd_data: &[u8]) -> response::Message { let command: SignEddsaCommand = @@ -615,10 +662,10 @@ fn sign_eddsa(state: &State, cmd_data: &[u8]) -> response::Message { .objects .get(command.key_id, object::Type::AsymmetricKey) { - if let Payload::Ed25519KeyPair(ref seed) = obj.payload { - let keypair = Ed25519KeyPair::from_seed_unchecked(seed).unwrap(); - - let signature_bytes = keypair.sign(command.data.as_ref()); + if let Payload::Ed25519Key(secret_key) = &obj.payload { + let expanded_secret_key = ed25519::ExpandedSecretKey::from(secret_key); + let public_key = ed25519::PublicKey::from(secret_key); + let signature_bytes = expanded_secret_key.sign(command.data.as_ref(), &public_key); SignEddsaResponse(signature_bytes.as_ref().into()).serialize() } else { debug!("not an Ed25519 key: {:?}", obj.algorithm()); diff --git a/src/mockhsm/digest.rs b/src/mockhsm/digest.rs new file mode 100644 index 00000000..66ca2288 --- /dev/null +++ b/src/mockhsm/digest.rs @@ -0,0 +1,45 @@ +//! Mock `Digest` type for use with ECDSA signatures + +use crate::ecdsa::commands::SignEcdsaCommand; +use digest::{ + consts::U32, generic_array::GenericArray, BlockInput, FixedOutputDirty, Output, Reset, Update, +}; + +/// Mock 256-bit digest +#[derive(Clone, Default)] +pub struct MockDigest256 { + output: Option>, +} + +impl From<&SignEcdsaCommand> for MockDigest256 { + fn from(cmd: &SignEcdsaCommand) -> MockDigest256 { + assert_eq!(cmd.digest.len(), 32); + Self { + output: Some(GenericArray::clone_from_slice(&cmd.digest[..])), + } + } +} + +impl BlockInput for MockDigest256 { + type BlockSize = U32; +} + +impl Update for MockDigest256 { + fn update(&mut self, _data: impl AsRef<[u8]>) { + unimplemented!("use explicit conversion from SignEcdsaCommand"); + } +} + +impl Reset for MockDigest256 { + fn reset(&mut self) { + self.output = None; + } +} + +impl FixedOutputDirty for MockDigest256 { + type OutputSize = U32; + + fn finalize_into_dirty(&mut self, output: &mut Output) { + *output = self.output.take().expect("never initialized!"); + } +} diff --git a/src/mockhsm/object.rs b/src/mockhsm/object.rs index 6fe19022..0f2b9e4b 100644 --- a/src/mockhsm/object.rs +++ b/src/mockhsm/object.rs @@ -42,7 +42,7 @@ impl<'a> From<&'a Object> for WrappedObject { fn from(obj: &'a Object) -> Self { Self { object_info: obj.object_info.clone(), - data: obj.payload.as_ref().into(), + data: obj.payload.to_bytes(), } } } diff --git a/src/mockhsm/object/objects.rs b/src/mockhsm/object/objects.rs index b29e9751..70db69b7 100644 --- a/src/mockhsm/object/objects.rs +++ b/src/mockhsm/object/objects.rs @@ -8,17 +8,71 @@ use crate::{ serialization::{deserialize, serialize}, wrap, Algorithm, Capability, Domain, }; +use aes::block_cipher::consts::{U13, U8}; use anomaly::{fail, format_err}; -use ring::aead::{self, AES_128_GCM, AES_256_GCM}; -use std::collections::{btree_map::Iter as BTreeMapIter, BTreeMap}; +use ccm::aead::{AeadInPlace, NewAead}; +use std::collections::{btree_map::Iter as MapIter, BTreeMap as Map}; + +/// AES-CCM with a 128-bit key +pub(crate) type Aes128Ccm = ccm::Ccm; + +/// AES-CCM with a 256-bit key +pub(crate) type Aes256Ccm = ccm::Ccm; + +/// AES-CCM key +pub(crate) enum AesCcmKey { + /// AES-CCM with a 128-bit key + Aes128Ccm(Aes128Ccm), + + /// AES-CCM with a 256-bit key + Aes256Ccm(Aes256Ccm), +} + +impl AesCcmKey { + /// Encrypt data in-place + pub fn encrypt_in_place( + &self, + nonce: &wrap::Nonce, + associated_data: &[u8], + buffer: &mut Vec, + ) -> Result<(), Error> { + match self { + AesCcmKey::Aes128Ccm(ccm) => { + ccm.encrypt_in_place(&nonce.0.into(), associated_data, buffer) + } + AesCcmKey::Aes256Ccm(ccm) => { + ccm.encrypt_in_place(&nonce.0.into(), associated_data, buffer) + } + } + .map_err(|_| format_err!(ErrorKind::CryptoError, "error encrypting wrapped object!").into()) + } + + /// Decrypt data in-place + pub fn decrypt_in_place( + &self, + nonce: &wrap::Nonce, + associated_data: &[u8], + buffer: &mut Vec, + ) -> Result<(), Error> { + match self { + AesCcmKey::Aes128Ccm(ccm) => { + ccm.decrypt_in_place(&nonce.0.into(), associated_data, buffer) + } + AesCcmKey::Aes256Ccm(ccm) => { + ccm.decrypt_in_place(&nonce.0.into(), associated_data, buffer) + } + } + .map_err(|_| format_err!(ErrorKind::CryptoError, "error decrypting wrapped object!").into()) + } +} /// Objects stored in the `MockHsm` #[derive(Debug)] -pub(crate) struct Objects(BTreeMap); +pub(crate) struct Objects(Map); impl Default for Objects { fn default() -> Self { - let mut objects = BTreeMap::new(); + let mut objects = Map::new(); // Insert default authentication key let authentication_key_handle = @@ -137,38 +191,15 @@ impl Objects { self.0.remove(&Handle::new(object_id, object_type)) } - /// Serialize an object as ciphertext - pub fn wrap( + /// Encrypt and serialize an object as ciphertext + pub fn wrap_obj( &mut self, wrap_key_id: Id, object_id: Id, object_type: Type, - wrap_nonce: &wrap::Nonce, + nonce: &wrap::Nonce, ) -> Result, Error> { - let wrap_key = match self.get(wrap_key_id, Type::WrapKey) { - Some(k) => k, - None => fail!( - ErrorKind::ObjectNotFound, - "no such wrap key: {:?}", - wrap_key_id - ), - }; - - let sealing_key = match wrap_key.algorithm().wrap().unwrap() { - // TODO: actually use AES-CCM - wrap::Algorithm::Aes128Ccm => { - aead::UnboundKey::new(&AES_128_GCM, wrap_key.payload.as_ref()) - } - wrap::Algorithm::Aes256Ccm => { - aead::UnboundKey::new(&AES_256_GCM, wrap_key.payload.as_ref()) - } - unsupported => fail!( - ErrorKind::UnsupportedAlgorithm, - "unsupported wrap key algorithm: {:?}", - unsupported - ), - } - .unwrap(); + let wrap_key = self.get_wrap_key(wrap_key_id)?; let object_to_wrap = match self.get(object_id, object_type) { Some(o) => o, @@ -203,67 +234,29 @@ impl Objects { let mut wrapped_object = serialize(&WrappedObject { object_info, - data: object_to_wrap.payload.as_ref().into(), + data: object_to_wrap.payload.to_bytes(), }) .unwrap(); - let mut nonce = [0u8; 12]; - nonce.copy_from_slice(&wrap_nonce.as_ref()[..12]); - - aead::LessSafeKey::new(sealing_key) - .seal_in_place_append_tag( - aead::Nonce::assume_unique_for_key(nonce), - aead::Aad::from(b""), - &mut wrapped_object, - ) + wrap_key + .encrypt_in_place(nonce, b"", &mut wrapped_object) .unwrap(); Ok(wrapped_object) } /// Deserialize an encrypted object and insert it into the HSM - pub fn unwrap>>( + pub fn unwrap_obj>>( &mut self, wrap_key_id: Id, - wrap_nonce: &wrap::Nonce, + nonce: &wrap::Nonce, ciphertext: V, ) -> Result { - let opening_key = match self.get(wrap_key_id, Type::WrapKey) { - Some(k) => match k.algorithm().wrap().unwrap() { - wrap::Algorithm::Aes128Ccm => { - aead::UnboundKey::new(&AES_128_GCM, k.payload.as_ref()) - } - wrap::Algorithm::Aes256Ccm => { - aead::UnboundKey::new(&AES_256_GCM, k.payload.as_ref()) - } - unsupported => fail!( - ErrorKind::UnsupportedAlgorithm, - "unsupported wrap key algorithm: {:?}", - unsupported - ), - } - .unwrap(), - None => fail!( - ErrorKind::ObjectNotFound, - "no such wrap key: {:?}", - wrap_key_id - ), - }; - + let wrap_key = self.get_wrap_key(wrap_key_id)?; let mut wrapped_data: Vec = ciphertext.into(); + wrap_key.decrypt_in_place(nonce, b"", &mut wrapped_data)?; - let mut nonce = [0u8; 12]; - nonce.copy_from_slice(&wrap_nonce.as_ref()[..12]); - - let plaintext = aead::LessSafeKey::new(opening_key) - .open_in_place( - aead::Nonce::assume_unique_for_key(nonce), - aead::Aad::from(b""), - &mut wrapped_data, - ) - .map_err(|_| format_err!(ErrorKind::CryptoError, "error decrypting wrapped object!"))?; - - let unwrapped_object: WrappedObject = deserialize(plaintext).unwrap(); + let unwrapped_object: WrappedObject = deserialize(&wrapped_data).unwrap(); let payload = Payload::new( unwrapped_object.object_info.algorithm, @@ -289,7 +282,33 @@ impl Objects { pub fn iter(&self) -> Iter<'_> { self.0.iter() } + + /// Get a wrapping key + fn get_wrap_key(&self, wrap_key_id: Id) -> Result { + let wrap_key = match self.get(wrap_key_id, Type::WrapKey) { + Some(k) => k, + None => fail!( + ErrorKind::ObjectNotFound, + "no such wrap key: {:?}", + wrap_key_id + ), + }; + + match wrap_key.algorithm().wrap().unwrap() { + wrap::Algorithm::Aes128Ccm => Ok(AesCcmKey::Aes128Ccm( + Aes128Ccm::new_varkey(&wrap_key.payload.to_bytes()).unwrap(), + )), + wrap::Algorithm::Aes256Ccm => Ok(AesCcmKey::Aes256Ccm( + Aes256Ccm::new_varkey(&wrap_key.payload.to_bytes()).unwrap(), + )), + unsupported => fail!( + ErrorKind::UnsupportedAlgorithm, + "unsupported wrap key algorithm: {:?}", + unsupported + ), + } + } } /// Iterator over objects -pub(crate) type Iter<'a> = BTreeMapIter<'a, Handle, Object>; +pub(crate) type Iter<'a> = MapIter<'a, Handle, Object>; diff --git a/src/mockhsm/object/payload.rs b/src/mockhsm/object/payload.rs index 52c5841c..4fc6a562 100644 --- a/src/mockhsm/object/payload.rs +++ b/src/mockhsm/object/payload.rs @@ -1,24 +1,24 @@ //! Object "payloads" in the MockHsm are instances of software implementations //! of supported cryptographic primitives, already initialized with a private key -use ring::{ - rand::{SecureRandom, SystemRandom}, - signature::{Ed25519KeyPair, KeyPair}, -}; - use crate::{algorithm::Algorithm, asymmetric, authentication, hmac, opaque, wrap}; - -/// Size of an Ed25519 seed -pub(crate) const ED25519_SEED_SIZE: usize = 32; +use ed25519_dalek as ed25519; +use rand_core::{OsRng, RngCore}; /// Loaded instances of a cryptographic primitives in the MockHsm #[derive(Debug)] pub(crate) enum Payload { - /// Authentication keys + /// Authentication key AuthenticationKey(authentication::Key), - /// Ed25519 signing keys - Ed25519KeyPair([u8; ED25519_SEED_SIZE]), + /// ECDSA/P-256 signing key + EcdsaNistP256(p256::SecretKey), + + /// ECDSA/secp256k1 signing key, + EcdsaSecp256k1(k256::SecretKey), + + /// Ed25519 signing key + Ed25519Key(ed25519::SecretKey), /// HMAC key HmacKey(hmac::Algorithm, Vec), @@ -27,7 +27,6 @@ pub(crate) enum Payload { Opaque(opaque::Algorithm, Vec), /// Wrapping (i.e. symmetric encryption keys) - // TODO: actually simulate AES-CCM. Instead we use GCM because *ring* has it WrapKey(wrap::Algorithm, Vec), } @@ -36,12 +35,24 @@ impl Payload { pub fn new(algorithm: Algorithm, data: &[u8]) -> Self { match algorithm { Algorithm::Wrap(alg) => Payload::WrapKey(alg, data.into()), - Algorithm::Asymmetric(asymmetric::Algorithm::Ed25519) => { - assert_eq!(data.len(), ED25519_SEED_SIZE); - let mut bytes = [0u8; ED25519_SEED_SIZE]; - bytes.copy_from_slice(data); - Payload::Ed25519KeyPair(bytes) - } + Algorithm::Asymmetric(asymmetric_alg) => match asymmetric_alg { + asymmetric::Algorithm::EcP256 => { + assert_eq!(data.len(), 32); + Payload::EcdsaNistP256(p256::SecretKey::from_bytes(data).unwrap()) + } + asymmetric::Algorithm::EcK256 => { + assert_eq!(data.len(), 32); + Payload::EcdsaSecp256k1(k256::SecretKey::from_bytes(data).unwrap()) + } + asymmetric::Algorithm::Ed25519 => { + assert_eq!(data.len(), ed25519::SECRET_KEY_LENGTH); + Payload::Ed25519Key(ed25519::SecretKey::from_bytes(data).unwrap()) + } + _ => panic!( + "MockHsm doesn't support this asymmetric algorithm: {:?}", + asymmetric_alg + ), + }, Algorithm::Hmac(alg) => Payload::HmacKey(alg, data.into()), Algorithm::Opaque(alg) => Payload::Opaque(alg, data.into()), Algorithm::Authentication(_) => { @@ -53,19 +64,21 @@ impl Payload { /// Generate a new key with the given algorithm pub fn generate(algorithm: Algorithm) -> Self { - let csprng = SystemRandom::new(); - match algorithm { Algorithm::Wrap(wrap_alg) => { let mut bytes = vec![0u8; wrap_alg.key_len()]; - csprng.fill(&mut bytes).unwrap(); + OsRng.fill_bytes(&mut bytes); Payload::WrapKey(wrap_alg, bytes) } Algorithm::Asymmetric(asymmetric_alg) => match asymmetric_alg { + asymmetric::Algorithm::EcP256 => { + Payload::EcdsaNistP256(p256::SecretKey::random(&mut OsRng)) + } + asymmetric::Algorithm::EcK256 => { + Payload::EcdsaSecp256k1(k256::SecretKey::random(&mut OsRng)) + } asymmetric::Algorithm::Ed25519 => { - let mut bytes = [0u8; ED25519_SEED_SIZE]; - csprng.fill(&mut bytes).unwrap(); - Payload::Ed25519KeyPair(bytes) + Payload::Ed25519Key(ed25519::SecretKey::generate(&mut OsRng)) } _ => panic!( "MockHsm doesn't support this asymmetric algorithm: {:?}", @@ -74,7 +87,7 @@ impl Payload { }, Algorithm::Hmac(hmac_alg) => { let mut bytes = vec![0u8; hmac_alg.key_len()]; - csprng.fill(&mut bytes).unwrap(); + OsRng.fill_bytes(&mut bytes); Payload::HmacKey(hmac_alg, bytes) } _ => panic!( @@ -90,7 +103,9 @@ impl Payload { Payload::AuthenticationKey(_) => { Algorithm::Authentication(authentication::Algorithm::YubicoAes) } - Payload::Ed25519KeyPair(_) => Algorithm::Asymmetric(asymmetric::Algorithm::Ed25519), + Payload::EcdsaNistP256(_) => Algorithm::Asymmetric(asymmetric::Algorithm::EcP256), + Payload::EcdsaSecp256k1(_) => Algorithm::Asymmetric(asymmetric::Algorithm::EcK256), + Payload::Ed25519Key(_) => Algorithm::Asymmetric(asymmetric::Algorithm::Ed25519), Payload::HmacKey(alg, _) => alg.into(), Payload::Opaque(alg, _) => alg.into(), Payload::WrapKey(alg, _) => alg.into(), @@ -99,9 +114,10 @@ impl Payload { /// Get the length of the object pub fn len(&self) -> u16 { - let l = match *self { + let l = match self { Payload::AuthenticationKey(_) => authentication::key::SIZE, - Payload::Ed25519KeyPair(_) => ED25519_SEED_SIZE, + Payload::EcdsaNistP256(_) | Payload::EcdsaSecp256k1(_) => 32, + Payload::Ed25519Key(_) => ed25519::SECRET_KEY_LENGTH, Payload::HmacKey(_, ref data) => data.len(), Payload::Opaque(_, ref data) => data.len(), Payload::WrapKey(_, ref data) => data.len(), @@ -111,14 +127,16 @@ impl Payload { /// If this object is a public key, return its byte serialization pub fn public_key_bytes(&self) -> Option> { - match *self { - Payload::Ed25519KeyPair(ref k) => Some( - Ed25519KeyPair::from_seed_unchecked(k) - .unwrap() - .public_key() - .as_ref() - .into(), - ), + match self { + Payload::EcdsaNistP256(secret_key) => { + Some(p256::EncodedPoint::from_secret_key(secret_key, false).as_bytes()[1..].into()) + } + Payload::EcdsaSecp256k1(secret_key) => { + Some(k256::EncodedPoint::from_secret_key(secret_key, false).as_bytes()[1..].into()) + } + Payload::Ed25519Key(secret_key) => { + Some(ed25519::PublicKey::from(secret_key).as_ref().into()) + } _ => None, } } @@ -130,16 +148,17 @@ impl Payload { _ => None, } } -} -impl AsRef<[u8]> for Payload { - fn as_ref(&self) -> &[u8] { - match *self { - Payload::AuthenticationKey(ref k) => k.0.as_ref(), - Payload::Ed25519KeyPair(ref k) => k.as_ref(), - Payload::HmacKey(_, ref data) => data, - Payload::Opaque(_, ref data) => data, - Payload::WrapKey(_, ref data) => data, + /// Serialize this payload as a byte vector + pub fn to_bytes(&self) -> Vec { + match self { + Payload::AuthenticationKey(k) => k.0.as_ref().into(), + Payload::EcdsaNistP256(k) => k.to_bytes().to_vec(), + Payload::EcdsaSecp256k1(k) => k.to_bytes().to_vec(), + Payload::Ed25519Key(k) => k.as_ref().into(), + Payload::HmacKey(_, data) => data.clone(), + Payload::Opaque(_, data) => data.clone(), + Payload::WrapKey(_, data) => data.clone(), } } } diff --git a/src/session/securechannel/challenge.rs b/src/session/securechannel/challenge.rs index fc4e1e7d..dc03c4ee 100644 --- a/src/session/securechannel/challenge.rs +++ b/src/session/securechannel/challenge.rs @@ -1,6 +1,6 @@ //! Challenge messages used as part of SCP03's challenge/response protocol. -use getrandom::getrandom; +use rand_core::{OsRng, RngCore}; use serde::{Deserialize, Serialize}; /// Size of a challenge message @@ -14,7 +14,7 @@ impl Challenge { /// Create a new random `Challenge` pub fn new() -> Self { let mut challenge = [0u8; CHALLENGE_SIZE]; - getrandom(&mut challenge).expect("RNG failure!"); + OsRng.fill_bytes(&mut challenge); Challenge(challenge) } diff --git a/src/uuid.rs b/src/uuid.rs index ba1f53e2..c468afef 100644 --- a/src/uuid.rs +++ b/src/uuid.rs @@ -1,13 +1,13 @@ //! UUID functionality -use getrandom::getrandom; +use rand_core::{OsRng, RngCore}; pub use uuid::Uuid; use uuid::{Builder, Variant, Version}; /// Create a random UUID pub fn new_v4() -> Uuid { let mut bytes = [0; 16]; - getrandom(&mut bytes).expect("RNG failure!"); + OsRng.fill_bytes(&mut bytes); Builder::from_bytes(bytes) .set_variant(Variant::RFC4122) diff --git a/src/wrap/key.rs b/src/wrap/key.rs index aa921644..76af27cd 100644 --- a/src/wrap/key.rs +++ b/src/wrap/key.rs @@ -8,7 +8,7 @@ use crate::{client, device, object, wrap, Capability, Client, Domain}; use anomaly::fail; -use getrandom::getrandom; +use rand_core::{OsRng, RngCore}; use std::fmt::{self, Debug}; use zeroize::{Zeroize, Zeroizing}; @@ -29,7 +29,7 @@ impl Key { /// Generate a random wrap key with the given key size. pub fn generate_random(key_id: object::Id, algorithm: wrap::Algorithm) -> Self { let mut bytes = Zeroizing::new(vec![0u8; algorithm.key_len()]); - getrandom(&mut bytes).expect("RNG failure!"); + OsRng.fill_bytes(&mut bytes); Self::from_bytes(key_id, &bytes).unwrap() } diff --git a/src/wrap/nonce.rs b/src/wrap/nonce.rs index 8ab6ff10..3025098d 100644 --- a/src/wrap/nonce.rs +++ b/src/wrap/nonce.rs @@ -1,7 +1,7 @@ //! Nonces used by the YubiHSM 2's AES-CCM encrypted `wrap::Message` #[cfg(feature = "mockhsm")] -use getrandom::getrandom; +use rand_core::{OsRng, RngCore}; /// Number of bytes in a nonce used for "wrapping" (i.e AES-CCM encryption) pub const SIZE: usize = 13; @@ -15,7 +15,7 @@ impl Nonce { #[cfg(feature = "mockhsm")] pub fn generate() -> Self { let mut bytes = [0u8; SIZE]; - getrandom(&mut bytes).expect("RNG failure!"); + OsRng.fill_bytes(&mut bytes); Nonce(bytes) } } diff --git a/tests/command/sign_ecdsa.rs b/tests/command/sign_ecdsa.rs index 5f2b8d7d..12f4aab4 100644 --- a/tests/command/sign_ecdsa.rs +++ b/tests/command/sign_ecdsa.rs @@ -1,5 +1,10 @@ +//! ECDSA signing test + use crate::{generate_asymmetric_key, TEST_KEY_ID, TEST_MESSAGE}; -use ring::signature::UnparsedPublicKey; +use p256::{ + ecdsa::{signature::Verifier, Signature, VerifyKey}, + NistP256, +}; use sha2::{Digest, Sha256}; use yubihsm::{asymmetric, Capability}; @@ -14,24 +19,23 @@ fn generated_nistp256_key_test() { Capability::SIGN_ECDSA, ); - let pubkey_response = client + let raw_public_key = client .get_public_key(TEST_KEY_ID) .unwrap_or_else(|err| panic!("error getting public key: {}", err)); - assert_eq!(pubkey_response.algorithm, asymmetric::Algorithm::EcP256); - assert_eq!(pubkey_response.bytes.len(), 64); - - let mut pubkey = [0u8; 65]; - pubkey[0] = 0x04; // DER OCTET STRING tag - pubkey[1..].copy_from_slice(pubkey_response.bytes.as_slice()); + assert_eq!(raw_public_key.algorithm, asymmetric::Algorithm::EcP256); + assert_eq!(raw_public_key.bytes.len(), 64); - let test_digest = Vec::from(Sha256::digest(TEST_MESSAGE).as_slice()); + let public_key = raw_public_key.ecdsa::().unwrap(); + let test_digest = Sha256::digest(TEST_MESSAGE); - let signature = client - .sign_ecdsa(TEST_KEY_ID, test_digest) - .unwrap_or_else(|err| panic!("error performing ECDSA signature: {}", err)); + let signature = Signature::from_asn1( + &client + .sign_ecdsa_prehash_raw(TEST_KEY_ID, test_digest.as_slice()) + .unwrap_or_else(|err| panic!("error performing ECDSA signature: {}", err)), + ) + .unwrap(); - UnparsedPublicKey::new(&ring::signature::ECDSA_P256_SHA256_ASN1, &pubkey[..]) - .verify(TEST_MESSAGE, signature.as_ref()) - .unwrap(); + let verify_key = VerifyKey::from_encoded_point(&public_key).unwrap(); + assert!(verify_key.verify(TEST_MESSAGE, &signature).is_ok()); } diff --git a/tests/command/sign_eddsa.rs b/tests/command/sign_eddsa.rs index e6040c83..73f86c34 100644 --- a/tests/command/sign_eddsa.rs +++ b/tests/command/sign_eddsa.rs @@ -1,10 +1,10 @@ -//! Tests for producing Ed25519 signatures +//! Ed25519 signing test use crate::{ generate_asymmetric_key, put_asymmetric_key, test_vectors::ED25519_TEST_VECTORS, TEST_KEY_ID, TEST_MESSAGE, }; -use ring::signature::UnparsedPublicKey; +use ed25519_dalek::Verifier; use yubihsm::{asymmetric, Capability}; /// Test Ed25519 against RFC 8032 test vectors @@ -46,17 +46,20 @@ fn generated_key_test() { Capability::SIGN_EDDSA, ); - let pubkey = client + let public_key = client .get_public_key(TEST_KEY_ID) .unwrap_or_else(|err| panic!("error getting public key: {}", err)); - assert_eq!(pubkey.algorithm, asymmetric::Algorithm::Ed25519); + assert_eq!(public_key.algorithm, asymmetric::Algorithm::Ed25519); let signature = client .sign_ed25519(TEST_KEY_ID, TEST_MESSAGE) .unwrap_or_else(|err| panic!("error performing Ed25519 signature: {}", err)); - UnparsedPublicKey::new(&ring::signature::ED25519, &pubkey.bytes) - .verify(TEST_MESSAGE, signature.as_ref()) - .unwrap(); + assert!( + ed25519_dalek::PublicKey::from_bytes(public_key.ed25519().unwrap().as_bytes()) + .unwrap() + .verify(TEST_MESSAGE, &signature) + .is_ok() + ); } diff --git a/tests/ecdsa/mod.rs b/tests/ecdsa/mod.rs index ba256a15..d0946bde 100644 --- a/tests/ecdsa/mod.rs +++ b/tests/ecdsa/mod.rs @@ -1,26 +1,24 @@ -// TODO: cleanup tests -#![allow(dead_code, unused_imports)] +//! Elliptic Curve Digital Signature Algorithm (ECDSA) tests -#[cfg(all(feature = "secp256k1", not(feature = "mockhsm")))] -use signatory::ecdsa::curve::Secp256k1; -use signatory::{ - ecdsa::{ - curve::{CompressedPointSize, Curve, NistP256, NistP384, UncompressedPointSize}, - generic_array::{typenum::U1, ArrayLength}, - Asn1Signature, +use ::ecdsa::{ + elliptic_curve::{ + consts::U1, + generic_array::ArrayLength, + sec1::{UncompressedPointSize, UntaggedPointSize}, + weierstrass::{point, Curve}, }, - public_key::PublicKeyed, signature::Verifier, }; -#[cfg(all(feature = "secp256k1", not(feature = "mockhsm")))] -use signatory_secp256k1::EcdsaVerifier as Secp256k1Verifier; use std::ops::Add; use yubihsm::{ - asymmetric::{self, signature::Signer as _}, - ecdsa::{self, algorithm::CurveAlgorithm}, + asymmetric::signature::Signer as _, + ecdsa::{self, algorithm::CurveAlgorithm, NistP256}, object, Client, }; +#[cfg(feature = "secp256k1")] +use yubihsm::ecdsa::Secp256k1; + /// Domain IDs for test key const TEST_SIGNING_KEY_DOMAINS: yubihsm::Domain = yubihsm::Domain::DOM1; @@ -36,12 +34,11 @@ const TEST_MESSAGE: &[u8] = Digital Signature Algorithm (DSA) which uses elliptic curve cryptography."; /// Create the signer for this test -fn create_signer(key_id: object::Id) -> ecdsa::Signer +fn create_signer(key_id: object::Id) -> ecdsa::Signer where - C: Curve + CurveAlgorithm, - ::Output: Add + ArrayLength, - CompressedPointSize: ArrayLength, - UncompressedPointSize: ArrayLength, + C: Curve + CurveAlgorithm + point::Compression, + UntaggedPointSize: Add + ArrayLength, + UncompressedPointSize: ArrayLength, { let client = crate::get_hsm_client(); create_yubihsm_key(&client, key_id, C::asymmetric_algorithm()); @@ -66,33 +63,19 @@ fn create_yubihsm_key(client: &Client, key_id: object::Id, alg: yubihsm::asymmet .unwrap(); } -// Use *ring* to verify NIST P-256 ECDSA signatures #[test] -#[cfg(not(feature = "mockhsm"))] fn ecdsa_nistp256_sign_test() { let signer = create_signer::(201); - let signature: Asn1Signature<_> = signer.sign(TEST_MESSAGE); - let verifier = signatory_ring::ecdsa::p256::Verifier::from(&signer.public_key().unwrap()); - assert!(verifier.verify(TEST_MESSAGE, &signature).is_ok()); -} - -// Use *ring* to verify NIST P-384 ECDSA signatures -#[cfg(not(feature = "mockhsm"))] -#[test] -fn ecdsa_nistp384_sign_test() { - let signer = create_signer::(202); - let signature: Asn1Signature<_> = signer.sign(TEST_MESSAGE); - let verifier = signatory_ring::ecdsa::p384::Verifier::from(&signer.public_key().unwrap()); + let signature = signer.sign(TEST_MESSAGE); + let verifier = p256::ecdsa::VerifyKey::from_encoded_point(&signer.public_key()).unwrap(); assert!(verifier.verify(TEST_MESSAGE, &signature).is_ok()); } -// Use `secp256k1` crate to verify secp256k1 ECDSA signatures. -// The MockHSM does not presently support secp256k1 -#[cfg(all(feature = "secp256k1", not(feature = "mockhsm")))] +#[cfg(feature = "secp256k1")] #[test] fn ecdsa_secp256k1_sign_test() { let signer = create_signer::(203); - let signature: Asn1Signature<_> = signer.sign(TEST_MESSAGE); - let verifier = Secp256k1Verifier::from(&signer.public_key().unwrap()); + let signature = signer.sign(TEST_MESSAGE); + let verifier = k256::ecdsa::VerifyKey::from_encoded_point(&signer.public_key()).unwrap(); assert!(verifier.verify(TEST_MESSAGE, &signature).is_ok()); } diff --git a/tests/ed25519/mod.rs b/tests/ed25519/mod.rs index cd6102fe..304bf526 100644 --- a/tests/ed25519/mod.rs +++ b/tests/ed25519/mod.rs @@ -1,7 +1,6 @@ //! Ed25519 tests -use signatory::{public_key::PublicKeyed, signature::Verifier}; -use signatory_ring::ed25519::Verifier as Ed25519Verifier; +use ed25519_dalek::{PublicKey, Verifier}; use yubihsm::{asymmetric::signature::Signer as _, ed25519, Client}; /// Key ID to use for test key @@ -47,6 +46,6 @@ fn ed25519_sign_test() { let signer = ed25519::Signer::create(client.clone(), TEST_SIGNING_KEY_ID).unwrap(); let signature = signer.sign(TEST_MESSAGE); - let verifier = Ed25519Verifier::from(&signer.public_key().unwrap()); + let verifier = PublicKey::from_bytes(signer.public_key().as_bytes()).unwrap(); assert!(verifier.verify(TEST_MESSAGE, &signature).is_ok()); }