Start with signing outgoing mails

- Add a scheme on how to discover Husk signing keys. Siging keys mut be
  certified by the `local trust root` with a signature having a notation
  with a name equal to `signing_keys@husk-projet.org`.
- Fetch the key from the keystore.
- Expand `authenticate()` so that notation can be added to the
  signature.
- Add a test to create a signing key and fetch it from the keystore.
This commit is contained in:
Malte Meiboom
2026-06-10 15:00:33 +02:00
parent 44d00b262e
commit 1f89d40cd3
7 changed files with 621 additions and 10 deletions
Generated
+411 -8
View File
@@ -112,6 +112,17 @@ dependencies = [
"term", "term",
] ]
[[package]]
name = "async-generic"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddf3728566eefa873833159754f5732fb0951d3649e6e5b891cc70d56dd41673"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.89"
@@ -165,15 +176,30 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "bit-set"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
dependencies = [
"bit-vec 0.8.0",
]
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.9.1" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd"
dependencies = [ dependencies = [
"bit-vec", "bit-vec 0.9.1",
] ]
[[package]]
name = "bit-vec"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
[[package]] [[package]]
name = "bit-vec" name = "bit-vec"
version = "0.9.1" version = "0.9.1"
@@ -255,6 +281,46 @@ dependencies = [
"pkg-config", "pkg-config",
] ]
[[package]]
name = "capnp"
version = "0.25.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfd2485d4b36ac9c5aa6572d7d35daa63a5b34f517627d6c34d068e616e4a73"
dependencies = [
"embedded-io",
]
[[package]]
name = "capnp-futures"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b69dfddccc57844f9a90f9d72b44b97c326914851ea94fb7da40ef9cad6e8d"
dependencies = [
"capnp",
"futures-channel",
"futures-util",
]
[[package]]
name = "capnp-rpc"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c74c337e87f75f3174ffb3513738ab0acc631c04f64cc33b867c60f84771da"
dependencies = [
"capnp",
"capnp-futures",
"futures",
]
[[package]]
name = "capnpc"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fca02be865c8c5a78bfc24b9819006ab6b59bef238467203928e26459557af93"
dependencies = [
"capnp",
]
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.62" version = "1.2.62"
@@ -469,6 +535,22 @@ dependencies = [
"typenum", "typenum",
] ]
[[package]]
name = "ctor"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e"
dependencies = [
"ctor-proc-macro",
"dtor",
]
[[package]]
name = "ctor-proc-macro"
version = "0.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
[[package]] [[package]]
name = "data-encoding" name = "data-encoding"
version = "2.11.0" version = "2.11.0"
@@ -567,6 +649,21 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "dtor"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301"
dependencies = [
"dtor-proc-macro",
]
[[package]]
name = "dtor-proc-macro"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.20" version = "1.0.20"
@@ -579,6 +676,12 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
[[package]]
name = "embedded-io"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
[[package]] [[package]]
name = "ena" name = "ena"
version = "0.14.4" version = "0.14.4"
@@ -615,6 +718,29 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "env_filter"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef"
dependencies = [
"log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.11.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a"
dependencies = [
"anstream",
"anstyle",
"env_filter",
"jiff",
"log",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
version = "1.0.2" version = "1.0.2"
@@ -724,6 +850,31 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.32" version = "0.3.32"
@@ -731,6 +882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink",
] ]
[[package]] [[package]]
@@ -739,6 +891,17 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.32" version = "0.3.32"
@@ -774,9 +937,13 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io",
"futures-macro", "futures-macro",
"futures-sink",
"futures-task", "futures-task",
"memchr",
"pin-project-lite", "pin-project-lite",
"slab", "slab",
] ]
@@ -1044,11 +1211,13 @@ dependencies = [
"log4rs", "log4rs",
"sequoia-cert-store", "sequoia-cert-store",
"sequoia-directories", "sequoia-directories",
"sequoia-keystore",
"sequoia-net", "sequoia-net",
"sequoia-openpgp", "sequoia-openpgp",
"sequoia-wot", "sequoia-wot",
"serde", "serde",
"serde_derive", "serde_derive",
"tempfile",
"thiserror 2.0.18", "thiserror 2.0.18",
"tokio", "tokio",
"toml", "toml",
@@ -1338,6 +1507,30 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jiff"
version = "0.2.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde_core",
]
[[package]]
name = "jiff-static"
version = "0.2.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.99" version = "0.3.99"
@@ -1359,6 +1552,27 @@ dependencies = [
"cpufeatures", "cpufeatures",
] ]
[[package]]
name = "lalrpop"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
dependencies = [
"ascii-canvas",
"bit-set 0.8.0",
"ena",
"itertools 0.14.0",
"lalrpop-util 0.22.2",
"petgraph 0.7.1",
"regex",
"regex-syntax",
"sha3",
"string_cache 0.8.9",
"term",
"unicode-xid",
"walkdir",
]
[[package]] [[package]]
name = "lalrpop" name = "lalrpop"
version = "0.23.1" version = "0.23.1"
@@ -1366,20 +1580,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98a80a963123205c7157323c99611bc4abb65dcbd62ef46dc4bac74a3941bc75" checksum = "98a80a963123205c7157323c99611bc4abb65dcbd62ef46dc4bac74a3941bc75"
dependencies = [ dependencies = [
"ascii-canvas", "ascii-canvas",
"bit-set", "bit-set 0.9.1",
"ena", "ena",
"itertools 0.14.0", "itertools 0.14.0",
"lalrpop-util", "lalrpop-util 0.23.1",
"petgraph", "petgraph 0.8.3",
"regex", "regex",
"regex-syntax", "regex-syntax",
"sha3", "sha3",
"string_cache", "string_cache 0.9.0",
"term", "term",
"unicode-xid", "unicode-xid",
"walkdir", "walkdir",
] ]
[[package]]
name = "lalrpop-util"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733"
dependencies = [
"regex-automata",
"rustversion",
]
[[package]] [[package]]
name = "lalrpop-util" name = "lalrpop-util"
version = "0.23.1" version = "0.23.1"
@@ -1777,12 +2001,28 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.2" version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "petgraph"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]] [[package]]
name = "petgraph" name = "petgraph"
version = "0.8.3" version = "0.8.3"
@@ -1794,6 +2034,15 @@ dependencies = [
"indexmap", "indexmap",
] ]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "phf_shared" name = "phf_shared"
version = "0.13.1" version = "0.13.1"
@@ -1815,6 +2064,21 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "portable-atomic-util"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "potential_utf" name = "potential_utf"
version = "0.1.5" version = "0.1.5"
@@ -2274,6 +2538,126 @@ dependencies = [
"thiserror 1.0.69", "thiserror 1.0.69",
] ]
[[package]]
name = "sequoia-gpg-agent"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c36b37720b61230507a953d9879eb0dd8c228d86948d37cba53ced942e862a1"
dependencies = [
"anyhow",
"chrono",
"futures",
"lalrpop 0.22.2",
"lalrpop-util 0.22.2",
"libc",
"sequoia-ipc",
"sequoia-openpgp",
"stfu8",
"tempfile",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "sequoia-ipc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0770bf144d96733921b4647cd4b243cbebf032127676a441e3ebe702c0768c54"
dependencies = [
"anyhow",
"capnp-rpc",
"capnpc",
"ctor",
"dirs",
"fs2",
"lalrpop 0.22.2",
"lalrpop-util 0.22.2",
"libc",
"memsec",
"sequoia-openpgp",
"socket2",
"tempfile",
"thiserror 2.0.18",
"tokio",
"tokio-util",
"winapi",
]
[[package]]
name = "sequoia-keystore"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e5887d0c3c5f0df1d1cab937ef7a5c398111ddfd911c714f28c904438f229c"
dependencies = [
"anyhow",
"async-generic",
"capnp",
"capnpc",
"dirs",
"env_logger",
"log",
"paste",
"sequoia-directories",
"sequoia-ipc",
"sequoia-keystore-backend",
"sequoia-keystore-gpg-agent",
"sequoia-keystore-softkeys",
"sequoia-openpgp",
"thiserror 2.0.18",
"tokio",
"tokio-util",
]
[[package]]
name = "sequoia-keystore-backend"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c55b047f9b6412c34dc7a26a42f278205cbf1e29516feab1228edccca215f500"
dependencies = [
"anyhow",
"async-trait",
"env_logger",
"futures",
"log",
"sequoia-openpgp",
"tempfile",
"thiserror 2.0.18",
"tokio",
]
[[package]]
name = "sequoia-keystore-gpg-agent"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ad901f1f1d88b38f5f5738d5bf2b80136ec44e94a40716193be0bbf3314df2b"
dependencies = [
"anyhow",
"async-trait",
"futures",
"log",
"openpgp-cert-d",
"sequoia-gpg-agent",
"sequoia-ipc",
"sequoia-keystore-backend",
"sequoia-openpgp",
"tokio",
]
[[package]]
name = "sequoia-keystore-softkeys"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c4944848f748ce22875020e9cff9e604d52c563aef8e19053f5abe037139e"
dependencies = [
"anyhow",
"async-trait",
"dirs",
"futures",
"log",
"sequoia-keystore-backend",
"sequoia-openpgp",
]
[[package]] [[package]]
name = "sequoia-net" name = "sequoia-net"
version = "0.30.1" version = "0.30.1"
@@ -2314,8 +2698,8 @@ dependencies = [
"flate2", "flate2",
"getrandom 0.2.17", "getrandom 0.2.17",
"idna", "idna",
"lalrpop", "lalrpop 0.23.1",
"lalrpop-util", "lalrpop-util 0.23.1",
"libc", "libc",
"memsec", "memsec",
"nettle", "nettle",
@@ -2527,6 +2911,24 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "stfu8"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978"
[[package]]
name = "string_cache"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
dependencies = [
"new_debug_unreachable",
"parking_lot",
"phf_shared 0.11.3",
"precomputed-hash",
]
[[package]] [[package]]
name = "string_cache" name = "string_cache"
version = "0.9.0" version = "0.9.0"
@@ -2535,7 +2937,7 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
dependencies = [ dependencies = [
"new_debug_unreachable", "new_debug_unreachable",
"parking_lot", "parking_lot",
"phf_shared", "phf_shared 0.13.1",
"precomputed-hash", "precomputed-hash",
] ]
@@ -2755,6 +3157,7 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
"futures-io",
"futures-sink", "futures-sink",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
+2
View File
@@ -21,11 +21,13 @@ log = "0.4.30"
log4rs = "1.3.0" log4rs = "1.3.0"
sequoia-cert-store = "0.7.3" sequoia-cert-store = "0.7.3"
sequoia-directories = "0.1.0" sequoia-directories = "0.1.0"
sequoia-keystore = "0.7.2"
sequoia-net = "0.30.1" sequoia-net = "0.30.1"
sequoia-openpgp = "2.3.0" sequoia-openpgp = "2.3.0"
sequoia-wot = "0.15.0" sequoia-wot = "0.15.0"
serde = "1.0.228" serde = "1.0.228"
serde_derive = "1.0.228" serde_derive = "1.0.228"
tempfile = "3.27.0"
thiserror = "2.0.17" thiserror = "2.0.17"
tokio = { version = "1.52.3", features = [ "tokio-macros", "rt-multi-thread", "signal" ] } tokio = { version = "1.52.3", features = [ "tokio-macros", "rt-multi-thread", "signal" ] }
toml = "0.9.8" toml = "0.9.8"
+1
View File
@@ -37,6 +37,7 @@ pub async fn dispatch(cmd: LocalsAddCommand, config: HuskConfigContainer) -> Res
&vc, &vc,
crypto::Role::Local, crypto::Role::Local,
true, true,
None,
None None
) )
} }
+1
View File
@@ -41,6 +41,7 @@ pub async fn dispatch(cmd: LocalsRemoveCommand, config: HuskConfigContainer) ->
cert, cert,
crypto::Role::Local, crypto::Role::Local,
false, false,
None,
None) None)
} else { } else {
Err(CommandError::CertNotUsable(fpr).into()) Err(CommandError::CertNotUsable(fpr).into())
+193 -1
View File
@@ -3,15 +3,20 @@
use std::io::Write; use std::io::Write;
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use std::time::SystemTime;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet; use std::collections::HashSet;
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
use anyhow; use anyhow;
use sequoia_openpgp::cert::ValidCert; use sequoia_openpgp::cert::ValidCert;
use sequoia_openpgp::packet::signature::subpacket::NotationDataFlags;
use tokio::task::JoinSet; use tokio::task::JoinSet;
use bytes::Bytes; use bytes::Bytes;
use sequoia_net::{KeyServer, wkd, dane}; use sequoia_net::{KeyServer, wkd, dane};
use sequoia_directories::Home;
use sequoia_keystore::Keystore;
use sequoia_keystore::Context;
use sequoia_openpgp::packet::prelude::SignatureBuilder; use sequoia_openpgp::packet::prelude::SignatureBuilder;
use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse}; use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse};
use sequoia_openpgp::policy::StandardPolicy; use sequoia_openpgp::policy::StandardPolicy;
@@ -437,7 +442,7 @@ pub enum Role {
/// `domains`. Passing `None` or an empty list in this case is an error. /// `domains`. Passing `None` or an empty list in this case is an error.
/// If `activate` is false, create a certifiaction with minimal trust_roots /// If `activate` is false, create a certifiaction with minimal trust_roots
/// amount and no depth. In this case `domains` is ignored. /// amount and no depth. In this case `domains` is ignored.
pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activate: bool, domains: Option<Vec<String>>) pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activate: bool, domains: Option<Vec<String>>, notation: Option<(String, String)>)
-> anyhow::Result<()> { -> anyhow::Result<()> {
let policy = &context.policy; let policy = &context.policy;
@@ -502,6 +507,14 @@ pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activat
builder = builder.set_trust_signature(0, 1)?; builder = builder.set_trust_signature(0, 1)?;
} }
if let Some((name, value)) = notation {
builder = builder.set_notation(
name,
value,
NotationDataFlags::empty().set_human_readable(),
false)?;
}
// For a certification, user ids are needed. Build a list of // For a certification, user ids are needed. Build a list of
// valid (non self revoked) user ids. // valid (non self revoked) user ids.
let user_ids: Vec<_> = cert.userids() let user_ids: Vec<_> = cert.userids()
@@ -559,9 +572,107 @@ pub fn can_encrypt(cert: &ValidCert) -> bool {
.count() > 0 .count() > 0
} }
/// Signing keys used by Husk must have a certification with a
/// notation of the name `HUSK_SIG_NOTATION`.
pub const HUSK_SIG_NOTATION: &str = "signing_key@husk-project.org";
/// Get Husks signing key
pub fn get_signing_key(sequoia_home: Home, cert_store: &CertStore<'_>, policy: &StandardPolicy<'_>, local_trust_root: &Cert) -> anyhow::Result<Option<Cert>> {
let local_trust_root = local_trust_root.with_policy(policy, None)?;
let local_trust_fpr = local_trust_root.fingerprint();
let mut newest = None;
let mut result = None;
if let Some(keys) = get_all_keys(sequoia_home) {
for key in keys {
if let Ok(cert) = cert_store.lookup_by_cert_fpr(&key) {
if let Ok(vc) = cert.with_policy(policy, None) {
if let Some(time) = contains_signing_key(&vc, &local_trust_fpr) {
if let Some(othertime) = newest {
if time > othertime {
newest = Some(time);
result = Some(vc.cert().clone());
}
} else { // first detected key
newest = Some(time);
result = Some(vc.cert().clone());
}
}
}
}
}
Ok(result)
} else {
Ok(None)
}
}
/// If `vc` is a certificate marked as a Husk signing key
/// key, return the creation time of the signature - else return `None`.
pub fn contains_signing_key(vc: &ValidCert, local_root_fpr: &Fingerprint) -> Option<SystemTime> {
for uid in vc.userids().into_iter() {
for sig in uid.certifications() {
if sig.issuer_fingerprints().any(|i| i == local_root_fpr)
&& sig.notation(HUSK_SIG_NOTATION).count() > 0 {
// ignore signatures without creation time
if let Some(time) = sig.signature_creation_time() {
return Some(time);
}
}
}
}
None
}
/// Get all keys from all backends
pub fn get_all_keys(sequoia_home: Home) -> Option<Vec<Fingerprint>> {
let mut result = Vec::new();
std::thread::scope(|s| {
s.spawn(|| {
if let Ok(keystore_base) = Context::configure()
.home(sequoia_home.data_dir(sequoia_directories::Component::Keystore))
.build() {
if let Ok(mut keystore) = Keystore::connect(&keystore_base) {
if let Ok(mut backends) = keystore.backends() {
for backend in &mut backends {
if let Ok(devices) = backend.devices() {
for mut device in devices {
if let Ok(keys) = device.keys() {
keys.iter().for_each(|k| {
result.push(k.fingerprint());
});
}
}
}
}
}
}
}
});
});
Some(result)
}
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use sequoia_cert_store::store::CertD;
use sequoia_cert_store::{CertStore, LazyCert, StoreUpdate};
use sequoia_keystore::Context;
use sequoia_keystore::Keystore;
use sequoia_directories::Home;
use sequoia_directories::Component;
use sequoia_openpgp::{cert::CertBuilder, policy::StandardPolicy};
use std::sync::Arc;
use crate::common::crypto::{HUSK_SIG_NOTATION, get_all_keys};
use crate::types::{husk_context::HuskContext, mail_context::MailContext};
use super::{authenticate, get_signing_key};
#[test] #[test]
pub fn test_is_encrypted() { pub fn test_is_encrypted() {
@@ -644,4 +755,85 @@ jk6lh01Ej9QTGQ==
} }
#[test]
fn test_signing_key() -> anyhow::Result<()> {
let temp_dir = tempfile::tempdir()?;
let temp_path = temp_dir.path().to_path_buf();
let home = Home::new(temp_path)?;
// build the keystore
let keystore_home = home.data_dir(Component::Keystore);
let c = Context::configure()
.home(keystore_home)
.build()?;
let mut ks = Keystore::connect(&c)?;
// get the softkeys backend
let mut softkeys = None;
for mut backend in ks.backends()?.into_iter() {
if backend.id().expect("backend id") == "softkeys" {
softkeys = Some(backend);
}
}
assert!(softkeys.is_some());
let mut softkeys = softkeys.unwrap();
// build the certstore
let cert_store_base = home.data_dir(sequoia_directories::Component::CertD);
std::fs::create_dir_all(&cert_store_base)?;
let _ = CertD::open(&cert_store_base)?;
let store = CertStore::open(cert_store_base)?;
// build the trust root
let (root, _) = CertBuilder::general_purpose(Some("local root"))
.add_signing_subkey()
.generate()?;
// create a skeleton HuskContext for later use.
let context = HuskContext {
cert_store: store,
policy: StandardPolicy::new(),
local_trust_root: root,
signing_key: None,
introducers: Vec::new(),
keyservers: Vec::new(),
mail: MailContext::new()
};
// store the local trust root
context.cert_store.update(Arc::new(LazyCert::from(context.local_trust_root.clone())))?;
// the siging key
let (signing_key, _) = CertBuilder::general_purpose(Some("signing key"))
.add_signing_subkey()
.generate()?;
let valid_signing_cert = signing_key.with_policy(&context.policy, None)?;
let signing_fpr = signing_key.fingerprint();
softkeys.import(&signing_key)?;
let keys= get_all_keys(home.clone());
eprintln!("{:?}", keys);
authenticate(&context,
&valid_signing_cert,
super::Role::Local,
true,
None,
Some((HUSK_SIG_NOTATION.to_string(), "dummy".to_string())))?;
// get the Husk signing key
let key = get_signing_key(home,
&context.cert_store,
&context.policy,
&context.local_trust_root)?;
assert!(key.is_some());
let fpr = key.unwrap().fingerprint();
assert_eq!(signing_fpr, fpr);
Ok(())
}
} }
+10
View File
@@ -26,6 +26,7 @@ pub struct HuskContext<'hc> {
pub policy: StandardPolicy<'hc>, pub policy: StandardPolicy<'hc>,
pub cert_store: CertStore<'hc>, pub cert_store: CertStore<'hc>,
pub local_trust_root: Cert, pub local_trust_root: Cert,
pub signing_key: Option<Cert>,
pub introducers: Vec<Introducer>, pub introducers: Vec<Introducer>,
pub keyservers: Vec<String>, pub keyservers: Vec<String>,
pub mail: MailContext<'hc>, pub mail: MailContext<'hc>,
@@ -45,10 +46,19 @@ impl<'hc> HuskContext<'hc> {
let local_trust_root = crypto::get_local_trust_root(&cert_store)?; let local_trust_root = crypto::get_local_trust_root(&cert_store)?;
let introducers = crypto::get_introducers(&cert_store, &policy, local_trust_root.fingerprint()); let introducers = crypto::get_introducers(&cert_store, &policy, local_trust_root.fingerprint());
let signing_key = match crypto::get_signing_key(sequoia_home, &cert_store, &policy, &local_trust_root) {
Ok(Some(cert)) => Some(cert),
_ => {
log::warn!("No signing key found");
None
}
};
Ok(HuskContext { Ok(HuskContext {
policy, policy,
cert_store, cert_store,
local_trust_root, local_trust_root,
signing_key,
introducers, introducers,
keyservers: config.get_keyservers(), keyservers: config.get_keyservers(),
mail: MailContext::new(), mail: MailContext::new(),
+3 -1
View File
@@ -83,7 +83,8 @@ impl Introducer {
cert, cert,
crypto::Role::Introducer, crypto::Role::Introducer,
true, true,
Some(domains)) Some(domains),
None)
} }
/// Remove an introducer. /// Remove an introducer.
@@ -101,6 +102,7 @@ impl Introducer {
cert, cert,
crypto::Role::Introducer, crypto::Role::Introducer,
false, false,
None,
None) None)
} }
} }