keyfork-shard: add keyfork-pinentry
This commit is contained in:
parent
7f90e4ada4
commit
c206800ad2
|
@ -233,9 +233,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "buffered-reader"
|
name = "buffered-reader"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "66d3bea5bcc3ecc38fe5388e6bc35e6fe7bd665eb3ae9a44283e15b91ad3867d"
|
checksum = "2b9b0a25eb06e83579bc985d836e1e3b957a7201301b48538764d2b2e78090d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"flate2",
|
"flate2",
|
||||||
|
@ -478,7 +478,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"curve25519-dalek-derive",
|
"curve25519-dalek-derive",
|
||||||
"digest 0.10.7",
|
"digest",
|
||||||
"fiat-crypto",
|
"fiat-crypto",
|
||||||
"platforms",
|
"platforms",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
|
@ -524,15 +524,6 @@ 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"
|
||||||
|
@ -580,19 +571,10 @@ checksum = "a4b1e0c257a9e9f25f90ff76d7a68360ed497ee519c8e428d1825ef0000799d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"der 0.7.8",
|
"der 0.7.8",
|
||||||
"elliptic-curve",
|
"elliptic-curve",
|
||||||
"signature 2.1.0",
|
"signature",
|
||||||
"spki 0.7.2",
|
"spki 0.7.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "2.2.2"
|
version = "2.2.2"
|
||||||
|
@ -600,7 +582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
|
checksum = "60f6d271ca33075c88028be6f04d502853d63a5ece419d269c15315d4fc1cf1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkcs8 0.10.2",
|
"pkcs8 0.10.2",
|
||||||
"signature 2.1.0",
|
"signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -610,7 +592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
|
checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"ed25519 2.2.2",
|
"ed25519",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
@ -630,7 +612,7 @@ checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base16ct",
|
"base16ct",
|
||||||
"crypto-bigint",
|
"crypto-bigint",
|
||||||
"digest 0.10.7",
|
"digest",
|
||||||
"ff",
|
"ff",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
"group",
|
"group",
|
||||||
|
@ -762,19 +744,6 @@ 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"
|
||||||
|
@ -784,7 +753,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -862,7 +831,16 @@ 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 0.10.7",
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
|
@ -890,9 +868,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.3.0"
|
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 = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
|
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
|
@ -1033,7 +1011,7 @@ dependencies = [
|
||||||
name = "keyfork-derive-util"
|
name = "keyfork-derive-util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest 0.10.7",
|
"digest",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
"hmac",
|
"hmac",
|
||||||
|
@ -1069,6 +1047,18 @@ 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"
|
||||||
|
@ -1085,11 +1075,13 @@ dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"card-backend-pcsc",
|
"card-backend-pcsc",
|
||||||
"keyfork-derive-openpgp",
|
"keyfork-derive-openpgp",
|
||||||
|
"keyfork-pinentry",
|
||||||
"openpgp-card-sequoia",
|
"openpgp-card-sequoia",
|
||||||
"sequoia-openpgp",
|
"sequoia-openpgp",
|
||||||
"serde",
|
"serde",
|
||||||
"sharks",
|
"sharks",
|
||||||
"smex",
|
"smex",
|
||||||
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1135,9 +1127,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop"
|
name = "lalrpop"
|
||||||
version = "0.19.12"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a1cbf952127589f2851ab2046af368fd20645491bb4b376f04b7f94d7a9837b"
|
checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ascii-canvas",
|
"ascii-canvas",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
|
@ -1148,7 +1140,7 @@ dependencies = [
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.6.29",
|
"regex-syntax 0.7.4",
|
||||||
"string_cache",
|
"string_cache",
|
||||||
"term",
|
"term",
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
|
@ -1157,9 +1149,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lalrpop-util"
|
name = "lalrpop-util"
|
||||||
version = "0.19.12"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3c48237b9604c5a4702de6b824e02006c3214327564636aef27c1028a8fa0ed"
|
checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
|
@ -1275,7 +1267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi",
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1285,7 +1277,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 0.2.10",
|
"getrandom",
|
||||||
"libc",
|
"libc",
|
||||||
"nettle-sys",
|
"nettle-sys",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -1470,7 +1462,7 @@ 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 0.10.7",
|
"digest",
|
||||||
"hmac",
|
"hmac",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1508,6 +1500,12 @@ dependencies = [
|
||||||
"base64ct",
|
"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"
|
||||||
|
@ -1646,19 +1644,6 @@ 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"
|
||||||
|
@ -1666,20 +1651,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.1",
|
"rand_chacha",
|
||||||
"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"
|
||||||
|
@ -1705,31 +1680,13 @@ 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 0.2.10",
|
"getrandom",
|
||||||
]
|
|
||||||
|
|
||||||
[[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]]
|
||||||
|
@ -1765,7 +1722,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 0.2.10",
|
"getrandom",
|
||||||
"redox_syscall 0.2.16",
|
"redox_syscall 0.2.16",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
@ -1814,6 +1771,12 @@ 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"
|
||||||
|
@ -1829,7 +1792,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 0.10.7",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1839,7 +1802,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55a77d189da1fee555ad95b7e50e7457d91c0e089ec68ca69ad2989413bbdab4"
|
checksum = "55a77d189da1fee555ad95b7e50e7457d91c0e089ec68ca69ad2989413bbdab4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"digest 0.10.7",
|
"digest",
|
||||||
"num-bigint-dig",
|
"num-bigint-dig",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-iter",
|
"num-iter",
|
||||||
|
@ -1847,7 +1810,7 @@ dependencies = [
|
||||||
"pkcs1",
|
"pkcs1",
|
||||||
"pkcs8 0.9.0",
|
"pkcs8 0.9.0",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"signature 2.1.0",
|
"signature",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
@ -1932,6 +1895,15 @@ dependencies = [
|
||||||
"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"
|
||||||
|
@ -1940,9 +1912,9 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sequoia-openpgp"
|
name = "sequoia-openpgp"
|
||||||
version = "1.16.1"
|
version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a16854c0f6297de6db4df195e28324dfbc2429802f0e48cd04007db8e3049709"
|
checksum = "2ea026cf8a70d331c742e3ad7e68fd405d0743ff86630fb4334a1bf8d0e194c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
@ -1950,9 +1922,9 @@ dependencies = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dyn-clone",
|
"dyn-clone",
|
||||||
"ed25519 1.5.3",
|
"ed25519",
|
||||||
"flate2",
|
"flate2",
|
||||||
"getrandom 0.2.10",
|
"getrandom",
|
||||||
"idna",
|
"idna",
|
||||||
"lalrpop",
|
"lalrpop",
|
||||||
"lalrpop-util",
|
"lalrpop-util",
|
||||||
|
@ -1961,9 +1933,9 @@ dependencies = [
|
||||||
"memsec",
|
"memsec",
|
||||||
"nettle",
|
"nettle",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rand 0.7.3",
|
"rand 0.8.5",
|
||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.6.29",
|
"regex-syntax 0.8.2",
|
||||||
"sha1collisiondetection",
|
"sha1collisiondetection",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"xxhash-rust",
|
"xxhash-rust",
|
||||||
|
@ -2002,11 +1974,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1collisiondetection"
|
name = "sha1collisiondetection"
|
||||||
version = "0.2.7"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b20793cf8330b2c7da4c438116660fed24e380bcb8a1bcfff2581b5593a0b38e"
|
checksum = "31c0b86a052106b16741199985c9ec2bf501f619f70c48fa479b44b093ad9a68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest 0.9.0",
|
"digest",
|
||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2018,7 +1990,7 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
"digest 0.10.7",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2056,19 +2028,13 @@ 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 0.10.7",
|
"digest",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2223,18 +2189,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.49"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
|
checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.49"
|
version = "1.0.50"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2469,12 +2435,6 @@ 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"
|
||||||
|
@ -2535,6 +2495,18 @@ 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"
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
[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"
|
||||||
|
|
||||||
|
[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"
|
|
@ -0,0 +1,22 @@
|
||||||
|
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.
|
|
@ -0,0 +1,225 @@
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,437 @@
|
||||||
|
//! `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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(desc))?;
|
||||||
|
}
|
||||||
|
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(|_| ())
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,16 +7,19 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["openpgp", "openpgp-card"]
|
default = ["openpgp", "openpgp-card"]
|
||||||
openpgp = ["sequoia-openpgp"]
|
openpgp = ["sequoia-openpgp", "prompt"]
|
||||||
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc"]
|
openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc"]
|
||||||
|
prompt = ["keyfork-pinentry"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
card-backend-pcsc = { version = "0.5.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 }
|
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"
|
||||||
|
|
|
@ -1,11 +1,4 @@
|
||||||
use std::{
|
use std::{env, fs::File, io::stdout, path::PathBuf, process::ExitCode, str::FromStr};
|
||||||
env,
|
|
||||||
fs::File,
|
|
||||||
io::stdout,
|
|
||||||
path::PathBuf,
|
|
||||||
process::ExitCode,
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert, parse_messages};
|
use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert, parse_messages};
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
#[cfg(feature = "openpgp")]
|
#[cfg(feature = "openpgp")]
|
||||||
pub mod openpgp;
|
pub mod openpgp;
|
||||||
|
|
||||||
|
#[cfg(feature = "prompt")]
|
||||||
|
mod prompt_manager;
|
||||||
|
|
|
@ -19,7 +19,7 @@ use openpgp::{
|
||||||
},
|
},
|
||||||
policy::{NullPolicy, Policy, StandardPolicy},
|
policy::{NullPolicy, Policy, StandardPolicy},
|
||||||
serialize::{
|
serialize::{
|
||||||
stream::{ArbitraryWriter, Encryptor, LiteralWriter, Message, Recipient, Signer},
|
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
|
||||||
Marshal,
|
Marshal,
|
||||||
},
|
},
|
||||||
types::KeyFlags,
|
types::KeyFlags,
|
||||||
|
@ -176,8 +176,8 @@ 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 mut manager = SmartcardManager::new()?;
|
||||||
let content = if keyring.is_empty() {
|
let content = if keyring.is_empty() {
|
||||||
let card_fp = manager.load_any_card()?;
|
let card_fp = manager.load_any_card()?;
|
||||||
eprintln!("key discovery is empty, using hardware smartcard: {card_fp}");
|
eprintln!("key discovery is empty, using hardware smartcard: {card_fp}");
|
||||||
|
@ -351,7 +351,7 @@ pub fn split(threshold: u8, certs: Vec<Cert>, secret: &[u8], output: impl Write)
|
||||||
|
|
||||||
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 = Encryptor::for_recipients(
|
let message = Encryptor2::for_recipients(
|
||||||
message,
|
message,
|
||||||
encryption_keys
|
encryption_keys
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -398,7 +398,7 @@ 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 = Encryptor::for_recipients(message, total_recipients).build()?;
|
let message = Encryptor2::for_recipients(message, total_recipients).build()?;
|
||||||
let mut message = LiteralWriter::new(message).build()?;
|
let mut message = LiteralWriter::new(message).build()?;
|
||||||
message.write_all(&pp)?;
|
message.write_all(&pp)?;
|
||||||
message.finalize()?;
|
message.finalize()?;
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
|
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::PromptManager;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum KeyringFailure {
|
pub enum KeyringFailure {
|
||||||
SecretKeyNotFound,
|
SecretKeyNotFound,
|
||||||
|
@ -26,18 +31,19 @@ impl std::fmt::Display for KeyringFailure {
|
||||||
|
|
||||||
impl std::error::Error for KeyringFailure {}
|
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]>) -> Self {
|
pub fn new(certs: impl AsRef<[Cert]>) -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
Self {
|
Ok(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 {
|
pub fn is_empty(&self) -> bool {
|
||||||
|
@ -114,14 +120,39 @@ 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();
|
||||||
// unoptimized 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) {
|
||||||
for key in cert.keys().secret() {
|
#[allow(deprecated, clippy::map_flatten)]
|
||||||
|
let name = cert
|
||||||
|
.userids()
|
||||||
|
.next()
|
||||||
|
.map(|userid| userid.userid().name().transpose())
|
||||||
|
.flatten()
|
||||||
|
.transpose()?;
|
||||||
|
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();
|
||||||
// NOTE: Returns an error if using an encrypted secret key.
|
let mut keypair = if secret_key.has_unencrypted_secret() {
|
||||||
// TODO: support skipping or validating encrypted secret keys.
|
secret_key.into_keypair()?
|
||||||
let mut keypair = secret_key.into_keypair()?;
|
} 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)?;
|
||||||
|
let key = secret_key
|
||||||
|
.decrypt_secret(&passphrase.expose_secret().as_str().into())?;
|
||||||
|
key.into_keypair()?
|
||||||
|
};
|
||||||
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))
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use keyfork_pinentry::ExposeSecret;
|
||||||
|
|
||||||
use super::openpgp::{
|
use super::openpgp::{
|
||||||
self,
|
self,
|
||||||
cert::Cert,
|
cert::Cert,
|
||||||
|
@ -7,6 +9,7 @@ use super::openpgp::{
|
||||||
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
parse::stream::{DecryptionHelper, MessageLayer, MessageStructure, VerificationHelper},
|
||||||
Fingerprint,
|
Fingerprint,
|
||||||
};
|
};
|
||||||
|
use crate::prompt_manager::PromptManager;
|
||||||
|
|
||||||
use card_backend_pcsc::PcscBackend;
|
use card_backend_pcsc::PcscBackend;
|
||||||
use openpgp_card_sequoia::{state::Open, Card};
|
use openpgp_card_sequoia::{state::Open, Card};
|
||||||
|
@ -28,15 +31,19 @@ impl std::fmt::Display for SmartcardFailure {
|
||||||
|
|
||||||
impl std::error::Error for SmartcardFailure {}
|
impl std::error::Error for SmartcardFailure {}
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct SmartcardManager {
|
pub struct SmartcardManager {
|
||||||
current_card: Option<Card<Open>>,
|
current_card: Option<Card<Open>>,
|
||||||
root: Option<Cert>,
|
root: Option<Cert>,
|
||||||
|
pm: PromptManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmartcardManager {
|
impl SmartcardManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
||||||
Default::default()
|
Ok(Self {
|
||||||
|
current_card: None,
|
||||||
|
root: None,
|
||||||
|
pm: PromptManager::new("keyfork-shard", None)?,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the root cert, returning the old cert
|
// Sets the root cert, returning the old cert
|
||||||
|
@ -46,40 +53,6 @@ impl SmartcardManager {
|
||||||
cert
|
cert
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Utility function to prompt for a newline from standard input.
|
|
||||||
pub fn prompt(&self, prompt: impl std::fmt::Display) -> std::io::Result<()> {
|
|
||||||
eprint!("{prompt}: ");
|
|
||||||
std::io::stdin().read_line(&mut String::new()).map(|_| ())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Utility function to obtain a prompt response from the command line.
|
|
||||||
pub fn prompt_input(&self, prompt: impl std::fmt::Display) -> std::io::Result<String> {
|
|
||||||
eprint!("{prompt}: ");
|
|
||||||
let mut output = String::new();
|
|
||||||
std::io::stdin().read_line(&mut output)?;
|
|
||||||
Ok(output)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
/// Return all [`Fingerprint`] for the currently accessible backends.
|
|
||||||
///
|
|
||||||
/// NOTE: Only implemented for decryption keys.
|
|
||||||
pub fn iter_fingerprints() -> impl Iterator<Item = Fingerprint> {
|
|
||||||
PcscBackend::cards(None).into_iter().flat_map(|iter| {
|
|
||||||
iter.filter_map(|backend| {
|
|
||||||
let backend = backend.ok()?;
|
|
||||||
let mut card = Card::<Open>::new(backend).ok()?;
|
|
||||||
let transaction = card.transaction().ok()?;
|
|
||||||
transaction
|
|
||||||
.fingerprints()
|
|
||||||
.ok()?
|
|
||||||
.decryption()
|
|
||||||
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// Load any backend.
|
/// Load any backend.
|
||||||
pub fn load_any_card(&mut self) -> Result<Fingerprint, Box<dyn std::error::Error>> {
|
pub fn load_any_card(&mut self) -> Result<Fingerprint, Box<dyn std::error::Error>> {
|
||||||
PcscBackend::cards(None)?
|
PcscBackend::cards(None)?
|
||||||
|
@ -141,7 +114,8 @@ impl SmartcardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
eprintln!("No matching smartcard detected.");
|
eprintln!("No matching smartcard detected.");
|
||||||
self.prompt("Please plug in a smart card and press enter")?;
|
self.pm
|
||||||
|
.prompt_message("Please plug in a smart card and press enter")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
@ -201,8 +175,18 @@ impl DecryptionHelper for &mut SmartcardManager {
|
||||||
.fingerprints()?
|
.fingerprints()?
|
||||||
.decryption()
|
.decryption()
|
||||||
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()));
|
.map(|fp| Fingerprint::from_bytes(fp.as_bytes()));
|
||||||
let pin = self.prompt_input("User PIN")?;
|
let Some(fp) = fp else {
|
||||||
let mut user = transaction.to_user_card(pin.as_str().trim())?;
|
return Err(SmartcardFailure::SmartCardHasNoDecrypt.into());
|
||||||
|
};
|
||||||
|
let cardholder_name = transaction.cardholder_name()?;
|
||||||
|
let card_id = transaction.application_identifier()?.ident();
|
||||||
|
let message = if cardholder_name.is_empty() {
|
||||||
|
format!("Unlock card {card_id}")
|
||||||
|
} else {
|
||||||
|
format!("Unlock card {card_id} ({cardholder_name})")
|
||||||
|
};
|
||||||
|
let pin = self.pm.prompt_passphrase("Smartcard User PIN", message)?;
|
||||||
|
let mut user = transaction.to_user_card(pin.expose_secret().as_str().trim())?;
|
||||||
let mut decryptor =
|
let mut decryptor =
|
||||||
user.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))?;
|
user.decryptor(&|| eprintln!("Touch confirmation needed for decryption"))?;
|
||||||
for pkesk in pkesks {
|
for pkesk in pkesks {
|
||||||
|
@ -211,7 +195,7 @@ impl DecryptionHelper for &mut SmartcardManager {
|
||||||
.map(|(algo, sk)| decrypt(algo, &sk))
|
.map(|(algo, sk)| decrypt(algo, &sk))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
return Ok(fp);
|
return Ok(Some(fp));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue