Compare commits
No commits in common. "ee258ac1154f20cd98a2ff6816ea78d5c308774e" and "8afcae54473a3c0790b340ba7ed5317e1eae04a0" have entirely different histories.
ee258ac115
...
8afcae5447
|
@ -32,21 +32,6 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android-tzdata"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "android_system_properties"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
|
@ -233,9 +218,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "buffered-reader"
|
name = "buffered-reader"
|
||||||
version = "1.3.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b9b0a25eb06e83579bc985d836e1e3b957a7201301b48538764d2b2e78090d4"
|
checksum = "66d3bea5bcc3ecc38fe5388e6bc35e6fe7bd665eb3ae9a44283e15b91ad3867d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -249,12 +234,6 @@ version = "3.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "byteorder"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -282,27 +261,6 @@ dependencies = [
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "card-backend"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bd3ee3a298842065dc489180c34a4fe4bbbb8643bb422009d79558a099fb42e5"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "card-backend-pcsc"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "68bb0b707b1b6b058ed93abd70ef65703ed6fd4150d32a0d735b78cfa61cbb35"
|
|
||||||
dependencies = [
|
|
||||||
"card-backend",
|
|
||||||
"iso7816-tlv",
|
|
||||||
"log",
|
|
||||||
"pcsc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.83"
|
version = "1.0.83"
|
||||||
|
@ -333,12 +291,9 @@ version = "0.4.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
|
||||||
"iana-time-zone",
|
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets 0.48.5",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -417,12 +372,6 @@ version = "0.9.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "core-foundation-sys"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cpufeatures"
|
name = "cpufeatures"
|
||||||
version = "0.2.9"
|
version = "0.2.9"
|
||||||
|
@ -478,7 +427,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"curve25519-dalek-derive",
|
"curve25519-dalek-derive",
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
"fiat-crypto",
|
"fiat-crypto",
|
||||||
"platforms",
|
"platforms",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
|
@ -497,17 +446,6 @@ dependencies = [
|
||||||
"syn 2.0.29",
|
"syn 2.0.29",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "der"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
|
|
||||||
dependencies = [
|
|
||||||
"const-oid",
|
|
||||||
"pem-rfc7468",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
|
@ -524,6 +462,15 @@ version = "0.1.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
@ -531,7 +478,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer",
|
||||||
"const-oid",
|
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
@ -569,10 +515,19 @@ version = "0.16.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4"
|
checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"der 0.7.8",
|
"der",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"signature",
|
"signature 2.1.0",
|
||||||
"spki 0.7.2",
|
"spki",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ed25519"
|
||||||
|
version = "1.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7"
|
||||||
|
dependencies = [
|
||||||
|
"signature 1.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -581,8 +536,8 @@ version = "2.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
|
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkcs8 0.10.2",
|
"pkcs8",
|
||||||
"signature",
|
"signature 2.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -592,7 +547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
|
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"ed25519",
|
"ed25519 2.2.2",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
@ -612,11 +567,11 @@ checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct",
|
"base16ct",
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
"ff",
|
"ff",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"group",
|
"group",
|
||||||
"pkcs8 0.10.2",
|
"pkcs8",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"sec1",
|
"sec1",
|
||||||
"subtle",
|
"subtle",
|
||||||
|
@ -744,6 +699,19 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.1.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
|
@ -753,7 +721,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -819,58 +787,20 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
|
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hex-slice"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5491a308e0214554f07a81d8944abe45f552871c12e3c3c6e7e5d354039a6c4c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "home"
|
|
||||||
version = "0.5.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-sys 0.48.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone"
|
|
||||||
version = "0.1.58"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20"
|
|
||||||
dependencies = [
|
|
||||||
"android_system_properties",
|
|
||||||
"core-foundation-sys",
|
|
||||||
"iana-time-zone-haiku",
|
|
||||||
"js-sys",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"windows-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iana-time-zone-haiku"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.4.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
|
@ -921,15 +851,6 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iso7816-tlv"
|
|
||||||
version = "0.4.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d47365efc3b4c252f8a3384445c0f7e8a4e0ae5c22bf3bedd2dd16f9bb45016a"
|
|
||||||
dependencies = [
|
|
||||||
"untrusted",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -1011,7 +932,7 @@ dependencies = [
|
||||||
name = "keyfork-derive-util"
|
name = "keyfork-derive-util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"hmac",
|
"hmac",
|
||||||
|
@ -1047,18 +968,6 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "keyfork-pinentry"
|
|
||||||
version = "0.5.0"
|
|
||||||
dependencies = [
|
|
||||||
"nom",
|
|
||||||
"percent-encoding",
|
|
||||||
"secrecy",
|
|
||||||
"thiserror",
|
|
||||||
"which",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keyfork-plumbing"
|
name = "keyfork-plumbing"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1073,16 +982,11 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
"card-backend",
|
|
||||||
"card-backend-pcsc",
|
|
||||||
"keyfork-derive-openpgp",
|
"keyfork-derive-openpgp",
|
||||||
"keyfork-pinentry",
|
|
||||||
"openpgp-card-sequoia",
|
|
||||||
"sequoia-openpgp",
|
"sequoia-openpgp",
|
||||||
"serde",
|
"serde",
|
||||||
"sharks",
|
"sharks",
|
||||||
"smex",
|
"smex",
|
||||||
"thiserror",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1103,7 +1007,6 @@ dependencies = [
|
||||||
"keyfork-frame",
|
"keyfork-frame",
|
||||||
"keyfork-mnemonic-util",
|
"keyfork-mnemonic-util",
|
||||||
"keyfork-slip10-test-data",
|
"keyfork-slip10-test-data",
|
||||||
"keyforkd-models",
|
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1122,26 +1025,16 @@ dependencies = [
|
||||||
"keyfork-frame",
|
"keyfork-frame",
|
||||||
"keyfork-slip10-test-data",
|
"keyfork-slip10-test-data",
|
||||||
"keyforkd",
|
"keyforkd",
|
||||||
"keyforkd-models",
|
|
||||||
"tempdir",
|
"tempdir",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "keyforkd-models"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"keyfork-derive-util",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop"
|
name = "lalrpop"
|
||||||
version = "0.20.0"
|
version = "0.19.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
|
checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ascii-canvas",
|
"ascii-canvas",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
@ -1152,7 +1045,7 @@ dependencies = [
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.7.4",
|
"regex-syntax 0.6.29",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
"term",
|
"term",
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
|
@ -1161,18 +1054,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop-util"
|
name = "lalrpop-util"
|
||||||
version = "0.20.0"
|
version = "0.19.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
|
checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
dependencies = [
|
|
||||||
"spin",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazycell"
|
name = "lazycell"
|
||||||
|
@ -1196,12 +1086,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "libm"
|
|
||||||
version = "0.2.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linked-hash-map"
|
name = "linked-hash-map"
|
||||||
version = "0.5.6"
|
version = "0.5.6"
|
||||||
|
@ -1279,7 +1163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1289,7 +1173,7 @@ version = "7.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b9fdccf3eae7b161910d2daa2f0155ca35041322e8fe5c5f1f2c9d0b12356336"
|
checksum = "b9fdccf3eae7b161910d2daa2f0155ca35041322e8fe5c5f1f2c9d0b12356336"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.10",
|
||||||
"libc",
|
"libc",
|
||||||
"nettle-sys",
|
"nettle-sys",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -1336,44 +1220,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-bigint-dig"
|
|
||||||
version = "0.8.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"lazy_static",
|
|
||||||
"libm",
|
|
||||||
"num-integer",
|
|
||||||
"num-iter",
|
|
||||||
"num-traits",
|
|
||||||
"rand 0.8.5",
|
|
||||||
"smallvec",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-integer"
|
|
||||||
version = "0.1.45"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num-iter"
|
|
||||||
version = "0.1.43"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
|
|
||||||
dependencies = [
|
|
||||||
"autocfg",
|
|
||||||
"num-integer",
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -1381,7 +1227,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"libm",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1409,36 +1254,6 @@ version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openpgp-card"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7ba6b39b46a9deba985be9cc960709e284806b550d7e1aff915f8be4b06c3640"
|
|
||||||
dependencies = [
|
|
||||||
"card-backend",
|
|
||||||
"chrono",
|
|
||||||
"hex-slice",
|
|
||||||
"log",
|
|
||||||
"nom",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openpgp-card-sequoia"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7637080b15379df16fef0f81fd2664d403366b7514c721f2231c8974778017c3"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"card-backend",
|
|
||||||
"chrono",
|
|
||||||
"log",
|
|
||||||
"openpgp-card",
|
|
||||||
"rsa",
|
|
||||||
"sequoia-openpgp",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -1474,50 +1289,16 @@ version = "0.12.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
"hmac",
|
"hmac",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pcsc"
|
|
||||||
version = "2.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "37cab0be9d04e808a8d8059fa54befcd71dc8b168f9f0c04bdb7e59832abbab4"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 1.3.2",
|
|
||||||
"pcsc-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pcsc-sys"
|
|
||||||
version = "1.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e1b7bfecba2c0f1b5efb0e7caf7533ab1c295024165bcbb066231f60d33e23ea"
|
|
||||||
dependencies = [
|
|
||||||
"pkg-config",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peeking_take_while"
|
name = "peeking_take_while"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pem-rfc7468"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac"
|
|
||||||
dependencies = [
|
|
||||||
"base64ct",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "percent-encoding"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "petgraph"
|
name = "petgraph"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
@ -1569,36 +1350,14 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkcs1"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719"
|
|
||||||
dependencies = [
|
|
||||||
"der 0.6.1",
|
|
||||||
"pkcs8 0.9.0",
|
|
||||||
"spki 0.6.0",
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkcs8"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
|
|
||||||
dependencies = [
|
|
||||||
"der 0.6.1",
|
|
||||||
"spki 0.6.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkcs8"
|
name = "pkcs8"
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"der 0.7.8",
|
"der",
|
||||||
"spki 0.7.2",
|
"spki",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1656,6 +1415,19 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.1.16",
|
||||||
|
"libc",
|
||||||
|
"rand_chacha 0.2.2",
|
||||||
|
"rand_core 0.5.1",
|
||||||
|
"rand_hc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
|
@ -1663,10 +1435,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha",
|
"rand_chacha 0.3.1",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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 0.5.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_chacha"
|
name = "rand_chacha"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -1692,13 +1474,31 @@ version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.1.16",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.10",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_hc"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1734,7 +1534,7 @@ version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.2.10",
|
||||||
"redox_syscall 0.2.16",
|
"redox_syscall 0.2.16",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -1783,12 +1583,6 @@ version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "regex-syntax"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "remove_dir_all"
|
name = "remove_dir_all"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
@ -1804,27 +1598,7 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
|
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rsa"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55a77d189da1fee555ad95b7e50e7457d91c0e089ec68ca69ad2989413bbdab4"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"digest",
|
|
||||||
"num-bigint-dig",
|
|
||||||
"num-integer",
|
|
||||||
"num-iter",
|
|
||||||
"num-traits",
|
|
||||||
"pkcs1",
|
|
||||||
"pkcs8 0.9.0",
|
|
||||||
"rand_core 0.6.4",
|
|
||||||
"signature",
|
|
||||||
"subtle",
|
|
||||||
"zeroize",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1900,22 +1674,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct",
|
"base16ct",
|
||||||
"der 0.7.8",
|
"der",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"pkcs8 0.10.2",
|
"pkcs8",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "secrecy"
|
|
||||||
version = "0.8.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e"
|
|
||||||
dependencies = [
|
|
||||||
"zeroize",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -1924,9 +1689,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sequoia-openpgp"
|
name = "sequoia-openpgp"
|
||||||
version = "1.17.0"
|
version = "1.16.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2ea026cf8a70d331c742e3ad7e68fd405d0743ff86630fb4334a1bf8d0e194c7"
|
checksum = "a16854c0f6297de6db4df195e28324dfbc2429802f0e48cd04007db8e3049709"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
@ -1934,9 +1699,9 @@ dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"ed25519",
|
"ed25519 1.5.3",
|
||||||
"flate2",
|
"flate2",
|
||||||
"getrandom",
|
"getrandom 0.2.10",
|
||||||
"idna",
|
"idna",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
|
@ -1945,9 +1710,9 @@ dependencies = [
|
||||||
"memsec",
|
"memsec",
|
||||||
"nettle",
|
"nettle",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand 0.8.5",
|
"rand 0.7.3",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.8.2",
|
"regex-syntax 0.6.29",
|
||||||
"sha1collisiondetection",
|
"sha1collisiondetection",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"xxhash-rust",
|
"xxhash-rust",
|
||||||
|
@ -1955,18 +1720,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.190"
|
version = "1.0.188"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7"
|
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.190"
|
version = "1.0.188"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3"
|
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1986,11 +1751,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1collisiondetection"
|
name = "sha1collisiondetection"
|
||||||
version = "0.3.2"
|
version = "0.2.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "31c0b86a052106b16741199985c9ec2bf501f619f70c48fa479b44b093ad9a68"
|
checksum = "b20793cf8330b2c7da4c438116660fed24e380bcb8a1bcfff2581b5593a0b38e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
"digest 0.9.0",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2002,7 +1767,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"digest",
|
"digest 0.10.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2040,13 +1805,18 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signature"
|
||||||
|
version = "1.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
|
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest",
|
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2082,22 +1852,6 @@ dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spin"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "spki"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
|
|
||||||
dependencies = [
|
|
||||||
"base64ct",
|
|
||||||
"der 0.6.1",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spki"
|
name = "spki"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
|
@ -2105,7 +1859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
|
checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64ct",
|
"base64ct",
|
||||||
"der 0.7.8",
|
"der",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2201,18 +1955,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.50"
|
version = "1.0.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.50"
|
version = "1.0.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2417,12 +2171,6 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "untrusted"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -2447,6 +2195,12 @@ version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.11.0+wasi-snapshot-preview1"
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
@ -2507,18 +2261,6 @@ version = "0.2.87"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "which"
|
|
||||||
version = "4.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
"home",
|
|
||||||
"once_cell",
|
|
||||||
"rustix 0.38.13",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -2541,15 +2283,6 @@ version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-core"
|
|
||||||
version = "0.51.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.48.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.45.0"
|
version = "0.45.0"
|
||||||
|
|
|
@ -9,12 +9,10 @@ members = [
|
||||||
"keyfork-derive-util",
|
"keyfork-derive-util",
|
||||||
"keyfork-frame",
|
"keyfork-frame",
|
||||||
"keyfork-mnemonic-util",
|
"keyfork-mnemonic-util",
|
||||||
"keyfork-pinentry",
|
|
||||||
"keyfork-plumbing",
|
"keyfork-plumbing",
|
||||||
"keyfork-shard",
|
"keyfork-shard",
|
||||||
"keyfork-slip10-test-data",
|
"keyfork-slip10-test-data",
|
||||||
"keyforkd",
|
"keyforkd",
|
||||||
"keyforkd-client",
|
"keyforkd-client",
|
||||||
"keyforkd-models",
|
|
||||||
"smex",
|
"smex",
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{env, process::ExitCode, str::FromStr};
|
use std::{env, process::ExitCode, str::FromStr};
|
||||||
|
|
||||||
use keyfork_derive_util::{
|
use keyfork_derive_util::{
|
||||||
request::{DerivationAlgorithm, DerivationError, DerivationRequest, DerivationResponse},
|
request::{DerivationAlgorithm, DerivationError, DerivationRequest},
|
||||||
DerivationPath,
|
DerivationPath,
|
||||||
};
|
};
|
||||||
use keyforkd_client::Client;
|
use keyforkd_client::Client;
|
||||||
|
@ -38,8 +38,8 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
let mut client = Client::discover_socket()?;
|
let mut client = Client::discover_socket()?;
|
||||||
let request = DerivationRequest::new(algo, &path);
|
let request = DerivationRequest::new(algo, &path);
|
||||||
let response = client.request(&request.into())?;
|
let response = client.request(&request)?;
|
||||||
println!("{}", smex::encode(&DerivationResponse::try_from(response)?.data));
|
println!("{}", smex::encode(&response.data));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{env, process::ExitCode, str::FromStr};
|
use std::{env, str::FromStr, process::ExitCode};
|
||||||
|
|
||||||
use keyfork_derive_util::{
|
use keyfork_derive_util::{
|
||||||
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
|
request::{DerivationAlgorithm, DerivationRequest},
|
||||||
DerivationIndex, DerivationPath,
|
DerivationIndex, DerivationPath,
|
||||||
};
|
};
|
||||||
use keyforkd_client::Client;
|
use keyforkd_client::Client;
|
||||||
|
@ -107,9 +107,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
};
|
};
|
||||||
|
|
||||||
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
let request = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
||||||
let derived_data: DerivationResponse = Client::discover_socket()?
|
let derived_data = Client::discover_socket()?.request(&request)?;
|
||||||
.request(&request.into())?
|
|
||||||
.try_into()?;
|
|
||||||
let subkeys = subkey_format
|
let subkeys = subkey_format
|
||||||
.iter()
|
.iter()
|
||||||
.map(|kt| kt.inner().clone())
|
.map(|kt| kt.inner().clone())
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{DerivationPath, DerivationIndex};
|
||||||
|
|
||||||
pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true);
|
pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{DerivationIndex, DerivationPath, ExtendedPublicKey, PrivateKey, PublicKey};
|
use crate::{DerivationIndex, DerivationPath, PrivateKey, PublicKey, ExtendedPublicKey};
|
||||||
|
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -122,14 +122,11 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn add_vec() -> Result<(), Box<dyn std::error::Error>> {
|
fn add_vec() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let path = DerivationPath::from_str("m")?;
|
let path = DerivationPath::from_str("m")?;
|
||||||
let other_path = [
|
let other_path = [DerivationIndex::new(72, true)?, DerivationIndex::new(47, false)?, DerivationIndex::new((i32::MAX) as u32, false)?];
|
||||||
DerivationIndex::new(72, true)?,
|
|
||||||
DerivationIndex::new(47, false)?,
|
|
||||||
DerivationIndex::new((i32::MAX) as u32, false)?,
|
|
||||||
];
|
|
||||||
let path = path + &other_path[..];
|
let path = path + &other_path[..];
|
||||||
assert_eq!(path, DerivationPath::from_str("m/72'/47/2147483647")?);
|
assert_eq!(path, DerivationPath::from_str("m/72'/47/2147483647")?);
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,7 @@ pub trait PublicKey: Sized {
|
||||||
let hash = Sha256::new().chain_update(self.to_bytes()).finalize();
|
let hash = Sha256::new().chain_update(self.to_bytes()).finalize();
|
||||||
let hash = Ripemd160::new().chain_update(hash).finalize();
|
let hash = Ripemd160::new().chain_update(hash).finalize();
|
||||||
// Note: Safety assured by type returned from Ripemd160
|
// Note: Safety assured by type returned from Ripemd160
|
||||||
hash[..4]
|
hash[..4].try_into().expect("Ripemd160 returned too little data")
|
||||||
.try_into()
|
|
||||||
.expect("Ripemd160 returned too little data")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,10 @@ fn secp256k1() {
|
||||||
fn ed25519() {
|
fn ed25519() {
|
||||||
use ed25519_dalek::SigningKey;
|
use ed25519_dalek::SigningKey;
|
||||||
|
|
||||||
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
let tests = test_data()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&"ed25519".to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for per_seed in tests {
|
for per_seed in tests {
|
||||||
let seed = &per_seed.seed;
|
let seed = &per_seed.seed;
|
||||||
|
@ -107,8 +110,7 @@ fn panics_with_unhardened_derivation() {
|
||||||
|
|
||||||
let seed = hex!("000102030405060708090a0b0c0d0e0f");
|
let seed = hex!("000102030405060708090a0b0c0d0e0f");
|
||||||
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
let xkey = ExtendedPrivateKey::<SigningKey>::new(seed).unwrap();
|
||||||
xkey.derive_path(&DerivationPath::from_str("m/0").unwrap())
|
xkey.derive_path(&DerivationPath::from_str("m/0").unwrap()).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ed25519")]
|
#[cfg(feature = "ed25519")]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::{collections::HashMap, error::Error, fmt::Display, str::FromStr, sync::Arc};
|
use std::{collections::HashMap, str::FromStr, sync::Arc, error::Error, fmt::Display};
|
||||||
|
|
||||||
use hmac::Hmac;
|
|
||||||
use pbkdf2::pbkdf2;
|
|
||||||
use sha2::{Digest, Sha256, Sha512};
|
use sha2::{Digest, Sha256, Sha512};
|
||||||
|
use pbkdf2::pbkdf2;
|
||||||
|
use hmac::Hmac;
|
||||||
|
|
||||||
/// The error type representing a failure to create a [`Mnemonic`]. These errors only occur during
|
/// The error type representing a failure to create a [`Mnemonic`]. These errors only occur during
|
||||||
/// [`Mnemonic`] creation.
|
/// [`Mnemonic`] creation.
|
||||||
|
@ -30,7 +30,7 @@ impl Display for MnemonicGenerationError {
|
||||||
}
|
}
|
||||||
MnemonicGenerationError::InvalidPbkdf2Length => {
|
MnemonicGenerationError::InvalidPbkdf2Length => {
|
||||||
f.write_str("Invalid length from PBKDF2")
|
f.write_str("Invalid length from PBKDF2")
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ impl Mnemonic {
|
||||||
bits[index * 11 + bit] = (word & (1 << (10 - bit))) > 0;
|
bits[index * 11 + bit] = (word & (1 << (10 - bit))) > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove checksum bits
|
// remove checksum bits
|
||||||
bits.truncate(bits.len() - bits.len() % 32);
|
bits.truncate(bits.len() - bits.len() % 32);
|
||||||
|
|
||||||
|
@ -222,10 +222,7 @@ impl Mnemonic {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seed<'a>(
|
pub fn seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> Result<Vec<u8>, MnemonicGenerationError> {
|
||||||
&self,
|
|
||||||
passphrase: impl Into<Option<&'a str>>,
|
|
||||||
) -> Result<Vec<u8>, MnemonicGenerationError> {
|
|
||||||
let passphrase = passphrase.into();
|
let passphrase = passphrase.into();
|
||||||
|
|
||||||
let mut seed = [0u8; 64];
|
let mut seed = [0u8; 64];
|
||||||
|
@ -296,14 +293,8 @@ mod tests {
|
||||||
let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap();
|
let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap();
|
||||||
assert_eq!(my_mnemonic.to_string(), their_mnemonic.to_string());
|
assert_eq!(my_mnemonic.to_string(), their_mnemonic.to_string());
|
||||||
assert_eq!(my_mnemonic.seed(None).unwrap(), their_mnemonic.to_seed(""));
|
assert_eq!(my_mnemonic.seed(None).unwrap(), their_mnemonic.to_seed(""));
|
||||||
assert_eq!(
|
assert_eq!(my_mnemonic.seed("testing").unwrap(), their_mnemonic.to_seed("testing"));
|
||||||
my_mnemonic.seed("testing").unwrap(),
|
assert_ne!(my_mnemonic.seed("test1").unwrap(), their_mnemonic.to_seed("test2"));
|
||||||
their_mnemonic.to_seed("testing")
|
|
||||||
);
|
|
||||||
assert_ne!(
|
|
||||||
my_mnemonic.seed("test1").unwrap(),
|
|
||||||
their_mnemonic.to_seed("test2")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
[package]
|
|
||||||
# name = "pinentry"
|
|
||||||
name = "keyfork-pinentry"
|
|
||||||
description = "API for interacting with pinentry binaries"
|
|
||||||
version = "0.5.0"
|
|
||||||
# authors = ["Jack Grigg <thestr4d@gmail.com>"]
|
|
||||||
authors = ["Ryan Heywood <ryan@distrust.co>"]
|
|
||||||
# repository = "https://github.com/str4d/pinentry-rs"
|
|
||||||
# readme = "README.md"
|
|
||||||
# keywords = ["passphrase", "password"]
|
|
||||||
# categories = ["api-bindings", "command-line-interface"]
|
|
||||||
# license = "MIT OR Apache-2.0"
|
|
||||||
license = "MIT"
|
|
||||||
# edition = "2018"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
nom = { version = "7", default-features = false }
|
|
||||||
percent-encoding = "2.1"
|
|
||||||
secrecy = "0.8"
|
|
||||||
thiserror = "1.0.50"
|
|
||||||
which = { version = "4", default-features = false }
|
|
||||||
zeroize = "1"
|
|
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2020 Jack Grigg
|
|
||||||
Copyright (c) 2023 Distrust
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,225 +0,0 @@
|
||||||
use percent_encoding::percent_decode_str;
|
|
||||||
use secrecy::{ExposeSecret, SecretString};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::io::{self, BufRead, BufReader, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
use std::process::{ChildStdin, ChildStdout};
|
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use zeroize::Zeroize;
|
|
||||||
|
|
||||||
use crate::{Error, Result};
|
|
||||||
|
|
||||||
/// Possible response lines from an Assuan server.
|
|
||||||
///
|
|
||||||
/// Reference: https://gnupg.org/documentation/manuals/assuan/Server-responses.html
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum Response {
|
|
||||||
/// Request was successful.
|
|
||||||
Ok(Option<String>),
|
|
||||||
/// Request could not be fulfilled. The possible error codes are defined by
|
|
||||||
/// `libgpg-error`.
|
|
||||||
Err {
|
|
||||||
code: u16,
|
|
||||||
description: Option<String>,
|
|
||||||
},
|
|
||||||
/// Informational output by the server, which is still processing the request.
|
|
||||||
Information {
|
|
||||||
keyword: String,
|
|
||||||
status: Option<String>,
|
|
||||||
},
|
|
||||||
/// Comment line issued only for debugging purposes.
|
|
||||||
Comment(String),
|
|
||||||
/// Raw data returned to client.
|
|
||||||
DataLine(SecretString),
|
|
||||||
/// The server needs further information from the client.
|
|
||||||
Inquire {
|
|
||||||
keyword: String,
|
|
||||||
parameters: Option<String>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Connection {
|
|
||||||
output: ChildStdin,
|
|
||||||
input: BufReader<ChildStdout>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
pub fn open(name: &Path) -> Result<Self> {
|
|
||||||
let process = Command::new(name)
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.stdout(Stdio::piped())
|
|
||||||
.spawn()?;
|
|
||||||
let output = process.stdin.expect("could open stdin");
|
|
||||||
let input = BufReader::new(process.stdout.expect("could open stdin"));
|
|
||||||
|
|
||||||
let mut conn = Connection { output, input };
|
|
||||||
// There is always an initial OK server response
|
|
||||||
conn.read_response()?;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
conn.send_request("OPTION", Some("ttyname=/dev/tty"))?;
|
|
||||||
conn.send_request(
|
|
||||||
"OPTION",
|
|
||||||
Some(&format!(
|
|
||||||
"ttytype={}",
|
|
||||||
std::env::var("TERM")
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.unwrap_or("xterm-256color")
|
|
||||||
)),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_request(
|
|
||||||
&mut self,
|
|
||||||
command: &str,
|
|
||||||
parameters: Option<&str>,
|
|
||||||
) -> Result<Option<SecretString>> {
|
|
||||||
self.output.write_all(command.as_bytes())?;
|
|
||||||
if let Some(p) = parameters {
|
|
||||||
self.output.write_all(b" ")?;
|
|
||||||
self.output.write_all(p.as_bytes())?;
|
|
||||||
}
|
|
||||||
self.output.write_all(b"\n")?;
|
|
||||||
self.read_response()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_response(&mut self) -> Result<Option<SecretString>> {
|
|
||||||
let mut line = String::new();
|
|
||||||
let mut data = None;
|
|
||||||
|
|
||||||
// We loop until we find an OK or ERR response. This is probably sufficient for
|
|
||||||
// pinentry, but other Assuan protocols might rely on INQUIRE, which needs
|
|
||||||
// intermediate completion states or callbacks.
|
|
||||||
loop {
|
|
||||||
line.zeroize();
|
|
||||||
self.input.read_line(&mut line)?;
|
|
||||||
match read::server_response(&line)
|
|
||||||
.map(|(_, r)| r)
|
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, format!("{}", e)))?
|
|
||||||
{
|
|
||||||
Response::Ok(_info) => {
|
|
||||||
/*
|
|
||||||
if let Some(info) = info {
|
|
||||||
debug!("< OK {}", info);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
line.zeroize();
|
|
||||||
return Ok(data.map(SecretString::new));
|
|
||||||
}
|
|
||||||
Response::Err { code, description } => {
|
|
||||||
line.zeroize();
|
|
||||||
if let Some(mut buf) = data {
|
|
||||||
buf.zeroize();
|
|
||||||
}
|
|
||||||
return Err(Error::from_parts(code, description));
|
|
||||||
}
|
|
||||||
Response::Comment(_comment) => {
|
|
||||||
// debug!("< # {}", comment)
|
|
||||||
}
|
|
||||||
Response::DataLine(data_line) => {
|
|
||||||
let buf = data.take();
|
|
||||||
let data_line_decoded =
|
|
||||||
percent_decode_str(data_line.expose_secret()).decode_utf8()?;
|
|
||||||
data = Some(buf.unwrap_or_else(String::new) + &data_line_decoded);
|
|
||||||
if let Cow::Owned(mut data_line_decoded) = data_line_decoded {
|
|
||||||
data_line_decoded.zeroize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_res => {
|
|
||||||
// info!("< {:?}", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Connection {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let _ = self.send_request("BYE", None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod read {
|
|
||||||
use nom::{
|
|
||||||
branch::alt,
|
|
||||||
bytes::complete::{is_not, tag},
|
|
||||||
character::complete::{digit1, line_ending},
|
|
||||||
combinator::{map, opt},
|
|
||||||
sequence::{pair, preceded, terminated},
|
|
||||||
IResult,
|
|
||||||
};
|
|
||||||
use secrecy::SecretString;
|
|
||||||
|
|
||||||
use super::Response;
|
|
||||||
|
|
||||||
fn gpg_error_code(input: &str) -> IResult<&str, u16> {
|
|
||||||
map(digit1, |code| {
|
|
||||||
#[allow(clippy::from_str_radix_10)]
|
|
||||||
let full = u32::from_str_radix(code, 10).expect("have decimal digits");
|
|
||||||
// gpg uses the lowest 16 bits for error codes.
|
|
||||||
full as u16
|
|
||||||
})(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn server_response(input: &str) -> IResult<&str, Response> {
|
|
||||||
terminated(
|
|
||||||
alt((
|
|
||||||
preceded(
|
|
||||||
tag("OK"),
|
|
||||||
map(opt(preceded(tag(" "), is_not("\r\n"))), |params| {
|
|
||||||
Response::Ok(params.map(String::from))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
preceded(
|
|
||||||
tag("ERR "),
|
|
||||||
map(
|
|
||||||
pair(gpg_error_code, opt(preceded(tag(" "), is_not("\r\n")))),
|
|
||||||
|(code, description)| Response::Err {
|
|
||||||
code,
|
|
||||||
description: description.map(String::from),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
preceded(
|
|
||||||
tag("S "),
|
|
||||||
map(
|
|
||||||
pair(is_not(" \r\n"), opt(preceded(tag(" "), is_not("\r\n")))),
|
|
||||||
|(keyword, status): (&str, _)| Response::Information {
|
|
||||||
keyword: keyword.to_owned(),
|
|
||||||
status: status.map(String::from),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
preceded(
|
|
||||||
tag("# "),
|
|
||||||
map(is_not("\r\n"), |comment: &str| {
|
|
||||||
Response::Comment(comment.to_owned())
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
preceded(
|
|
||||||
tag("D "),
|
|
||||||
map(is_not("\r\n"), |data: &str| {
|
|
||||||
Response::DataLine(SecretString::new(data.to_owned()))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
preceded(
|
|
||||||
tag("INQUIRE "),
|
|
||||||
map(
|
|
||||||
pair(is_not(" \r\n"), opt(preceded(tag(" "), is_not("\r\n")))),
|
|
||||||
|(keyword, parameters): (&str, _)| Response::Inquire {
|
|
||||||
keyword: keyword.to_owned(),
|
|
||||||
parameters: parameters.map(String::from),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
line_ending,
|
|
||||||
)(input)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
use std::{fmt, io};
|
|
||||||
|
|
||||||
pub(crate) const GPG_ERR_TIMEOUT: u16 = 62;
|
|
||||||
pub(crate) const GPG_ERR_CANCELED: u16 = 99;
|
|
||||||
pub(crate) const GPG_ERR_NOT_CONFIRMED: u16 = 114;
|
|
||||||
|
|
||||||
/// An uncommon or unexpected GPG error.
|
|
||||||
///
|
|
||||||
/// `pinentry` is built on top of Assuan, which inherits all of GPG's error codes. Only
|
|
||||||
/// some of these error codes are actually used by the common `pinentry` implementations,
|
|
||||||
/// but it's possible to receive any of them.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct GpgError {
|
|
||||||
/// The GPG error code.
|
|
||||||
///
|
|
||||||
/// See <https://github.com/gpg/libgpg-error/blob/master/src/err-codes.h.in> for the
|
|
||||||
/// mapping from error code to GPG error type.
|
|
||||||
code: u16,
|
|
||||||
|
|
||||||
/// A description of the error, if available.
|
|
||||||
///
|
|
||||||
/// See <https://github.com/gpg/libgpg-error/blob/master/src/err-codes.h.in> for the
|
|
||||||
/// likely descriptions.
|
|
||||||
description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for GpgError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "Code {}", self.code)?;
|
|
||||||
if let Some(desc) = &self.description {
|
|
||||||
write!(f, ": {}", desc)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::error::Error for GpgError {}
|
|
||||||
|
|
||||||
impl GpgError {
|
|
||||||
pub(super) fn new(code: u16, description: Option<String>) -> Self {
|
|
||||||
GpgError { code, description }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the GPG code for this error.
|
|
||||||
pub fn code(&self) -> u16 {
|
|
||||||
self.code
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Errors that may be returned while interacting with `pinentry` binaries.
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
/// The user cancelled the operation.
|
|
||||||
#[error("The user cancelled the operation")]
|
|
||||||
Cancelled,
|
|
||||||
|
|
||||||
/// Operation timed out waiting for the user to respond.
|
|
||||||
#[error("Operation timed out waiting for the user to respond")]
|
|
||||||
Timeout,
|
|
||||||
|
|
||||||
/// An error occurred while finding the `pinentry` binary.
|
|
||||||
#[error("{0}")]
|
|
||||||
Which(#[from] which::Error),
|
|
||||||
|
|
||||||
/// An I/O error occurred while communicating with the `pinentry` binary.
|
|
||||||
#[error("{0}")]
|
|
||||||
Io(#[from] io::Error),
|
|
||||||
|
|
||||||
/// An uncommon or unexpected GPG error.
|
|
||||||
#[error("{0}")]
|
|
||||||
Gpg(#[from] GpgError),
|
|
||||||
|
|
||||||
/// The user's input doesn't decode to valid UTF-8.
|
|
||||||
#[error("{0}")]
|
|
||||||
Encoding(#[from] std::str::Utf8Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
impl fmt::Display for Error {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
Error::Timeout => write!(f, "Operation timed out"),
|
|
||||||
Error::Cancelled => write!(f, "Operation cancelled"),
|
|
||||||
Error::Gpg(e) => e.fmt(f),
|
|
||||||
Error::Io(e) => e.fmt(f),
|
|
||||||
Error::Encoding(e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
impl From<io::Error> for Error {
|
|
||||||
fn from(e: io::Error) -> Self {
|
|
||||||
Error::Io(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<std::str::Utf8Error> for Error {
|
|
||||||
fn from(e: std::str::Utf8Error) -> Self {
|
|
||||||
Error::Encoding(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
impl Error {
|
|
||||||
pub(crate) fn from_parts(code: u16, description: Option<String>) -> Self {
|
|
||||||
match code {
|
|
||||||
GPG_ERR_TIMEOUT => Error::Timeout,
|
|
||||||
GPG_ERR_CANCELED => Error::Cancelled,
|
|
||||||
_ => Error::Gpg(GpgError::new(code, description)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,455 +0,0 @@
|
||||||
//! `keyfork_pinentry` is a library for interacting with the pinentry binaries available on
|
|
||||||
//! various platforms.
|
|
||||||
//!
|
|
||||||
//! # Examples
|
|
||||||
//!
|
|
||||||
//! ## Request passphrase or PIN
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use keyfork_pinentry::PassphraseInput;
|
|
||||||
//! use secrecy::SecretString;
|
|
||||||
//!
|
|
||||||
//! let passphrase = if let Ok(mut input) = PassphraseInput::with_default_binary() {
|
|
||||||
//! // pinentry binary is available!
|
|
||||||
//! input
|
|
||||||
//! .with_description("Enter new passphrase for FooBar")
|
|
||||||
//! .with_prompt("Passphrase:")
|
|
||||||
//! .with_confirmation("Confirm passphrase:", "Passphrases do not match")
|
|
||||||
//! .interact()
|
|
||||||
//! } else {
|
|
||||||
//! // Fall back to some other passphrase entry method.
|
|
||||||
//! Ok(SecretString::new("a better passphrase than this".to_owned()))
|
|
||||||
//! }?;
|
|
||||||
//! # Ok::<(), keyfork_pinentry::Error>(())
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## Ask user for confirmation
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use keyfork_pinentry::ConfirmationDialog;
|
|
||||||
//!
|
|
||||||
//! if let Ok(mut input) = ConfirmationDialog::with_default_binary() {
|
|
||||||
//! input
|
|
||||||
//! .with_ok("Definitely!")
|
|
||||||
//! .with_not_ok("No thanks")
|
|
||||||
//! .with_cancel("Maybe later")
|
|
||||||
//! .confirm("Would you like to play a game?")?;
|
|
||||||
//! };
|
|
||||||
//! # Ok::<(), keyfork_pinentry::Error>(())
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! ## Display a message
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use keyfork_pinentry::MessageDialog;
|
|
||||||
//!
|
|
||||||
//! if let Ok(mut input) = MessageDialog::with_default_binary() {
|
|
||||||
//! input.with_ok("Got it!").show_message("This will be shown with a single button.")?;
|
|
||||||
//! };
|
|
||||||
//! # Ok::<(), keyfork_pinentry::Error>(())
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
// Catch documentation errors caused by code changes.
|
|
||||||
#![deny(rustdoc::broken_intra_doc_links)]
|
|
||||||
#![deny(missing_docs)]
|
|
||||||
|
|
||||||
pub use secrecy::{ExposeSecret, SecretString};
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
mod assuan;
|
|
||||||
mod error;
|
|
||||||
|
|
||||||
pub use error::{Error, GpgError};
|
|
||||||
|
|
||||||
/// Result type for the `keyfork_pinentry` crate.
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
/// Find the expected default pinentry binary
|
|
||||||
pub fn default_binary() -> Result<PathBuf> {
|
|
||||||
which::which("pinentry-curses").map_err(Into::into)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_multiline(line: &str) -> String {
|
|
||||||
// convert into multiline
|
|
||||||
let mut converted_line = String::new();
|
|
||||||
let mut last_end = 0;
|
|
||||||
for (start, part) in line.match_indices(&['\n', '\r', '%']) {
|
|
||||||
converted_line.push_str(line.get(last_end..start).unwrap());
|
|
||||||
converted_line.push_str(match part {
|
|
||||||
"\n" => "%0A",
|
|
||||||
"\r" => "%0D",
|
|
||||||
"%" => "%25",
|
|
||||||
fb => panic!("expected index given to match_indices, got: {fb}"),
|
|
||||||
});
|
|
||||||
last_end = start + part.len();
|
|
||||||
}
|
|
||||||
converted_line.push_str(line.get(last_end..line.len()).unwrap());
|
|
||||||
converted_line
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dialog for requesting a passphrase from the user.
|
|
||||||
pub struct PassphraseInput<'a> {
|
|
||||||
binary: PathBuf,
|
|
||||||
required: Option<&'a str>,
|
|
||||||
title: Option<&'a str>,
|
|
||||||
description: Option<&'a str>,
|
|
||||||
error: Option<&'a str>,
|
|
||||||
prompt: Option<&'a str>,
|
|
||||||
confirmation: Option<(&'a str, &'a str)>,
|
|
||||||
ok: Option<&'a str>,
|
|
||||||
cancel: Option<&'a str>,
|
|
||||||
timeout: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PassphraseInput<'a> {
|
|
||||||
/// Creates a new PassphraseInput using the binary named `keyfork_pinentry`.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if `default_binary()` cannot be found in `PATH`.
|
|
||||||
pub fn with_default_binary() -> Result<Self> {
|
|
||||||
default_binary().map(Self::with_binary)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new PassphraseInput using the given path to, or name of, a `pinentry`
|
|
||||||
/// binary.
|
|
||||||
pub fn with_binary(binary: PathBuf) -> Self {
|
|
||||||
PassphraseInput {
|
|
||||||
binary,
|
|
||||||
required: None,
|
|
||||||
title: None,
|
|
||||||
description: None,
|
|
||||||
error: None,
|
|
||||||
prompt: None,
|
|
||||||
confirmation: None,
|
|
||||||
ok: None,
|
|
||||||
cancel: None,
|
|
||||||
timeout: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Prevents the user from submitting an empty passphrase.
|
|
||||||
///
|
|
||||||
/// The provided error text will be displayed if the user submits an empty passphrase.
|
|
||||||
/// The dialog will remain open until the user either submits a non-empty passphrase,
|
|
||||||
/// or selects the "Cancel" button.
|
|
||||||
pub fn required(&mut self, empty_error: &'a str) -> &mut Self {
|
|
||||||
self.required = Some(empty_error);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the window title.
|
|
||||||
///
|
|
||||||
/// When using this feature you should take care that the window is still identifiable
|
|
||||||
/// as the pinentry.
|
|
||||||
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
|
|
||||||
self.title = Some(title);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the descriptive text to display.
|
|
||||||
pub fn with_description(&mut self, description: &'a str) -> &mut Self {
|
|
||||||
self.description = Some(description);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the error text to display.
|
|
||||||
///
|
|
||||||
/// This is used to display an error message, for example on a second interaction if
|
|
||||||
/// the first passphrase was invalid.
|
|
||||||
pub fn with_error(&mut self, error: &'a str) -> &mut Self {
|
|
||||||
self.error = Some(error);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the prompt to show.
|
|
||||||
///
|
|
||||||
/// When asking for a passphrase or PIN, this sets the text just before the widget for
|
|
||||||
/// passphrase entry.
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_prompt(&mut self, prompt: &'a str) -> &mut Self {
|
|
||||||
self.prompt = Some(prompt);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enables confirmation prompting.
|
|
||||||
///
|
|
||||||
/// When asking for a passphrase or PIN, this sets the text just before the widget for
|
|
||||||
/// the passphrase confirmation entry.
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_confirmation(
|
|
||||||
&mut self,
|
|
||||||
confirmation_prompt: &'a str,
|
|
||||||
mismatch_error: &'a str,
|
|
||||||
) -> &mut Self {
|
|
||||||
self.confirmation = Some((confirmation_prompt, mismatch_error));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the text for the button signalling confirmation (the "OK" button).
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_ok(&mut self, ok: &'a str) -> &mut Self {
|
|
||||||
self.ok = Some(ok);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the text for the button signaling cancellation or disagreement (the "Cancel"
|
|
||||||
/// button).
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_cancel(&mut self, cancel: &'a str) -> &mut Self {
|
|
||||||
self.cancel = Some(cancel);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the timeout (in seconds) before returning an error.
|
|
||||||
pub fn with_timeout(&mut self, timeout: u16) -> &mut Self {
|
|
||||||
self.timeout = Some(timeout);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asks for a passphrase or PIN.
|
|
||||||
pub fn interact(&self) -> Result<SecretString> {
|
|
||||||
let mut pinentry = assuan::Connection::open(&self.binary)?;
|
|
||||||
|
|
||||||
if let Some(title) = &self.title {
|
|
||||||
pinentry.send_request("SETTITLE", Some(title))?;
|
|
||||||
}
|
|
||||||
if let Some(desc) = &self.description {
|
|
||||||
pinentry.send_request("SETDESC", Some(convert_multiline(desc).as_ref()))?;
|
|
||||||
}
|
|
||||||
if let Some(error) = &self.error {
|
|
||||||
pinentry.send_request("SETERROR", Some(error))?;
|
|
||||||
}
|
|
||||||
if let Some(prompt) = &self.prompt {
|
|
||||||
pinentry.send_request("SETPROMPT", Some(prompt))?;
|
|
||||||
}
|
|
||||||
if let Some(ok) = &self.ok {
|
|
||||||
pinentry.send_request("SETOK", Some(ok))?;
|
|
||||||
}
|
|
||||||
if let Some(cancel) = &self.cancel {
|
|
||||||
pinentry.send_request("SETCANCEL", Some(cancel))?;
|
|
||||||
}
|
|
||||||
if let Some((confirmation_prompt, mismatch_error)) = &self.confirmation {
|
|
||||||
pinentry.send_request("SETREPEAT", Some(confirmation_prompt))?;
|
|
||||||
pinentry.send_request("SETREPEATERROR", Some(mismatch_error))?;
|
|
||||||
}
|
|
||||||
if let Some(timeout) = self.timeout {
|
|
||||||
pinentry.send_request("SETTIMEOUT", Some(&format!("{}", timeout)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match (pinentry.send_request("GETPIN", None)?, self.required) {
|
|
||||||
// If the user provides an empty passphrase, GETPIN returns no data.
|
|
||||||
(None, None) => return Ok(SecretString::new(String::new())),
|
|
||||||
(Some(passphrase), _) => return Ok(passphrase),
|
|
||||||
(_, Some(empty_error)) => {
|
|
||||||
// SETERROR is cleared by GETPIN, so we reset it on each loop.
|
|
||||||
pinentry.send_request("SETERROR", Some(empty_error))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dialog for requesting a confirmation from the user.
|
|
||||||
pub struct ConfirmationDialog<'a> {
|
|
||||||
binary: PathBuf,
|
|
||||||
title: Option<&'a str>,
|
|
||||||
ok: Option<&'a str>,
|
|
||||||
cancel: Option<&'a str>,
|
|
||||||
not_ok: Option<&'a str>,
|
|
||||||
timeout: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ConfirmationDialog<'a> {
|
|
||||||
/// Creates a new ConfirmationDialog using the binary named `pinentry`.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if `pinentry` cannot be found in `PATH`.
|
|
||||||
pub fn with_default_binary() -> Result<Self> {
|
|
||||||
default_binary().map(Self::with_binary)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new ConfirmationDialog using the given path to, or name of, a `pinentry`
|
|
||||||
/// binary.
|
|
||||||
pub fn with_binary(binary: PathBuf) -> Self {
|
|
||||||
ConfirmationDialog {
|
|
||||||
binary,
|
|
||||||
title: None,
|
|
||||||
ok: None,
|
|
||||||
cancel: None,
|
|
||||||
not_ok: None,
|
|
||||||
timeout: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the window title.
|
|
||||||
///
|
|
||||||
/// When using this feature you should take care that the window is still identifiable
|
|
||||||
/// as the pinentry.
|
|
||||||
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
|
|
||||||
self.title = Some(title);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the text for the button signalling confirmation (the "OK" button).
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_ok(&mut self, ok: &'a str) -> &mut Self {
|
|
||||||
self.ok = Some(ok);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the text for the button signaling cancellation or disagreement (the "Cancel"
|
|
||||||
/// button).
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_cancel(&mut self, cancel: &'a str) -> &mut Self {
|
|
||||||
self.cancel = Some(cancel);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enables the third non-affirmative response button (the "Not OK" button).
|
|
||||||
///
|
|
||||||
/// This can be used in case three buttons are required (to distinguish between
|
|
||||||
/// cancellation and disagreement).
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_not_ok(&mut self, not_ok: &'a str) -> &mut Self {
|
|
||||||
self.not_ok = Some(not_ok);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the timeout (in seconds) before returning an error.
|
|
||||||
pub fn with_timeout(&mut self, timeout: u16) -> &mut Self {
|
|
||||||
self.timeout = Some(timeout);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Asks for confirmation.
|
|
||||||
///
|
|
||||||
/// Returns:
|
|
||||||
/// - `Ok(true)` if the "OK" button is selected.
|
|
||||||
/// - `Ok(false)` if:
|
|
||||||
/// - the "Cancel" button is selected and the "Not OK" button is disabled.
|
|
||||||
/// - the "Not OK" button is enabled and selected.
|
|
||||||
/// - `Err(Error::Cancelled)` if the "Cancel" button is selected and the "Not OK"
|
|
||||||
/// button is enabled.
|
|
||||||
pub fn confirm(&self, query: &str) -> Result<bool> {
|
|
||||||
let mut pinentry = assuan::Connection::open(&self.binary)?;
|
|
||||||
|
|
||||||
pinentry.send_request("SETDESC", Some(query))?;
|
|
||||||
if let Some(ok) = &self.ok {
|
|
||||||
pinentry.send_request("SETOK", Some(ok))?;
|
|
||||||
}
|
|
||||||
if let Some(cancel) = &self.cancel {
|
|
||||||
pinentry.send_request("SETCANCEL", Some(cancel))?;
|
|
||||||
}
|
|
||||||
if let Some(not_ok) = &self.not_ok {
|
|
||||||
pinentry.send_request("SETNOTOK", Some(not_ok))?;
|
|
||||||
}
|
|
||||||
if let Some(timeout) = self.timeout {
|
|
||||||
pinentry.send_request("SETTIMEOUT", Some(&format!("{}", timeout)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pinentry
|
|
||||||
.send_request("CONFIRM", None)
|
|
||||||
.map(|_| true)
|
|
||||||
.or_else(|e| match (&e, self.not_ok.is_some()) {
|
|
||||||
(Error::Cancelled, false) => Ok(false),
|
|
||||||
(Error::Gpg(gpg), true) if gpg.code() == error::GPG_ERR_NOT_CONFIRMED => Ok(false),
|
|
||||||
_ => Err(e),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A dialog for showing a message to the user.
|
|
||||||
pub struct MessageDialog<'a> {
|
|
||||||
binary: PathBuf,
|
|
||||||
title: Option<&'a str>,
|
|
||||||
ok: Option<&'a str>,
|
|
||||||
timeout: Option<u16>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> MessageDialog<'a> {
|
|
||||||
/// Creates a new MessageDialog using the binary named `pinentry`.
|
|
||||||
///
|
|
||||||
/// Returns `Err` if `pinentry` cannot be found in `PATH`.
|
|
||||||
pub fn with_default_binary() -> Result<Self> {
|
|
||||||
default_binary().map(Self::with_binary)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new MessageDialog using the given path to, or name of, a `pinentry`
|
|
||||||
/// binary.
|
|
||||||
pub fn with_binary(binary: PathBuf) -> Self {
|
|
||||||
MessageDialog {
|
|
||||||
binary,
|
|
||||||
title: None,
|
|
||||||
ok: None,
|
|
||||||
timeout: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the window title.
|
|
||||||
///
|
|
||||||
/// When using this feature you should take care that the window is still identifiable
|
|
||||||
/// as the pinentry.
|
|
||||||
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
|
|
||||||
self.title = Some(title);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the text for the button signalling confirmation (the "OK" button).
|
|
||||||
///
|
|
||||||
/// You should use an underscore in the text only if you know that a modern version of
|
|
||||||
/// pinentry is used. Modern versions underline the next character after the
|
|
||||||
/// underscore and use the first such underlined character as a keyboard accelerator.
|
|
||||||
/// Use a double underscore to escape an underscore.
|
|
||||||
pub fn with_ok(&mut self, ok: &'a str) -> &mut Self {
|
|
||||||
self.ok = Some(ok);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the timeout (in seconds) before returning an error.
|
|
||||||
pub fn with_timeout(&mut self, timeout: u16) -> &mut Self {
|
|
||||||
self.timeout = Some(timeout);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shows a message.
|
|
||||||
pub fn show_message(&self, message: &str) -> Result<()> {
|
|
||||||
let mut pinentry = assuan::Connection::open(&self.binary)?;
|
|
||||||
|
|
||||||
pinentry.send_request("SETDESC", Some(message))?;
|
|
||||||
if let Some(ok) = &self.ok {
|
|
||||||
pinentry.send_request("SETOK", Some(ok))?;
|
|
||||||
}
|
|
||||||
if let Some(timeout) = self.timeout {
|
|
||||||
pinentry.send_request("SETTIMEOUT", Some(&format!("{}", timeout)))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pinentry.send_request("MESSAGE", None).map(|_| ())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,21 +6,14 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["openpgp", "openpgp-card"]
|
default = ["openpgp"]
|
||||||
openpgp = ["sequoia-openpgp", "prompt"]
|
openpgp = ["sequoia-openpgp"]
|
||||||
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend"]
|
|
||||||
prompt = ["keyfork-pinentry"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
card-backend = { version = "0.2.0", optional = true }
|
|
||||||
card-backend-pcsc = { version = "0.5.0", optional = true }
|
|
||||||
keyfork-derive-openpgp = { version = "0.1.0", path = "../keyfork-derive-openpgp" }
|
keyfork-derive-openpgp = { version = "0.1.0", path = "../keyfork-derive-openpgp" }
|
||||||
keyfork-pinentry = { version = "0.5.0", path = "../keyfork-pinentry", optional = true }
|
|
||||||
openpgp-card-sequoia = { version = "0.2.0", optional = true }
|
|
||||||
sequoia-openpgp = { version = "1.16.1", optional = true }
|
sequoia-openpgp = { version = "1.16.1", optional = true }
|
||||||
serde = "1.0.188"
|
serde = "1.0.188"
|
||||||
sharks = "0.5.0"
|
sharks = "0.5.0"
|
||||||
smex = { version = "0.1.0", path = "../smex" }
|
smex = { version = "0.1.0", path = "../smex" }
|
||||||
thiserror = "1.0.50"
|
|
||||||
|
|
|
@ -6,23 +6,19 @@ use std::{
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert, parse_messages};
|
use keyfork_shard::openpgp::{combine, discover_certs, parse_messages, openpgp::Cert};
|
||||||
|
|
||||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
fn validate<'a>(
|
fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec<Cert>)> {
|
||||||
threshold: &str,
|
|
||||||
key_discovery: impl Into<Option<&'a str>>,
|
|
||||||
) -> Result<(u8, Vec<Cert>)> {
|
|
||||||
let threshold = u8::from_str(threshold)?;
|
let threshold = u8::from_str(threshold)?;
|
||||||
let key_discovery = key_discovery.into().map(PathBuf::from);
|
let key_discovery = PathBuf::from(key_discovery);
|
||||||
key_discovery.as_ref().map(std::fs::metadata).transpose()?;
|
|
||||||
|
// Verify path exists
|
||||||
|
std::fs::metadata(&key_discovery)?;
|
||||||
|
|
||||||
// Load certs from path
|
// Load certs from path
|
||||||
let certs = key_discovery
|
let certs = discover_certs(key_discovery)?;
|
||||||
.map(discover_certs)
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or(vec![]);
|
|
||||||
|
|
||||||
Ok((threshold, certs))
|
Ok((threshold, certs))
|
||||||
}
|
}
|
||||||
|
@ -32,9 +28,8 @@ fn run() -> Result<()> {
|
||||||
let program_name = args.next().expect("program name");
|
let program_name = args.next().expect("program name");
|
||||||
let args = args.collect::<Vec<_>>();
|
let args = args.collect::<Vec<_>>();
|
||||||
let (threshold, cert_list) = match args.as_slice() {
|
let (threshold, cert_list) = match args.as_slice() {
|
||||||
[threshold, key_discovery] => validate(threshold, key_discovery.as_str())?,
|
[threshold, key_discovery] => validate(threshold, key_discovery)?,
|
||||||
[threshold] => validate(threshold, None)?,
|
_ => panic!("Usage: {program_name} threshold key_discovery"),
|
||||||
_ => panic!("Usage: {program_name} threshold [key_discovery]"),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut encrypted_messages = parse_messages(stdin())?;
|
let mut encrypted_messages = parse_messages(stdin())?;
|
||||||
|
@ -58,11 +53,6 @@ fn main() -> ExitCode {
|
||||||
let result = run();
|
let result = run();
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
eprintln!("Error: {e}");
|
eprintln!("Error: {e}");
|
||||||
let mut source = e.source();
|
|
||||||
while let Some(new_error) = source.take() {
|
|
||||||
eprintln!("Source: {new_error}");
|
|
||||||
source = new_error.source();
|
|
||||||
}
|
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
ExitCode::SUCCESS
|
ExitCode::SUCCESS
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
#[cfg(feature = "openpgp")]
|
#[cfg(feature = "openpgp")]
|
||||||
pub mod openpgp;
|
pub mod openpgp;
|
||||||
|
|
||||||
#[cfg(feature = "prompt")]
|
|
||||||
mod prompt_manager;
|
|
||||||
|
|
|
@ -13,17 +13,14 @@ use openpgp::{
|
||||||
armor::{Kind, Writer},
|
armor::{Kind, Writer},
|
||||||
cert::{Cert, CertParser, ValidCert},
|
cert::{Cert, CertParser, ValidCert},
|
||||||
packet::{Packet, Tag, UserID, PKESK, SEIP},
|
packet::{Packet, Tag, UserID, PKESK, SEIP},
|
||||||
parse::{
|
parse::{stream::DecryptorBuilder, Parse},
|
||||||
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
|
|
||||||
Parse,
|
|
||||||
},
|
|
||||||
policy::{NullPolicy, Policy, StandardPolicy},
|
policy::{NullPolicy, Policy, StandardPolicy},
|
||||||
serialize::{
|
serialize::{
|
||||||
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
|
stream::{ArbitraryWriter, Encryptor, LiteralWriter, Message, Recipient, Signer},
|
||||||
Marshal,
|
Marshal,
|
||||||
},
|
},
|
||||||
types::KeyFlags,
|
types::KeyFlags,
|
||||||
Fingerprint, KeyID, PacketPile,
|
KeyID, PacketPile,
|
||||||
};
|
};
|
||||||
pub use sequoia_openpgp as openpgp;
|
pub use sequoia_openpgp as openpgp;
|
||||||
use sharks::{Share, Sharks};
|
use sharks::{Share, Sharks};
|
||||||
|
@ -31,46 +28,20 @@ use sharks::{Share, Sharks};
|
||||||
mod keyring;
|
mod keyring;
|
||||||
use keyring::Keyring;
|
use keyring::Keyring;
|
||||||
|
|
||||||
mod smartcard;
|
// TODO: better error handling
|
||||||
use smartcard::SmartcardManager;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Error {
|
pub struct WrappedError(String);
|
||||||
#[error("Error with creating Share: {0}")]
|
|
||||||
Share(String),
|
|
||||||
|
|
||||||
#[error("Error combining shares: {0}")]
|
impl std::fmt::Display for WrappedError {
|
||||||
CombineShares(String),
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.0)
|
||||||
#[error("Derived secret hash {0} != expected {1}")]
|
}
|
||||||
InvalidSecret(Fingerprint, Fingerprint),
|
|
||||||
|
|
||||||
#[error("OpenPGP error: {0}")]
|
|
||||||
Sequoia(#[source] anyhow::Error),
|
|
||||||
|
|
||||||
#[error("OpenPGP IO error: {0}")]
|
|
||||||
SequoiaIo(#[source] std::io::Error),
|
|
||||||
|
|
||||||
#[error("Keyring error: {0}")]
|
|
||||||
Keyring(#[from] keyring::Error),
|
|
||||||
|
|
||||||
#[error("Smartcard error: {0}")]
|
|
||||||
Smartcard(#[from] smartcard::Error),
|
|
||||||
|
|
||||||
#[error("IO error: {0}")]
|
|
||||||
Io(#[source] std::io::Error),
|
|
||||||
|
|
||||||
#[error("Derivation path: {0}")]
|
|
||||||
DerivationPath(#[from] keyfork_derive_openpgp::derive_util::path::Error),
|
|
||||||
|
|
||||||
#[error("Derivation request: {0}")]
|
|
||||||
DerivationRequest(#[from] keyfork_derive_openpgp::derive_util::request::DerivationError),
|
|
||||||
|
|
||||||
#[error("Keyfork OpenPGP: {0}")]
|
|
||||||
KeyforkOpenPGP(#[from] keyfork_derive_openpgp::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
impl std::error::Error for WrappedError {}
|
||||||
|
|
||||||
|
pub type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EncryptedMessage {
|
pub struct EncryptedMessage {
|
||||||
|
@ -86,38 +57,29 @@ impl EncryptedMessage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn decrypt_with<H>(&self, policy: &'_ dyn Policy, decryptor: H) -> Result<Vec<u8>>
|
pub fn decrypt_with(&self, policy: &'_ dyn Policy, keyring: &mut Keyring) -> Result<Vec<u8>> {
|
||||||
where
|
|
||||||
H: VerificationHelper + DecryptionHelper,
|
|
||||||
{
|
|
||||||
let mut packets = vec![];
|
let mut packets = vec![];
|
||||||
|
|
||||||
for pkesk in &self.pkesks {
|
for pkesk in &self.pkesks {
|
||||||
let mut packet = vec![];
|
let mut packet = vec![];
|
||||||
pkesk.serialize(&mut packet).map_err(Error::Sequoia)?;
|
pkesk.serialize(&mut packet)?;
|
||||||
let message = Message::new(&mut packets);
|
let message = Message::new(&mut packets);
|
||||||
let mut message = ArbitraryWriter::new(message, Tag::PKESK).map_err(Error::Sequoia)?;
|
let mut message = ArbitraryWriter::new(message, Tag::PKESK)?;
|
||||||
message.write_all(&packet).map_err(Error::SequoiaIo)?;
|
message.write_all(&packet)?;
|
||||||
message.finalize().map_err(Error::Sequoia)?;
|
message.finalize()?;
|
||||||
}
|
}
|
||||||
let mut packet = vec![];
|
let mut packet = vec![];
|
||||||
self.message
|
self.message.serialize(&mut packet)?;
|
||||||
.serialize(&mut packet)
|
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
let message = Message::new(&mut packets);
|
let message = Message::new(&mut packets);
|
||||||
let mut message = ArbitraryWriter::new(message, Tag::SEIP).map_err(Error::Sequoia)?;
|
let mut message = ArbitraryWriter::new(message, Tag::SEIP)?;
|
||||||
message.write_all(&packet).map_err(Error::SequoiaIo)?;
|
message.write_all(&packet)?;
|
||||||
message.finalize().map_err(Error::Sequoia)?;
|
message.finalize()?;
|
||||||
|
|
||||||
let mut decryptor = DecryptorBuilder::from_bytes(&packets)
|
let mut decryptor =
|
||||||
.map_err(Error::Sequoia)?
|
DecryptorBuilder::from_bytes(&packets)?.with_policy(policy, None, keyring)?;
|
||||||
.with_policy(policy, None, decryptor)
|
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
|
|
||||||
let mut content = vec![];
|
let mut content = vec![];
|
||||||
decryptor
|
decryptor.read_to_end(&mut content)?;
|
||||||
.read_to_end(&mut content)
|
|
||||||
.map_err(Error::SequoiaIo)?;
|
|
||||||
Ok(content)
|
Ok(content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,19 +89,18 @@ pub fn discover_certs(path: impl AsRef<Path>) -> Result<Vec<Cert>> {
|
||||||
|
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
for cert in CertParser::from_file(path).map_err(Error::Sequoia)? {
|
for cert in CertParser::from_file(path)? {
|
||||||
vec.push(cert.map_err(Error::Sequoia)?);
|
vec.push(cert?);
|
||||||
}
|
}
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
} else {
|
} else {
|
||||||
let mut vec = vec![];
|
let mut vec = vec![];
|
||||||
for entry in path
|
for entry in path
|
||||||
.read_dir()
|
.read_dir()?
|
||||||
.map_err(Error::Io)?
|
|
||||||
.filter_map(Result::ok)
|
.filter_map(Result::ok)
|
||||||
.filter(|p| p.path().is_file())
|
.filter(|p| p.path().is_file())
|
||||||
{
|
{
|
||||||
vec.push(Cert::from_file(entry.path()).map_err(Error::Sequoia)?);
|
vec.push(Cert::from_file(entry.path())?);
|
||||||
}
|
}
|
||||||
Ok(vec)
|
Ok(vec)
|
||||||
}
|
}
|
||||||
|
@ -149,10 +110,7 @@ pub fn parse_messages(reader: impl Read + Send + Sync) -> Result<VecDeque<Encryp
|
||||||
let mut pkesks = Vec::new();
|
let mut pkesks = Vec::new();
|
||||||
let mut encrypted_messages = VecDeque::new();
|
let mut encrypted_messages = VecDeque::new();
|
||||||
|
|
||||||
for packet in PacketPile::from_reader(reader)
|
for packet in PacketPile::from_reader(reader)?.into_children() {
|
||||||
.map_err(Error::Sequoia)?
|
|
||||||
.into_children()
|
|
||||||
{
|
|
||||||
match packet {
|
match packet {
|
||||||
Packet::PKESK(p) => pkesks.push(p),
|
Packet::PKESK(p) => pkesks.push(p),
|
||||||
Packet::SEIP(s) => {
|
Packet::SEIP(s) => {
|
||||||
|
@ -209,29 +167,17 @@ pub fn combine(
|
||||||
// We don't want to invalidate someone's keys just because the old sig expired.
|
// We don't want to invalidate someone's keys just because the old sig expired.
|
||||||
let policy = NullPolicy::new();
|
let policy = NullPolicy::new();
|
||||||
|
|
||||||
let mut keyring = Keyring::new(certs)?;
|
let mut keyring = Keyring::new(certs);
|
||||||
let mut manager = SmartcardManager::new()?;
|
let content = metadata.decrypt_with(&policy, &mut keyring)?;
|
||||||
let content = if keyring.is_empty() {
|
|
||||||
// NOTE: Any card plugged in that can't decrypt, will raise issues.
|
|
||||||
// This should not be used on a system where OpenPGP cards are available that shouldn't be
|
|
||||||
// used, due to the nature of how wildcard decryption works.
|
|
||||||
manager.load_any_card()?;
|
|
||||||
metadata.decrypt_with(&policy, &mut manager)?
|
|
||||||
} else {
|
|
||||||
metadata.decrypt_with(&policy, &mut keyring)?
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut cert_parser = CertParser::from_bytes(&content).map_err(Error::Sequoia)?;
|
let mut cert_parser = CertParser::from_bytes(&content)?;
|
||||||
let root_cert = match cert_parser.next() {
|
let root_cert = match cert_parser.next() {
|
||||||
Some(Ok(c)) => c,
|
Some(Ok(c)) => c,
|
||||||
Some(Err(e)) => panic!("Could not find root (first) certificate: {e}"),
|
Some(Err(e)) => panic!("Could not find root (first) certificate: {e}"),
|
||||||
None => panic!("No certs found in cert parser"),
|
None => panic!("No certs found in cert parser"),
|
||||||
};
|
};
|
||||||
let certs = cert_parser
|
let certs = cert_parser.collect::<openpgp::Result<Vec<_>>>()?;
|
||||||
.collect::<openpgp::Result<Vec<_>>>()
|
keyring.set_root_cert(root_cert);
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
keyring.set_root_cert(root_cert.clone());
|
|
||||||
manager.set_root_cert(root_cert);
|
|
||||||
let mut messages: HashMap<KeyID, EncryptedMessage> =
|
let mut messages: HashMap<KeyID, EncryptedMessage> =
|
||||||
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
|
HashMap::from_iter(certs.iter().map(|c| c.keyid()).zip(messages));
|
||||||
let mut decrypted_messages: HashMap<KeyID, Vec<u8>> = HashMap::new();
|
let mut decrypted_messages: HashMap<KeyID, Vec<u8>> = HashMap::new();
|
||||||
|
@ -239,14 +185,12 @@ pub fn combine(
|
||||||
// NOTE: This is ONLY stable because we control the generation of PKESK packets and
|
// NOTE: This is ONLY stable because we control the generation of PKESK packets and
|
||||||
// encode the policy to ourselves.
|
// encode the policy to ourselves.
|
||||||
for valid_cert in certs.iter().map(|cert| cert.with_policy(&policy, None)) {
|
for valid_cert in certs.iter().map(|cert| cert.with_policy(&policy, None)) {
|
||||||
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
|
let valid_cert = valid_cert?;
|
||||||
// get keys from keyring for cert
|
// get keys from keyring for cert
|
||||||
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
|
let Some(secret_cert) = keyring.get_cert_for_primary_keyid(&valid_cert.keyid()) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let secret_cert = secret_cert
|
let secret_cert = secret_cert.with_policy(&policy, None)?;
|
||||||
.with_policy(&policy, None)
|
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
|
let keys = get_decryption_keys(&secret_cert).collect::<Vec<_>>();
|
||||||
if !keys.is_empty() {
|
if !keys.is_empty() {
|
||||||
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
|
if let Some(message) = messages.get_mut(&valid_cert.keyid()) {
|
||||||
|
@ -254,10 +198,20 @@ pub fn combine(
|
||||||
pkesk.set_recipient(key.keyid());
|
pkesk.set_recipient(key.keyid());
|
||||||
}
|
}
|
||||||
// we have a pkesk, decrypt via keyring
|
// we have a pkesk, decrypt via keyring
|
||||||
decrypted_messages.insert(
|
let result = message.decrypt_with(&policy, &mut keyring);
|
||||||
valid_cert.keyid(),
|
match result {
|
||||||
message.decrypt_with(&policy, &mut keyring)?,
|
Ok(message) => {
|
||||||
);
|
decrypted_messages.insert(valid_cert.keyid(), message);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Could not decrypt with fingerprint {}: {}",
|
||||||
|
valid_cert.keyid(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
// do nothing, key will be retained
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,53 +221,18 @@ pub fn combine(
|
||||||
|
|
||||||
let left_from_threshold = threshold as usize - decrypted_messages.len();
|
let left_from_threshold = threshold as usize - decrypted_messages.len();
|
||||||
if left_from_threshold > 0 {
|
if left_from_threshold > 0 {
|
||||||
let mut remaining_usable_certs = certs
|
eprintln!("remaining keys: {left_from_threshold}, prompting yubikeys");
|
||||||
.iter()
|
}
|
||||||
.filter(|cert| messages.contains_key(&cert.keyid()))
|
for _ in 0..left_from_threshold {
|
||||||
.collect::<Vec<_>>();
|
todo!("prompt for Yubikeys")
|
||||||
|
|
||||||
while threshold as usize - decrypted_messages.len() > 0 {
|
|
||||||
remaining_usable_certs.retain(|cert| messages.contains_key(&cert.keyid()));
|
|
||||||
let mut key_by_fingerprints = HashMap::new();
|
|
||||||
let mut total_fingerprints = vec![];
|
|
||||||
for valid_cert in remaining_usable_certs
|
|
||||||
.iter()
|
|
||||||
.map(|cert| cert.with_policy(&policy, None))
|
|
||||||
{
|
|
||||||
let valid_cert = valid_cert.map_err(Error::Sequoia)?;
|
|
||||||
let fp = valid_cert
|
|
||||||
.keys()
|
|
||||||
.for_storage_encryption()
|
|
||||||
.map(|k| k.fingerprint())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
for fp in &fp {
|
|
||||||
key_by_fingerprints.insert(fp.clone(), valid_cert.keyid());
|
|
||||||
}
|
|
||||||
total_fingerprints.extend(fp.iter().cloned());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all fingerprints and use key_by_fingerprints to assoc with Enc. Message
|
|
||||||
if let Some(fp) = manager.load_any_fingerprint(total_fingerprints)? {
|
|
||||||
// soundness: `key_by_fingerprints` is extended by the same fps that are then
|
|
||||||
// inserted into `total_fingerprints`
|
|
||||||
let cert_keyid = key_by_fingerprints.get(&fp).unwrap().clone();
|
|
||||||
let message = messages.remove(&cert_keyid);
|
|
||||||
if let Some(message) = message {
|
|
||||||
let message = message.decrypt_with(&policy, &mut manager)?;
|
|
||||||
decrypted_messages.insert(cert_keyid, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let shares = decrypted_messages
|
let shares = decrypted_messages
|
||||||
.values()
|
.values()
|
||||||
.map(|message| Share::try_from(message.as_slice()))
|
.map(|message| Share::try_from(message.as_slice()))
|
||||||
.collect::<Result<Vec<_>, &str>>()
|
.collect::<Result<Vec<_>, &str>>()
|
||||||
.map_err(|e| Error::Share(e.to_string()))?;
|
.map_err(|e| WrappedError(e.to_string()))?;
|
||||||
let secret = Sharks(threshold)
|
let secret = Sharks(threshold).recover(&shares)?;
|
||||||
.recover(&shares)
|
|
||||||
.map_err(|e| Error::CombineShares(e.to_string()))?;
|
|
||||||
|
|
||||||
let userid = UserID::from("keyfork-sss");
|
let userid = UserID::from("keyfork-sss");
|
||||||
let kdr = DerivationRequest::new(
|
let kdr = DerivationRequest::new(
|
||||||
|
@ -328,18 +247,19 @@ pub fn combine(
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// NOTE: Signatures on certs will be different. Compare fingerprints instead.
|
// NOTE: Signatures on certs will be different. Compare fingerprints instead.
|
||||||
let derived_fp = derived_cert.fingerprint();
|
if Some(derived_cert.fingerprint()) != keyring.root_cert().map(Cert::fingerprint) {
|
||||||
let expected_fp = keyring
|
return Err(WrappedError(format!(
|
||||||
.root_cert()
|
"Derived {} != expected {}",
|
||||||
.expect("cert was previously set")
|
derived_cert.fingerprint(),
|
||||||
.fingerprint();
|
keyring
|
||||||
if derived_fp != expected_fp {
|
.root_cert()
|
||||||
return Err(Error::InvalidSecret(derived_fp, expected_fp));
|
.expect("cert was previously set")
|
||||||
|
.fingerprint()
|
||||||
|
))
|
||||||
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
output
|
output.write_all(smex::encode(&secret).as_bytes())?;
|
||||||
.write_all(smex::encode(&secret).as_bytes())
|
|
||||||
.map_err(Error::Io)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -359,63 +279,55 @@ pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write)
|
||||||
)?;
|
)?;
|
||||||
let signing_key = derived_cert
|
let signing_key = derived_cert
|
||||||
.primary_key()
|
.primary_key()
|
||||||
.parts_into_secret()
|
.parts_into_secret()?
|
||||||
.map_err(Error::Sequoia)?
|
|
||||||
.key()
|
.key()
|
||||||
.clone()
|
.clone()
|
||||||
.into_keypair()
|
.into_keypair()?;
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
|
|
||||||
let sharks = Sharks(threshold);
|
let sharks = Sharks(threshold);
|
||||||
let dealer = sharks.dealer(secret);
|
let dealer = sharks.dealer(secret);
|
||||||
let shares = dealer.map(|s| Vec::from(&s)).collect::<Vec<_>>();
|
let shares = dealer.map(|s| Vec::from(&s)).collect::<Vec<_>>();
|
||||||
let policy = StandardPolicy::new();
|
let policy = StandardPolicy::new();
|
||||||
let mut writer = Writer::new(output, Kind::Message).map_err(Error::SequoiaIo)?;
|
let mut writer = Writer::new(output, Kind::Message)?;
|
||||||
|
|
||||||
let mut total_recipients = vec![];
|
let mut total_recipients = vec![];
|
||||||
let mut messages = vec![];
|
let mut messages = vec![];
|
||||||
|
|
||||||
for (share, cert) in shares.iter().zip(certs) {
|
for (share, cert) in shares.iter().zip(certs) {
|
||||||
total_recipients.push(cert.clone());
|
total_recipients.push(cert.clone());
|
||||||
let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?;
|
let valid_cert = cert.with_policy(&policy, None)?;
|
||||||
let encryption_keys = get_encryption_keys(&valid_cert).collect::<Vec<_>>();
|
let encryption_keys = get_encryption_keys(&valid_cert).collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut message_output = vec![];
|
let mut message_output = vec![];
|
||||||
let message = Message::new(&mut message_output);
|
let message = Message::new(&mut message_output);
|
||||||
let message = Encryptor2::for_recipients(
|
let message = Encryptor::for_recipients(
|
||||||
message,
|
message,
|
||||||
encryption_keys
|
encryption_keys
|
||||||
.iter()
|
.iter()
|
||||||
.map(|k| Recipient::new(KeyID::wildcard(), k.key())),
|
.map(|k| Recipient::new(KeyID::wildcard(), k.key())),
|
||||||
)
|
)
|
||||||
.build()
|
.build()?;
|
||||||
.map_err(Error::Sequoia)?;
|
let message = Signer::new(message, signing_key.clone()).build()?;
|
||||||
let message = Signer::new(message, signing_key.clone())
|
let mut message = LiteralWriter::new(message).build()?;
|
||||||
.build()
|
message.write_all(share)?;
|
||||||
.map_err(Error::Sequoia)?;
|
message.finalize()?;
|
||||||
let mut message = LiteralWriter::new(message)
|
|
||||||
.build()
|
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
message.write_all(share).map_err(Error::SequoiaIo)?;
|
|
||||||
message.finalize().map_err(Error::Sequoia)?;
|
|
||||||
|
|
||||||
messages.push(message_output);
|
messages.push(message_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut pp = vec![];
|
let mut pp = vec![];
|
||||||
// store derived cert to verify provided shares
|
// store derived cert to verify provided shares
|
||||||
derived_cert.serialize(&mut pp).map_err(Error::Sequoia)?;
|
derived_cert.serialize(&mut pp)?;
|
||||||
for recipient in &total_recipients {
|
for recipient in &total_recipients {
|
||||||
recipient.serialize(&mut pp).map_err(Error::Sequoia)?;
|
recipient.serialize(&mut pp)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify packet pile
|
// verify packet pile
|
||||||
for (packet_cert, cert) in openpgp::cert::CertParser::from_bytes(&pp)
|
for (packet_cert, cert) in openpgp::cert::CertParser::from_bytes(&pp)?
|
||||||
.map_err(Error::Sequoia)?
|
|
||||||
.skip(1)
|
.skip(1)
|
||||||
.zip(total_recipients.iter())
|
.zip(total_recipients.iter())
|
||||||
{
|
{
|
||||||
if packet_cert.map_err(Error::Sequoia)? != *cert {
|
if packet_cert? != *cert {
|
||||||
panic!(
|
panic!(
|
||||||
"packet pile could not recreate cert: {}",
|
"packet pile could not recreate cert: {}",
|
||||||
cert.fingerprint()
|
cert.fingerprint()
|
||||||
|
@ -426,8 +338,7 @@ pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write)
|
||||||
let valid_certs = total_recipients
|
let valid_certs = total_recipients
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.with_policy(&policy, None))
|
.map(|c| c.with_policy(&policy, None))
|
||||||
.collect::<openpgp::Result<Vec<_>>>()
|
.collect::<openpgp::Result<Vec<_>>>()?;
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
|
|
||||||
let total_recipients = valid_certs.iter().flat_map(|vc| {
|
let total_recipients = valid_certs.iter().flat_map(|vc| {
|
||||||
get_encryption_keys(vc).map(|key| Recipient::new(KeyID::wildcard(), key.key()))
|
get_encryption_keys(vc).map(|key| Recipient::new(KeyID::wildcard(), key.key()))
|
||||||
|
@ -436,23 +347,17 @@ pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write)
|
||||||
// metadata
|
// metadata
|
||||||
let mut message_output = vec![];
|
let mut message_output = vec![];
|
||||||
let message = Message::new(&mut message_output);
|
let message = Message::new(&mut message_output);
|
||||||
let message = Encryptor2::for_recipients(message, total_recipients)
|
let message = Encryptor::for_recipients(message, total_recipients).build()?;
|
||||||
.build()
|
let mut message = LiteralWriter::new(message).build()?;
|
||||||
.map_err(Error::Sequoia)?;
|
message.write_all(&pp)?;
|
||||||
let mut message = LiteralWriter::new(message)
|
message.finalize()?;
|
||||||
.build()
|
writer.write_all(&message_output)?;
|
||||||
.map_err(Error::Sequoia)?;
|
|
||||||
message.write_all(&pp).map_err(Error::SequoiaIo)?;
|
|
||||||
message.finalize().map_err(Error::Sequoia)?;
|
|
||||||
writer
|
|
||||||
.write_all(&message_output)
|
|
||||||
.map_err(Error::SequoiaIo)?;
|
|
||||||
|
|
||||||
for message in messages {
|
for message in messages {
|
||||||
writer.write_all(&message).map_err(Error::SequoiaIo)?;
|
writer.write_all(&message)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.finalize().map_err(Error::SequoiaIo)?;
|
writer.finalize()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,43 @@
|
||||||
use keyfork_pinentry::ExposeSecret;
|
|
||||||
|
|
||||||
use super::openpgp::{
|
use super::openpgp::{
|
||||||
self,
|
self,
|
||||||
cert::Cert,
|
cert::Cert,
|
||||||
packet::{PKESK, SKESK},
|
packet::{PKESK, SKESK},
|
||||||
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
||||||
policy::NullPolicy,
|
|
||||||
KeyHandle, KeyID,
|
KeyHandle, KeyID,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::prompt_manager::{PinentryError, PromptManager};
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum KeyringFailure {
|
||||||
use anyhow::Context;
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Secret key was not found")]
|
|
||||||
SecretKeyNotFound,
|
SecretKeyNotFound,
|
||||||
|
#[allow(dead_code)]
|
||||||
#[error("Prompt failed: {0}")]
|
SmartcardDecrypt,
|
||||||
Prompt(#[from] PinentryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
impl std::fmt::Display for KeyringFailure {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
KeyringFailure::SecretKeyNotFound => f.write_str("Secret key was not found"),
|
||||||
|
KeyringFailure::SmartcardDecrypt => {
|
||||||
|
f.write_str("Smartcard could not decrypt any PKESKs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for KeyringFailure {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct Keyring {
|
pub struct Keyring {
|
||||||
full_certs: Vec<Cert>,
|
full_certs: Vec<Cert>,
|
||||||
root: Option<Cert>,
|
root: Option<Cert>,
|
||||||
pm: PromptManager,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Keyring {
|
impl Keyring {
|
||||||
pub fn new(certs: impl AsRef<[Cert]>) -> Result<Self> {
|
pub fn new(certs: impl AsRef<[Cert]>) -> Self {
|
||||||
Ok(Self {
|
Self {
|
||||||
full_certs: certs.as_ref().to_vec(),
|
full_certs: certs.as_ref().to_vec(),
|
||||||
root: Default::default(),
|
root: Default::default(),
|
||||||
pm: PromptManager::new("keyfork-shard", None)?,
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.full_certs.is_empty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the root cert, returning the old cert
|
// Sets the root cert, returning the old cert
|
||||||
|
@ -91,9 +88,8 @@ impl VerificationHelper for &mut Keyring {
|
||||||
MessageLayer::SignatureGroup { results } => {
|
MessageLayer::SignatureGroup { results } => {
|
||||||
for result in results {
|
for result in results {
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
// FIXME: anyhow leak: VerificationError impl std::error::Error
|
// FIXME: anyhow leak
|
||||||
// return Err(e.context("Invalid signature"));
|
return Err(anyhow::anyhow!(e.to_string()));
|
||||||
return Err(anyhow::anyhow!("Invalid signature: {e}"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,46 +110,14 @@ impl DecryptionHelper for &mut Keyring {
|
||||||
where
|
where
|
||||||
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
|
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
|
||||||
{
|
{
|
||||||
let null = NullPolicy::new();
|
// optimized route: use all locally stored certs
|
||||||
// unoptimized route: use all locally stored certs
|
|
||||||
for pkesk in pkesks {
|
for pkesk in pkesks {
|
||||||
for cert in self.get_certs_for_pkesk(pkesk) {
|
for cert in self.get_certs_for_pkesk(pkesk) {
|
||||||
#[allow(deprecated, clippy::map_flatten)]
|
for key in cert.keys().secret() {
|
||||||
let name = cert
|
|
||||||
.userids()
|
|
||||||
.next()
|
|
||||||
.map(|userid| userid.userid().name().transpose())
|
|
||||||
.flatten()
|
|
||||||
.transpose()
|
|
||||||
.ok()
|
|
||||||
.flatten();
|
|
||||||
for key in cert
|
|
||||||
.keys()
|
|
||||||
.with_policy(&null, None)
|
|
||||||
.for_storage_encryption()
|
|
||||||
.secret()
|
|
||||||
{
|
|
||||||
let secret_key = key.key().clone();
|
let secret_key = key.key().clone();
|
||||||
let mut keypair = if secret_key.has_unencrypted_secret() {
|
// NOTE: Returns an error if using an encrypted secret key.
|
||||||
secret_key
|
// TODO: support skipping or validating encrypted secret keys.
|
||||||
.into_keypair()
|
let mut keypair = secret_key.into_keypair()?;
|
||||||
.context("Has unencrypted secret")?
|
|
||||||
} else {
|
|
||||||
let message = if let Some(name) = name.as_ref() {
|
|
||||||
format!("Decryption key for: {} ({name})", secret_key.keyid())
|
|
||||||
} else {
|
|
||||||
format!("Decryption key for: {}", secret_key.keyid())
|
|
||||||
};
|
|
||||||
let passphrase = self
|
|
||||||
.pm
|
|
||||||
.prompt_passphrase("Decryption passphrase", message)
|
|
||||||
.context("Decryption passphrase")?;
|
|
||||||
secret_key
|
|
||||||
.decrypt_secret(&passphrase.expose_secret().as_str().into())
|
|
||||||
.context("has_unencrypted_secret is false, could not decrypt secret")?
|
|
||||||
.into_keypair()
|
|
||||||
.context("just-decrypted key")?
|
|
||||||
};
|
|
||||||
if pkesk
|
if pkesk
|
||||||
.decrypt(&mut keypair, sym_algo)
|
.decrypt(&mut keypair, sym_algo)
|
||||||
.map(|(algo, sk)| decrypt(algo, &sk))
|
.map(|(algo, sk)| decrypt(algo, &sk))
|
||||||
|
@ -165,6 +129,8 @@ impl DecryptionHelper for &mut Keyring {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(Error::SecretKeyNotFound.into())
|
// smartcard route: plug in smartcard, attempt decrypt, fail and bail
|
||||||
|
|
||||||
|
Err(KeyringFailure::SecretKeyNotFound.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,261 +0,0 @@
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use keyfork_pinentry::ExposeSecret;
|
|
||||||
|
|
||||||
use super::openpgp::{
|
|
||||||
self,
|
|
||||||
cert::Cert,
|
|
||||||
packet::{PKESK, SKESK},
|
|
||||||
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
|
||||||
Fingerprint,
|
|
||||||
};
|
|
||||||
use crate::prompt_manager::{PinentryError, PromptManager};
|
|
||||||
|
|
||||||
use anyhow::Context;
|
|
||||||
use card_backend_pcsc::PcscBackend;
|
|
||||||
use openpgp_card_sequoia::{state::Open, types::Error as SequoiaCardError, Card};
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("No smart card backend was stored")]
|
|
||||||
SmartCardNotFound,
|
|
||||||
|
|
||||||
#[error("Selected smart card has no decryption key")]
|
|
||||||
SmartCardHasNoDecrypt,
|
|
||||||
|
|
||||||
#[error("Smart card backend error: {0}")]
|
|
||||||
SmartCardBackend(#[from] card_backend::SmartcardError),
|
|
||||||
|
|
||||||
#[error("Smartcard password status unavailable: {0}")]
|
|
||||||
PwStatusBytes(SequoiaCardError),
|
|
||||||
|
|
||||||
#[error("Could not open smart card")]
|
|
||||||
OpenSmartCard(SequoiaCardError),
|
|
||||||
|
|
||||||
#[error("Could not initialize transaction")]
|
|
||||||
Transaction(SequoiaCardError),
|
|
||||||
|
|
||||||
#[error("Could not load fingerprints")]
|
|
||||||
Fingerprints(SequoiaCardError),
|
|
||||||
|
|
||||||
#[error("Invalid PIN entered too many times")]
|
|
||||||
InvalidPIN,
|
|
||||||
|
|
||||||
#[error("Prompt failed: {0}")]
|
|
||||||
Prompt(#[from] PinentryError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
fn format_name(input: impl AsRef<str>) -> String {
|
|
||||||
let mut n = input
|
|
||||||
.as_ref()
|
|
||||||
.split("<<")
|
|
||||||
.take(2)
|
|
||||||
.map(|s| s.replace('>', " "))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
n.reverse();
|
|
||||||
n.join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SmartcardManager {
|
|
||||||
current_card: Option<Card<Open>>,
|
|
||||||
root: Option<Cert>,
|
|
||||||
pm: PromptManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SmartcardManager {
|
|
||||||
pub fn new() -> Result<Self> {
|
|
||||||
Ok(Self {
|
|
||||||
current_card: None,
|
|
||||||
root: None,
|
|
||||||
pm: PromptManager::new("keyfork-shard", None)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the root cert, returning the old cert
|
|
||||||
pub fn set_root_cert(&mut self, cert: impl Into<Option<Cert>>) -> Option<Cert> {
|
|
||||||
let mut cert = cert.into();
|
|
||||||
std::mem::swap(&mut self.root, &mut cert);
|
|
||||||
cert
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load any backend.
|
|
||||||
pub fn load_any_card(&mut self) -> Result<Fingerprint> {
|
|
||||||
PcscBackend::cards(None)?
|
|
||||||
.next()
|
|
||||||
.transpose()?
|
|
||||||
.ok_or(Error::SmartCardNotFound)
|
|
||||||
.and_then(|backend| {
|
|
||||||
let mut card = Card::<Open>::new(backend).map_err(Error::OpenSmartCard)?;
|
|
||||||
let transaction = card.transaction().map_err(Error::Transaction)?;
|
|
||||||
let fingerprint = transaction
|
|
||||||
.fingerprints()
|
|
||||||
.map_err(Error::Fingerprints)?
|
|
||||||
.decryption()
|
|
||||||
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
|
|
||||||
.ok_or(Error::SmartCardHasNoDecrypt)?;
|
|
||||||
drop(transaction);
|
|
||||||
self.current_card.replace(card);
|
|
||||||
Ok(fingerprint)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load a backend if any [`Fingerprint`] has been matched by a currently active card.
|
|
||||||
///
|
|
||||||
/// NOTE: Only implemented for decryption keys.
|
|
||||||
pub fn load_any_fingerprint(
|
|
||||||
&mut self,
|
|
||||||
fingerprints: impl IntoIterator<Item = Fingerprint>,
|
|
||||||
) -> Result<Option<Fingerprint>> {
|
|
||||||
// NOTE: This can't be HashSet::from_iter() because from_iter() requires a passed-in state
|
|
||||||
// I do not want to provide.
|
|
||||||
let mut requested_fingerprints = HashSet::new();
|
|
||||||
requested_fingerprints.extend(fingerprints);
|
|
||||||
|
|
||||||
let mut had_any_backend = false;
|
|
||||||
|
|
||||||
while !had_any_backend {
|
|
||||||
// Load all backends, confirm if any have any fingerprints
|
|
||||||
for backend in PcscBackend::cards(None)? {
|
|
||||||
had_any_backend = true;
|
|
||||||
let backend = backend?;
|
|
||||||
let mut card = Card::<Open>::new(backend).map_err(Error::OpenSmartCard)?;
|
|
||||||
let transaction = card.transaction().map_err(Error::Transaction)?;
|
|
||||||
let mut fingerprint = None;
|
|
||||||
if let Some(fp) = transaction
|
|
||||||
.fingerprints()
|
|
||||||
.map_err(Error::Fingerprints)?
|
|
||||||
.decryption()
|
|
||||||
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
|
|
||||||
{
|
|
||||||
if requested_fingerprints.contains(&fp) {
|
|
||||||
fingerprint.replace(fp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
drop(transaction);
|
|
||||||
if fingerprint.is_some() {
|
|
||||||
self.current_card.replace(card);
|
|
||||||
return Ok(fingerprint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
self.pm.prompt_message("Please plug in a smart card and press enter")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VerificationHelper for &mut SmartcardManager {
|
|
||||||
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
|
||||||
Ok(ids
|
|
||||||
.iter()
|
|
||||||
.flat_map(|kh| self.root.as_ref().filter(|cert| cert.key_handle() == *kh))
|
|
||||||
.cloned()
|
|
||||||
.collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> {
|
|
||||||
for layer in structure.into_iter() {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
match layer {
|
|
||||||
MessageLayer::Compression { algo } => {}
|
|
||||||
MessageLayer::Encryption {
|
|
||||||
sym_algo,
|
|
||||||
aead_algo,
|
|
||||||
} => {}
|
|
||||||
MessageLayer::SignatureGroup { results } => {
|
|
||||||
for result in results {
|
|
||||||
if let Err(e) = result {
|
|
||||||
// FIXME: anyhow leak
|
|
||||||
return Err(anyhow::anyhow!("Verification error: {}", e.to_string()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DecryptionHelper for &mut SmartcardManager {
|
|
||||||
fn decrypt<D>(
|
|
||||||
&mut self,
|
|
||||||
pkesks: &[PKESK],
|
|
||||||
_skesks: &[SKESK],
|
|
||||||
sym_algo: Option<openpgp::types::SymmetricAlgorithm>,
|
|
||||||
mut decrypt: D,
|
|
||||||
) -> openpgp::Result<Option<Fingerprint>>
|
|
||||||
where
|
|
||||||
D: FnMut(openpgp::types::SymmetricAlgorithm, &openpgp::crypto::SessionKey) -> bool,
|
|
||||||
{
|
|
||||||
let mut card = self.current_card.take();
|
|
||||||
let Some(card) = card.as_mut() else {
|
|
||||||
return Err(Error::SmartCardNotFound.into());
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut transaction = card
|
|
||||||
.transaction()
|
|
||||||
.context("Could not initialize transaction")?;
|
|
||||||
let fp = transaction
|
|
||||||
.fingerprints()
|
|
||||||
.context("Could not load fingerprints")?
|
|
||||||
.decryption()
|
|
||||||
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
|
|
||||||
.ok_or(Error::SmartCardHasNoDecrypt)?;
|
|
||||||
let cardholder_name = format_name(
|
|
||||||
transaction
|
|
||||||
.cardholder_name()
|
|
||||||
.context("Could not load (optionally empty) cardholder name")?,
|
|
||||||
);
|
|
||||||
let card_id = transaction
|
|
||||||
.application_identifier()
|
|
||||||
.context("Could not load application identifier")?
|
|
||||||
.ident();
|
|
||||||
let pw_status = transaction
|
|
||||||
.pw_status_bytes()
|
|
||||||
.map_err(Error::PwStatusBytes)?;
|
|
||||||
let mut pin = None;
|
|
||||||
for _ in 0..pw_status.err_count_pw1() {
|
|
||||||
transaction.reload_ard()?;
|
|
||||||
let attempts = transaction
|
|
||||||
.pw_status_bytes()
|
|
||||||
.map_err(Error::PwStatusBytes)?
|
|
||||||
.err_count_pw1();
|
|
||||||
let rpea = "Remaining PIN entry attempts";
|
|
||||||
let message = if cardholder_name.is_empty() {
|
|
||||||
format!("Unlock card {card_id}\n\n{rpea}: {attempts}")
|
|
||||||
} else {
|
|
||||||
format!("Unlock card {card_id} ({cardholder_name})\n\n{rpea}: {attempts}")
|
|
||||||
};
|
|
||||||
let temp_pin = self.pm.prompt_passphrase("Smartcard User PIN", message)?;
|
|
||||||
if transaction
|
|
||||||
.verify_user_pin(temp_pin.expose_secret().as_str().trim())
|
|
||||||
.is_ok()
|
|
||||||
{
|
|
||||||
pin.replace(temp_pin);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let pin = pin.ok_or(Error::InvalidPIN)?;
|
|
||||||
let mut user = transaction
|
|
||||||
.to_user_card(pin.expose_secret().as_str().trim())
|
|
||||||
.context("Could not load user smartcard from PIN")?;
|
|
||||||
let mut decryptor = user
|
|
||||||
.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))
|
|
||||||
.context("Could not decrypt using smartcard")?;
|
|
||||||
for pkesk in pkesks {
|
|
||||||
if pkesk
|
|
||||||
.decrypt(&mut decryptor, sym_algo)
|
|
||||||
.map(|(algo, sk)| decrypt(algo, &sk))
|
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
|
||||||
return Ok(Some(fp));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::SmartCardNotFound.into())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use keyfork_pinentry::{
|
|
||||||
self, default_binary, ConfirmationDialog, MessageDialog, PassphraseInput, SecretString,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum PinentryError {
|
|
||||||
#[error("No pinentry binary found")]
|
|
||||||
NoPinentryFound,
|
|
||||||
|
|
||||||
#[error("{0}")]
|
|
||||||
Internal(#[from] keyfork_pinentry::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Result<T, E = PinentryError> = std::result::Result<T, E>;
|
|
||||||
|
|
||||||
/// Display message dialogues, confirmation prompts, and passphrase inputs with keyfork-pinentry.
|
|
||||||
pub struct PromptManager {
|
|
||||||
program_title: String,
|
|
||||||
pinentry_binary: PathBuf,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PromptManager {
|
|
||||||
pub fn new(
|
|
||||||
program_title: impl Into<String>,
|
|
||||||
pinentry_binary: impl Into<Option<PathBuf>>,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let path = match pinentry_binary.into() {
|
|
||||||
Some(p) => p,
|
|
||||||
None => default_binary()?,
|
|
||||||
};
|
|
||||||
std::fs::metadata(&path).map_err(|_| PinentryError::NoPinentryFound)?;
|
|
||||||
Ok(Self {
|
|
||||||
program_title: program_title.into(),
|
|
||||||
pinentry_binary: path,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn prompt_confirmation(&self, prompt: impl AsRef<str>) -> Result<bool> {
|
|
||||||
ConfirmationDialog::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.confirm(prompt.as_ref())
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_message(&self, prompt: impl AsRef<str>) -> Result<()> {
|
|
||||||
MessageDialog::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.show_message(prompt.as_ref())
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn prompt_passphrase(
|
|
||||||
&self,
|
|
||||||
prompt: impl AsRef<str>,
|
|
||||||
description: impl Into<Option<String>>,
|
|
||||||
) -> Result<SecretString> {
|
|
||||||
match description.into() {
|
|
||||||
Some(desc) => PassphraseInput::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.with_prompt(prompt.as_ref())
|
|
||||||
.with_description(&desc)
|
|
||||||
.interact()
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
None => PassphraseInput::with_binary(self.pinentry_binary.clone())
|
|
||||||
.with_title(&self.program_title)
|
|
||||||
.with_prompt(prompt.as_ref())
|
|
||||||
.interact()
|
|
||||||
.map_err(|e| e.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -219,67 +219,37 @@ pub fn test_data() -> Result<HashMap<String, Vec<TestData>>, Box<dyn std::error:
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'",
|
chain: "m/0'",
|
||||||
fingerprint: hex::decode("ddebc675")?,
|
fingerprint: hex::decode("ddebc675")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69")?,
|
||||||
"8b59aa11380b624e81507a27fedda59fea6d0b779a778918a2fd3590e16e9c69",
|
private_key: hex::decode("68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3")?,
|
||||||
)?,
|
public_key: hex::decode("008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"68e0fe46dfb67e368c75379acec591dad19df3cde26e63b93a8e704f1dade7a3",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"008c8a13df77a28f3445213a0f432fde644acaa215fc72dcdf300d5efaa85d350c",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/1'",
|
chain: "m/0'/1'",
|
||||||
fingerprint: hex::decode("13dab143")?,
|
fingerprint: hex::decode("13dab143")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14")?,
|
||||||
"a320425f77d1b5c2505a6b1b27382b37368ee640e3557c315416801243552f14",
|
private_key: hex::decode("b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2")?,
|
||||||
)?,
|
public_key: hex::decode("001932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"b1d0bad404bf35da785a64ca1ac54b2617211d2777696fbffaf208f746ae84f2",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"001932a5270f335bed617d5b935c80aedb1a35bd9fc1e31acafd5372c30f5c1187",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/1'/2'",
|
chain: "m/0'/1'/2'",
|
||||||
fingerprint: hex::decode("ebe4cb29")?,
|
fingerprint: hex::decode("ebe4cb29")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("2e69929e00b5ab250f49c3fb1c12f252de4fed2c1db88387094a0f8c4c9ccd6c")?,
|
||||||
"2e69929e00b5ab250f49c3fb1c12f252de4fed2c1db88387094a0f8c4c9ccd6c",
|
private_key: hex::decode("92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9")?,
|
||||||
)?,
|
public_key: hex::decode("00ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"92a5b23c0b8a99e37d07df3fb9966917f5d06e02ddbd909c7e184371463e9fc9",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"00ae98736566d30ed0e9d2f4486a64bc95740d89c7db33f52121f8ea8f76ff0fc1",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/1'/2'/2'",
|
chain: "m/0'/1'/2'/2'",
|
||||||
fingerprint: hex::decode("316ec1c6")?,
|
fingerprint: hex::decode("316ec1c6")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("8f6d87f93d750e0efccda017d662a1b31a266e4a6f5993b15f5c1f07f74dd5cc")?,
|
||||||
"8f6d87f93d750e0efccda017d662a1b31a266e4a6f5993b15f5c1f07f74dd5cc",
|
private_key: hex::decode("30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662")?,
|
||||||
)?,
|
public_key: hex::decode("008abae2d66361c879b900d204ad2cc4984fa2aa344dd7ddc46007329ac76c429c")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"30d1dc7e5fc04c31219ab25a27ae00b50f6fd66622f6e9c913253d6511d1e662",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"008abae2d66361c879b900d204ad2cc4984fa2aa344dd7ddc46007329ac76c429c",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/1'/2'/2'/1000000000'",
|
chain: "m/0'/1'/2'/2'/1000000000'",
|
||||||
fingerprint: hex::decode("d6322ccd")?,
|
fingerprint: hex::decode("d6322ccd")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230")?,
|
||||||
"68789923a0cac2cd5a29172a475fe9e0fb14cd6adb5ad98a3fa70333e7afa230",
|
private_key: hex::decode("8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793")?,
|
||||||
)?,
|
public_key: hex::decode("003c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"8f94d394a8e8fd6b1bc2f3f49f5c47e385281d5c17e65324b0f62483e37e8793",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"003c24da049451555d51a7014a37337aa4e12d41e485abccfa46b47dfb2af54b7a",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -289,80 +259,44 @@ pub fn test_data() -> Result<HashMap<String, Vec<TestData>>, Box<dyn std::error:
|
||||||
Test {
|
Test {
|
||||||
chain: "m",
|
chain: "m",
|
||||||
fingerprint: hex::decode("00000000")?,
|
fingerprint: hex::decode("00000000")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b")?,
|
||||||
"ef70a74db9c3a5af931b5fe73ed8e1a53464133654fd55e7a66f8570b8e33c3b",
|
private_key: hex::decode("171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012")?,
|
||||||
)?,
|
public_key: hex::decode("008fe9693f8fa62a4305a140b9764c5ee01e455963744fe18204b4fb948249308a")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"171cb88b1b3c1db25add599712e36245d75bc65a1a5c9e18d76f9f2b1eab4012",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"008fe9693f8fa62a4305a140b9764c5ee01e455963744fe18204b4fb948249308a",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'",
|
chain: "m/0'",
|
||||||
fingerprint: hex::decode("31981b50")?,
|
fingerprint: hex::decode("31981b50")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d")?,
|
||||||
"0b78a3226f915c082bf118f83618a618ab6dec793752624cbeb622acb562862d",
|
private_key: hex::decode("1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635")?,
|
||||||
)?,
|
public_key: hex::decode("0086fab68dcb57aa196c77c5f264f215a112c22a912c10d123b0d03c3c28ef1037")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"1559eb2bbec5790b0c65d8693e4d0875b1747f4970ae8b650486ed7470845635",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"0086fab68dcb57aa196c77c5f264f215a112c22a912c10d123b0d03c3c28ef1037",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/2147483647'",
|
chain: "m/0'/2147483647'",
|
||||||
fingerprint: hex::decode("1e9411b1")?,
|
fingerprint: hex::decode("1e9411b1")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f")?,
|
||||||
"138f0b2551bcafeca6ff2aa88ba8ed0ed8de070841f0c4ef0165df8181eaad7f",
|
private_key: hex::decode("ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4")?,
|
||||||
)?,
|
public_key: hex::decode("005ba3b9ac6e90e83effcd25ac4e58a1365a9e35a3d3ae5eb07b9e4d90bcf7506d")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"ea4f5bfe8694d8bb74b7b59404632fd5968b774ed545e810de9c32a4fb4192f4",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"005ba3b9ac6e90e83effcd25ac4e58a1365a9e35a3d3ae5eb07b9e4d90bcf7506d",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/2147483647'/1'",
|
chain: "m/0'/2147483647'/1'",
|
||||||
fingerprint: hex::decode("fcadf38c")?,
|
fingerprint: hex::decode("fcadf38c")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("73bd9fff1cfbde33a1b846c27085f711c0fe2d66fd32e139d3ebc28e5a4a6b90")?,
|
||||||
"73bd9fff1cfbde33a1b846c27085f711c0fe2d66fd32e139d3ebc28e5a4a6b90",
|
private_key: hex::decode("3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c")?,
|
||||||
)?,
|
public_key: hex::decode("002e66aa57069c86cc18249aecf5cb5a9cebbfd6fadeab056254763874a9352b45")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"3757c7577170179c7868353ada796c839135b3d30554bbb74a4b1e4a5a58505c",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"002e66aa57069c86cc18249aecf5cb5a9cebbfd6fadeab056254763874a9352b45",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/2147483647'/1'/2147483646'",
|
chain: "m/0'/2147483647'/1'/2147483646'",
|
||||||
fingerprint: hex::decode("aca70953")?,
|
fingerprint: hex::decode("aca70953")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("0902fe8a29f9140480a00ef244bd183e8a13288e4412d8389d140aac1794825a")?,
|
||||||
"0902fe8a29f9140480a00ef244bd183e8a13288e4412d8389d140aac1794825a",
|
private_key: hex::decode("5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72")?,
|
||||||
)?,
|
public_key: hex::decode("00e33c0f7d81d843c572275f287498e8d408654fdf0d1e065b84e2e6f157aab09b")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"5837736c89570de861ebc173b1086da4f505d4adb387c6a1b1342d5e4ac9ec72",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"00e33c0f7d81d843c572275f287498e8d408654fdf0d1e065b84e2e6f157aab09b",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
Test {
|
Test {
|
||||||
chain: "m/0'/2147483647'/1'/2147483646'/2'",
|
chain: "m/0'/2147483647'/1'/2147483646'/2'",
|
||||||
fingerprint: hex::decode("422c654b")?,
|
fingerprint: hex::decode("422c654b")?,
|
||||||
chain_code: hex::decode(
|
chain_code: hex::decode("5d70af781f3a37b829f0d060924d5e960bdc02e85423494afc0b1a41bbe196d4")?,
|
||||||
"5d70af781f3a37b829f0d060924d5e960bdc02e85423494afc0b1a41bbe196d4",
|
private_key: hex::decode("551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d")?,
|
||||||
)?,
|
public_key: hex::decode("0047150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0")?,
|
||||||
private_key: hex::decode(
|
|
||||||
"551d333177df541ad876a60ea71f00447931c0a9da16f227c11ea080d7391b8d",
|
|
||||||
)?,
|
|
||||||
public_key: hex::decode(
|
|
||||||
"0047150c75db263559a70d5778bf36abbab30fb061ad69f69ece61a72b0cfa4fc0",
|
|
||||||
)?,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,20 +8,16 @@ Combine `threshold` shares into a previously [`split`] secret.
|
||||||
|
|
||||||
* `threshold`: Minimum number of operators present to recover the secret, as
|
* `threshold`: Minimum number of operators present to recover the secret, as
|
||||||
previously configured when creating the secret
|
previously configured when creating the secret
|
||||||
* `key_discovery`: A directory containing OpenPGP keys.
|
* `key_discovery`: Either a file or a directory containing OpenPGP keys.
|
||||||
|
If a file, load all keys from the file.
|
||||||
|
If a directory, for every file in the directory (non-recursively), load
|
||||||
|
keys from the file.
|
||||||
If the amount of keys found is less than `threshold`, an OpenPGP Card
|
If the amount of keys found is less than `threshold`, an OpenPGP Card
|
||||||
fallback will be used to decrypt the rest of the messages.
|
fallback will be used to decrypt the rest of the messages.
|
||||||
|
|
||||||
## Pinentry
|
|
||||||
|
|
||||||
The terminal may be overridden if the default pinentry command is
|
|
||||||
`pinentry-curses`, but this will affect neither input nor output.` Pinentry is
|
|
||||||
used if an OpenPGP key file has an encrypted secret key or to prompt for the
|
|
||||||
PIN for an OpenPGP smart card.
|
|
||||||
|
|
||||||
## Input
|
## Input
|
||||||
|
|
||||||
OpenPGP messages from [`split`].
|
OpenPGP Messages from [`split`].
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::Keyfork;
|
use super::Keyfork;
|
||||||
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
|
use clap::{Parser, Subcommand, ValueEnum, builder::PossibleValue};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl ShardExec for OpenPGP {
|
||||||
"cert count {} != max {max}",
|
"cert count {} != max {max}",
|
||||||
certs.len()
|
certs.len()
|
||||||
);
|
);
|
||||||
keyfork_shard::openpgp::split(threshold, certs, secret, output).map_err(Into::into)
|
keyfork_shard::openpgp::split(threshold, certs, secret, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn combine<T>(
|
fn combine<T>(
|
||||||
|
|
|
@ -7,7 +7,5 @@ mod cli;
|
||||||
fn main() {
|
fn main() {
|
||||||
let opts = cli::Keyfork::parse();
|
let opts = cli::Keyfork::parse();
|
||||||
|
|
||||||
opts.command
|
opts.command.handle(&opts).expect("Unable to handle command");
|
||||||
.handle(&opts)
|
|
||||||
.expect("Unable to handle command");
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,6 @@ secp256k1 = ["keyfork-derive-util/secp256k1"]
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", default-features = false }
|
keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", default-features = false }
|
||||||
keyfork-frame = { version = "0.1.0", path = "../keyfork-frame" }
|
keyfork-frame = { version = "0.1.0", path = "../keyfork-frame" }
|
||||||
keyforkd-models = { version = "0.1.0", path = "../keyforkd-models" }
|
|
||||||
thiserror = "1.0.49"
|
thiserror = "1.0.49"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf};
|
use std::{collections::HashMap, os::unix::net::UnixStream, path::PathBuf};
|
||||||
|
|
||||||
|
use keyfork_derive_util::request::{DerivationRequest, DerivationResponse};
|
||||||
use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError};
|
use keyfork_frame::{try_decode_from, try_encode_to, DecodeError, EncodeError};
|
||||||
use keyforkd_models::{Request, Response, Error as KeyforkdError};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
@ -25,9 +25,6 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("Could not perform frame transformation: {0}")]
|
#[error("Could not perform frame transformation: {0}")]
|
||||||
FrameDec(#[from] DecodeError),
|
FrameDec(#[from] DecodeError),
|
||||||
|
|
||||||
#[error("Error in Keyforkd: {0}")]
|
|
||||||
Keyforkd(#[from] KeyforkdError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||||
|
@ -54,32 +51,23 @@ pub fn get_socket() -> Result<UnixStream, Error> {
|
||||||
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
|
UnixStream::connect(&socket_path).map_err(|e| Error::Connect(e, socket_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A client to interact with Keyforkd.
|
|
||||||
///
|
|
||||||
/// Upon creation, a socket is opened, and is kept open for the duration of the object's lifetime.
|
|
||||||
/// Currently, Keyforkd does not support the reuse of sockets. Attempting to reuse the socket after
|
|
||||||
/// previously using it will likely result in an error.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
socket: UnixStream,
|
socket: UnixStream,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// Create a new client from a given already-connected [`UnixStream`].
|
|
||||||
pub fn new(socket: UnixStream) -> Self {
|
pub fn new(socket: UnixStream) -> Self {
|
||||||
Self { socket }
|
Self { socket }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new client using well-known socket locations.
|
|
||||||
pub fn discover_socket() -> Result<Self> {
|
pub fn discover_socket() -> Result<Self> {
|
||||||
get_socket().map(|socket| Self { socket })
|
get_socket().map(|socket| Self { socket })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
|
pub fn request(&mut self, req: &DerivationRequest) -> Result<DerivationResponse> {
|
||||||
pub fn request(&mut self, req: &Request) -> Result<Response> {
|
|
||||||
try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?;
|
try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?;
|
||||||
let resp = try_decode_from(&mut self.socket)?;
|
let resp = try_decode_from(&mut self.socket)?;
|
||||||
let resp: Result<Response, KeyforkdError> = bincode::deserialize(&resp)?;
|
bincode::deserialize(&resp).map_err(From::from)
|
||||||
resp.map_err(From::from)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ fn secp256k1() {
|
||||||
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
||||||
let tempdir = TempDir::new("keyfork-seed").unwrap();
|
let tempdir = TempDir::new("keyfork-seed").unwrap();
|
||||||
for (i, per_seed) in tests.into_iter().enumerate() {
|
for (i, per_seed) in tests.into_iter().enumerate() {
|
||||||
|
|
||||||
let mut socket_name = i.to_string();
|
let mut socket_name = i.to_string();
|
||||||
socket_name.push_str("-keyforkd.sock");
|
socket_name.push_str("-keyforkd.sock");
|
||||||
let socket_path = tempdir.path().join(socket_name);
|
let socket_path = tempdir.path().join(socket_name);
|
||||||
|
@ -46,8 +47,7 @@ fn secp256k1() {
|
||||||
DerivationAlgorithm::Secp256k1,
|
DerivationAlgorithm::Secp256k1,
|
||||||
&DerivationPath::from_str(test.chain).unwrap(),
|
&DerivationPath::from_str(test.chain).unwrap(),
|
||||||
);
|
);
|
||||||
let response =
|
let response = client.request(&req).unwrap();
|
||||||
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
|
||||||
assert_eq!(response.data, test.private_key);
|
assert_eq!(response.data, test.private_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,11 +57,15 @@ fn secp256k1() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ed25519() {
|
fn ed25519() {
|
||||||
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
let tests = test_data()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&"ed25519".to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
let rt = Builder::new_multi_thread().enable_io().build().unwrap();
|
||||||
let tempdir = TempDir::new("keyfork-seed").unwrap();
|
let tempdir = TempDir::new("keyfork-seed").unwrap();
|
||||||
for (i, per_seed) in tests.into_iter().enumerate() {
|
for (i, per_seed) in tests.into_iter().enumerate() {
|
||||||
|
|
||||||
let mut socket_name = i.to_string();
|
let mut socket_name = i.to_string();
|
||||||
socket_name.push_str("-keyforkd.sock");
|
socket_name.push_str("-keyforkd.sock");
|
||||||
let socket_path = tempdir.path().join(socket_name);
|
let socket_path = tempdir.path().join(socket_name);
|
||||||
|
@ -91,8 +95,7 @@ fn ed25519() {
|
||||||
DerivationAlgorithm::Ed25519,
|
DerivationAlgorithm::Ed25519,
|
||||||
&DerivationPath::from_str(test.chain).unwrap(),
|
&DerivationPath::from_str(test.chain).unwrap(),
|
||||||
);
|
);
|
||||||
let response =
|
let response = client.request(&req).unwrap();
|
||||||
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
|
||||||
assert_eq!(response.data, test.private_key);
|
assert_eq!(response.data, test.private_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "keyforkd-models"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", default-features = false }
|
|
||||||
serde = { version = "1.0.190", features = ["derive"] }
|
|
||||||
thiserror = "1.0.50"
|
|
|
@ -1,84 +0,0 @@
|
||||||
//! All values that can be sent to and returned from Keyforkd.
|
|
||||||
//!
|
|
||||||
//! All Error values are stored as enum unit values.
|
|
||||||
|
|
||||||
use keyfork_derive_util::request::{DerivationRequest, DerivationResponse};
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// A request made to Keyforkd.
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub enum Request {
|
|
||||||
/// A derivation request.
|
|
||||||
Derivation(DerivationRequest),
|
|
||||||
|
|
||||||
/// A derivation request, with a TTY provided for pinentry if necessary.
|
|
||||||
DerivationWithTTY(DerivationRequest, String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DerivationRequest> for Request {
|
|
||||||
fn from(value: DerivationRequest) -> Self {
|
|
||||||
Self::Derivation(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(DerivationRequest, String)> for Request {
|
|
||||||
fn from((request, tty): (DerivationRequest, String)) -> Self {
|
|
||||||
Self::DerivationWithTTY(request, tty)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub enum DerivationError {
|
|
||||||
#[error("The provided TTY was not valid")]
|
|
||||||
InvalidTTY,
|
|
||||||
|
|
||||||
#[error("A TTY was required by the pinentry program but was not provided")]
|
|
||||||
NoTTY,
|
|
||||||
|
|
||||||
#[error("Invalid derivation length: Expected 2, actual: {0}")]
|
|
||||||
InvalidDerivationLength(usize),
|
|
||||||
|
|
||||||
#[error("Derivation error: {0}")]
|
|
||||||
Derivation(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Derivation(#[from] DerivationError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Any response from a Keyforkd request.
|
|
||||||
///
|
|
||||||
/// Responses can be converted to their inner values without matching the Response type by
|
|
||||||
/// using a type annotation and [`TryInto::try_into`].
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Response {
|
|
||||||
/// A derivation response.
|
|
||||||
///
|
|
||||||
/// From:
|
|
||||||
/// * [`Request::Derivation`]
|
|
||||||
/// * [`Request::DerivationWithTTY`]
|
|
||||||
Derivation(DerivationResponse),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
#[error("Unable to downcast to {0}")]
|
|
||||||
pub struct Downcast(&'static str);
|
|
||||||
|
|
||||||
use std::any::type_name;
|
|
||||||
|
|
||||||
impl TryFrom<Response> for DerivationResponse {
|
|
||||||
type Error = Downcast;
|
|
||||||
|
|
||||||
fn try_from(value: Response) -> Result<Self, Self::Error> {
|
|
||||||
#[allow(irrefutable_let_patterns)]
|
|
||||||
if let Response::Derivation(response) = value {
|
|
||||||
Ok(response)
|
|
||||||
} else {
|
|
||||||
Err(Downcast(type_name::<Self>()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ tower = { version = "0.4.13", features = ["tokio", "util"] }
|
||||||
thiserror = "1.0.47"
|
thiserror = "1.0.47"
|
||||||
serde = { version = "1.0.186", features = ["derive"] }
|
serde = { version = "1.0.186", features = ["derive"] }
|
||||||
keyfork-derive-path-data = { version = "0.1.0", path = "../keyfork-derive-path-data" }
|
keyfork-derive-path-data = { version = "0.1.0", path = "../keyfork-derive-path-data" }
|
||||||
keyforkd-models = { version = "0.1.0", path = "../keyforkd-models" }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
|
|
@ -59,7 +59,7 @@ where
|
||||||
S: Service<Request>,
|
S: Service<Request>,
|
||||||
Request: DeserializeOwned,
|
Request: DeserializeOwned,
|
||||||
<S as Service<Request>>::Response: Serialize,
|
<S as Service<Request>>::Response: Serialize,
|
||||||
<S as Service<Request>>::Error: std::error::Error + Serialize,
|
<S as Service<Request>>::Error: std::error::Error,
|
||||||
<S as Service<Request>>::Future: Send + 'static,
|
<S as Service<Request>>::Future: Send + 'static,
|
||||||
{
|
{
|
||||||
type Response = Vec<u8>;
|
type Response = Vec<u8>;
|
||||||
|
@ -86,11 +86,9 @@ where
|
||||||
let response = self.service.call(request);
|
let response = self.service.call(request);
|
||||||
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let response = response.await;
|
let response = response
|
||||||
#[cfg(feature = "tracing")]
|
.await
|
||||||
if let Err(e) = &response {
|
.map_err(|e| BincodeServiceError::Call(e.to_string()))?;
|
||||||
tracing::error!("Error performing derivation: {e}");
|
|
||||||
}
|
|
||||||
serialize(&response).map_err(|e| BincodeServiceError::Convert(e.to_string()))
|
serialize(&response).map_err(|e| BincodeServiceError::Convert(e.to_string()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -118,7 +116,7 @@ mod tests {
|
||||||
|
|
||||||
struct App;
|
struct App;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Serialize)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum Infallible {}
|
enum Infallible {}
|
||||||
|
|
||||||
impl Service<Test> for App {
|
impl Service<Test> for App {
|
||||||
|
@ -143,8 +141,7 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn can_serde_responses() {
|
async fn can_serde_responses() {
|
||||||
let test = Test::new();
|
let content = serialize(&Test::new()).unwrap();
|
||||||
let content = serialize(&test).unwrap();
|
|
||||||
let mut service = ServiceBuilder::new()
|
let mut service = ServiceBuilder::new()
|
||||||
.layer(BincodeLayer::<Test>::default())
|
.layer(BincodeLayer::<Test>::default())
|
||||||
.service(App);
|
.service(App);
|
||||||
|
@ -155,6 +152,6 @@ mod tests {
|
||||||
.call(content.clone())
|
.call(content.clone())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(result, serialize(&Result::<Test, Infallible>::Ok(test)).unwrap());
|
assert_eq!(result, content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
use std::{future::Future, pin::Pin, sync::Arc, task::Poll};
|
use std::{future::Future, pin::Pin, sync::Arc, task::Poll};
|
||||||
|
|
||||||
|
use keyfork_derive_util::request::{DerivationError, DerivationRequest, DerivationResponse};
|
||||||
use keyfork_derive_path_data::guess_target;
|
use keyfork_derive_path_data::guess_target;
|
||||||
// use keyfork_derive_util::request::{DerivationError, DerivationRequest, DerivationResponse};
|
|
||||||
use keyforkd_models::{DerivationError, Error, Request, Response};
|
|
||||||
use tower::Service;
|
use tower::Service;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
// NOTE: All values implemented in Keyforkd must implement Clone with low overhead, either by
|
// NOTE: All values implemented in Keyforkd must implement Clone with low overhead, either by
|
||||||
// using an Arc or by having a small signature. This is because Service<T> takes &mut self.
|
// using an Arc or by having a small signature. This is because Service<T> takes &mut self.
|
||||||
//
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum KeyforkdRequestError {
|
||||||
|
#[error("Invalid derivation length: Expected: 2, actual: {0}")]
|
||||||
|
InvalidDerivationLength(usize),
|
||||||
|
|
||||||
|
#[error("Derivation error: {0}")]
|
||||||
|
Derivation(#[from] DerivationError),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Keyforkd {
|
pub struct Keyforkd {
|
||||||
seed: Arc<Vec<u8>>,
|
seed: Arc<Vec<u8>>,
|
||||||
|
@ -22,11 +30,10 @@ impl Keyforkd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<Request> for Keyforkd {
|
impl Service<DerivationRequest> for Keyforkd {
|
||||||
type Response = Response;
|
type Response = DerivationResponse;
|
||||||
|
|
||||||
// TODO: indicate serialize in BincodeLayer
|
type Error = KeyforkdRequestError;
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||||
|
|
||||||
|
@ -38,28 +45,24 @@ impl Service<Request> for Keyforkd {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
|
#[cfg_attr(feature = "tracing", tracing::instrument(skip(self)))]
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&mut self, req: DerivationRequest) -> Self::Future {
|
||||||
let seed = self.seed.clone();
|
let seed = self.seed.clone();
|
||||||
match req {
|
Box::pin(async move {
|
||||||
Request::Derivation(req) => Box::pin(async move {
|
let len = req.path().len();
|
||||||
let len = req.path().len();
|
if len < 2 {
|
||||||
if len < 2 {
|
return Err(KeyforkdRequestError::InvalidDerivationLength(len));
|
||||||
return Err(DerivationError::InvalidDerivationLength(len).into());
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tracing")]
|
#[cfg(feature = "tracing")]
|
||||||
if let Some(target) = guess_target(req.path()) {
|
if let Some(target) = guess_target(req.path()) {
|
||||||
info!("Deriving path: {target}");
|
info!("Deriving path: {target}");
|
||||||
} else {
|
} else {
|
||||||
info!("Deriving path: {}", req.path());
|
info!("Deriving path: {}", req.path());
|
||||||
}
|
}
|
||||||
|
|
||||||
req.derive_with_master_seed((*seed).clone())
|
req.derive_with_master_seed((*seed).clone())
|
||||||
.map(Response::Derivation)
|
.map_err(KeyforkdRequestError::from)
|
||||||
.map_err(|e| DerivationError::Derivation(e.to_string()).into())
|
})
|
||||||
}),
|
|
||||||
Request::DerivationWithTTY(_, _) => todo!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,15 +92,7 @@ mod tests {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let req = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
|
let req = DerivationRequest::new(DerivationAlgorithm::Secp256k1, &chain);
|
||||||
let response: DerivationResponse = keyforkd
|
let response = keyforkd.ready().await.unwrap().call(req).await.unwrap();
|
||||||
.ready()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.call(Request::Derivation(req))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(response.data, test.private_key);
|
assert_eq!(response.data, test.private_key);
|
||||||
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
||||||
}
|
}
|
||||||
|
@ -106,7 +101,10 @@ mod tests {
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn properly_derives_ed25519() {
|
async fn properly_derives_ed25519() {
|
||||||
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
let tests = test_data()
|
||||||
|
.unwrap()
|
||||||
|
.remove(&"ed25519".to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
for per_seed in tests {
|
for per_seed in tests {
|
||||||
let seed = &per_seed.seed;
|
let seed = &per_seed.seed;
|
||||||
|
@ -119,15 +117,7 @@ mod tests {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain);
|
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &chain);
|
||||||
let response: DerivationResponse = keyforkd
|
let response = keyforkd.ready().await.unwrap().call(req).await.unwrap();
|
||||||
.ready()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.call(Request::Derivation(req))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(response.data, test.private_key);
|
assert_eq!(response.data, test.private_key);
|
||||||
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
assert_eq!(response.chain_code.as_slice(), test.chain_code);
|
||||||
}
|
}
|
||||||
|
@ -147,15 +137,7 @@ mod tests {
|
||||||
for (seed, path, _, private_key, _) in tests {
|
for (seed, path, _, private_key, _) in tests {
|
||||||
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
||||||
let mut keyforkd = Keyforkd::new(seed.to_vec());
|
let mut keyforkd = Keyforkd::new(seed.to_vec());
|
||||||
let response: DerivationResponse = keyforkd
|
let response = keyforkd.ready().await.unwrap().call(req).await.unwrap();
|
||||||
.ready()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.call(Request::Derivation(req))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(response.data, private_key)
|
assert_eq!(response.data, private_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,15 +155,7 @@ mod tests {
|
||||||
for (seed, path, _, private_key, _) in tests {
|
for (seed, path, _, private_key, _) in tests {
|
||||||
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
let req = DerivationRequest::new(DerivationAlgorithm::Ed25519, &path);
|
||||||
let mut keyforkd = Keyforkd::new(seed.to_vec());
|
let mut keyforkd = Keyforkd::new(seed.to_vec());
|
||||||
let response: DerivationResponse = keyforkd
|
let response = keyforkd.ready().await.unwrap().call(req).await.unwrap();
|
||||||
.ready()
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.call(Request::Derivation(req))
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.try_into()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(response.data, private_key)
|
assert_eq!(response.data, private_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue