Merge branch 'malte/signing' into 'main'
Signing outgoing mails See merge request husk-project/husk-milter!6
This commit is contained in:
+1
-1
@@ -3,6 +3,6 @@ stages:
|
||||
|
||||
test-job:
|
||||
stage: test
|
||||
image: localhost:5000/huskbuilder:latest
|
||||
image: localhost:5000/mta:latest
|
||||
script:
|
||||
- cargo test
|
||||
|
||||
Generated
+407
-8
@@ -112,6 +112,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@@ -165,15 +176,30 @@ dependencies = [
|
||||
"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]]
|
||||
name = "bit-set"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd"
|
||||
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]]
|
||||
name = "bit-vec"
|
||||
version = "0.9.1"
|
||||
@@ -255,6 +281,46 @@ dependencies = [
|
||||
"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]]
|
||||
name = "cc"
|
||||
version = "1.2.62"
|
||||
@@ -469,6 +535,22 @@ dependencies = [
|
||||
"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]]
|
||||
name = "data-encoding"
|
||||
version = "2.11.0"
|
||||
@@ -567,6 +649,21 @@ dependencies = [
|
||||
"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]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
@@ -579,6 +676,12 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
|
||||
|
||||
[[package]]
|
||||
name = "embedded-io"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.4"
|
||||
@@ -615,6 +718,29 @@ dependencies = [
|
||||
"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]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -724,6 +850,31 @@ dependencies = [
|
||||
"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]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.32"
|
||||
@@ -731,6 +882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -739,6 +891,17 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "futures-io"
|
||||
version = "0.3.32"
|
||||
@@ -774,9 +937,13 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
@@ -1044,11 +1211,13 @@ dependencies = [
|
||||
"log4rs",
|
||||
"sequoia-cert-store",
|
||||
"sequoia-directories",
|
||||
"sequoia-keystore",
|
||||
"sequoia-net",
|
||||
"sequoia-openpgp",
|
||||
"sequoia-wot",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"toml",
|
||||
@@ -1338,6 +1507,30 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "js-sys"
|
||||
version = "0.3.99"
|
||||
@@ -1359,6 +1552,27 @@ dependencies = [
|
||||
"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]]
|
||||
name = "lalrpop"
|
||||
version = "0.23.1"
|
||||
@@ -1366,20 +1580,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98a80a963123205c7157323c99611bc4abb65dcbd62ef46dc4bac74a3941bc75"
|
||||
dependencies = [
|
||||
"ascii-canvas",
|
||||
"bit-set",
|
||||
"bit-set 0.9.1",
|
||||
"ena",
|
||||
"itertools 0.14.0",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"lalrpop-util 0.23.1",
|
||||
"petgraph 0.8.3",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"sha3",
|
||||
"string_cache",
|
||||
"string_cache 0.9.0",
|
||||
"term",
|
||||
"unicode-xid",
|
||||
"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]]
|
||||
name = "lalrpop-util"
|
||||
version = "0.23.1"
|
||||
@@ -1777,12 +2001,28 @@ dependencies = [
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "petgraph"
|
||||
version = "0.8.3"
|
||||
@@ -1794,6 +2034,15 @@ dependencies = [
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.13.1"
|
||||
@@ -1815,6 +2064,21 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@@ -2274,6 +2538,122 @@ dependencies = [
|
||||
"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 = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7"
|
||||
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 1.0.69",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-keystore-backend"
|
||||
version = "0.7.0"
|
||||
source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"log",
|
||||
"sequoia-openpgp",
|
||||
"tempfile",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-keystore-gpg-agent"
|
||||
version = "0.5.0"
|
||||
source = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7"
|
||||
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 = "git+https://gitlab.com/sequoia-pgp/sequoia-keystore?branch=malte%2Fsoftkey-refresh#cc77f2ea46ab1c3ca1ace0c16848a4137e67bdf7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"dirs",
|
||||
"futures",
|
||||
"log",
|
||||
"sequoia-keystore-backend",
|
||||
"sequoia-openpgp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sequoia-net"
|
||||
version = "0.30.1"
|
||||
@@ -2314,8 +2694,8 @@ dependencies = [
|
||||
"flate2",
|
||||
"getrandom 0.2.17",
|
||||
"idna",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"lalrpop 0.23.1",
|
||||
"lalrpop-util 0.23.1",
|
||||
"libc",
|
||||
"memsec",
|
||||
"nettle",
|
||||
@@ -2527,6 +2907,24 @@ version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "string_cache"
|
||||
version = "0.9.0"
|
||||
@@ -2535,7 +2933,7 @@ checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared",
|
||||
"phf_shared 0.13.1",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
@@ -2755,6 +3153,7 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
|
||||
@@ -21,11 +21,14 @@ log = "0.4.30"
|
||||
log4rs = "1.3.0"
|
||||
sequoia-cert-store = "0.7.3"
|
||||
sequoia-directories = "0.1.0"
|
||||
#sequoia-keystore = "0.7.2"
|
||||
sequoia-keystore = { git = "https://gitlab.com/sequoia-pgp/sequoia-keystore", branch = "malte/softkey-refresh" }
|
||||
sequoia-net = "0.30.1"
|
||||
sequoia-openpgp = "2.3.0"
|
||||
sequoia-wot = "0.15.0"
|
||||
serde = "1.0.228"
|
||||
serde_derive = "1.0.228"
|
||||
tempfile = "3.27.0"
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.52.3", features = [ "tokio-macros", "rt-multi-thread", "signal" ] }
|
||||
toml = "0.9.8"
|
||||
|
||||
@@ -42,6 +42,8 @@ RUN apt-get update -yqq && \
|
||||
libssl-dev \
|
||||
llvm \
|
||||
nettle-dev \
|
||||
capnproto \
|
||||
libsqlite3-dev \
|
||||
pkg-config
|
||||
|
||||
##
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ send_test_mail() { # email_address
|
||||
chown $USER:mail /var/mail/$USER
|
||||
chmod 600 /var/mail/$USER
|
||||
|
||||
echo "test mail" | sendmail $1
|
||||
echo -e "Subject: testmail\n\ntest mail" | sendmail $1
|
||||
inotifywait -qq -t 3 -e close_write /var/mail/$USER
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
# source some functions
|
||||
. common.sh
|
||||
|
||||
test_header "Signing"
|
||||
|
||||
# sq key generate --shared-key --without-password --email 'signing@example.com' --output /tmp/signing.pgp --rev-cert /tmp/signing.rev
|
||||
|
||||
SIGN_FPR=$(sq key generate --own-key --without-password --name "Signing Key" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
|
||||
cd $HUSK_DIR
|
||||
|
||||
$HUSK_BIN --config config/config.toml signkey set --cert $SIGN_FPR
|
||||
SIGN_FPR=$($HUSK_BIN --config config/config.toml signkey show | cut -d ':' -f 2 | tr -d ' ')
|
||||
echo "found signing key $SIGN_FPR"
|
||||
|
||||
create_unix_user juliette
|
||||
JULIETTE_FPR=$(sq key generate --own-key --without-password --name "Juliette" --email "juliette@example.com" 2>&1 | grep "Fingerprint:" | cut -d ':' -f 2)
|
||||
sq pki vouch add --certifier-userid "CA" --cert $JULIETTE_FPR --all > /dev/null 2>&1
|
||||
|
||||
# send a mail, husk should encrypt it
|
||||
echo "send mail to juliette@example.com"
|
||||
send_test_mail juliette@example.com
|
||||
|
||||
if [ -f /var/mail/juliette ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/juliette ; then
|
||||
|
||||
FOUND_FPR=$(sq packet dump /var/mail/juliette | grep "Issuer Fingerprint:" | cut -d ':' -f 2 | tr -d ' ')
|
||||
|
||||
if [ "$SIGN_FPR" != "$FOUND_FPR" ] ; then
|
||||
echo "Found wrong fingerprint '$FOUND_FPR' - expected '$SIGN_FPR'"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "no encryption detected"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "no mail detected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "set new signing key"
|
||||
|
||||
SIGN_FPR=$($HUSK_BIN --config config/config.toml signkey create | cut -d ':' -f 2 | tr -d ' ')
|
||||
echo "new signing key $SIGN_FPR"
|
||||
|
||||
# remove old mail
|
||||
rm /var/mail/juliette
|
||||
|
||||
echo "send mail to juliette@example.com"
|
||||
send_test_mail juliette@example.com
|
||||
|
||||
if [ -f /var/mail/juliette ] ; then
|
||||
if grep -q "BEGIN PGP MESSAGE" /var/mail/juliette ; then
|
||||
|
||||
FOUND_FPR=$(sq packet dump /var/mail/juliette | grep "Issuer Fingerprint:" | cut -d ':' -f 2 | tr -d ' ')
|
||||
|
||||
if [ "$SIGN_FPR" != "$FOUND_FPR" ] ; then
|
||||
echo "Found wrong fingerprint '$FOUND_FPR' - expected '$SIGN_FPR'"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "no encryption detected"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "no mail detected"
|
||||
exit 1
|
||||
fi
|
||||
+81
-4
@@ -18,7 +18,7 @@ pub struct CliArgs {
|
||||
#[clap(
|
||||
long,
|
||||
global = true,
|
||||
help = "Certification store (SEQUOIA_HOME).",
|
||||
help = "Certificate store (SEQUOIA_HOME).",
|
||||
)]
|
||||
pub home: Option<String>,
|
||||
|
||||
@@ -33,6 +33,7 @@ pub enum HuskSubcommands {
|
||||
Daemon(DaemonCommand),
|
||||
Introducer(IntroducerCommand),
|
||||
Locals(LocalsCommand),
|
||||
Signkey(SignkeyCommand),
|
||||
}
|
||||
|
||||
// Daemon subcommands
|
||||
@@ -76,6 +77,9 @@ pub struct IntroducerCommand {
|
||||
pub enum IntroducerSubcommand {
|
||||
Add(IntroducerAddCommand),
|
||||
Remove(IntroducerRemoveCommand),
|
||||
#[clap(
|
||||
about = "List introducers."
|
||||
)]
|
||||
List
|
||||
}
|
||||
|
||||
@@ -88,7 +92,8 @@ pub struct IntroducerAddCommand {
|
||||
#[clap(
|
||||
long = "cert",
|
||||
help = "Certificate to declare as introducer.",
|
||||
conflicts_with = "cert_file"
|
||||
conflicts_with = "cert_file",
|
||||
value_name = "FINGERPRINT",
|
||||
)]
|
||||
pub cert: Option<String>,
|
||||
|
||||
@@ -118,7 +123,8 @@ pub struct IntroducerAddCommand {
|
||||
pub struct IntroducerRemoveCommand {
|
||||
#[clap(
|
||||
long = "cert",
|
||||
help = "Certificate to be removed as introducer."
|
||||
help = "Certificate to be removed as introducer.",
|
||||
value_name = "FINGERPRINT",
|
||||
)]
|
||||
pub cert: String,
|
||||
|
||||
@@ -149,6 +155,9 @@ pub struct LocalsCommand {
|
||||
pub enum LocalsSubcommand {
|
||||
Add(LocalsAddCommand),
|
||||
Remove(LocalsRemoveCommand),
|
||||
#[clap(
|
||||
about = "List locals."
|
||||
)]
|
||||
List
|
||||
}
|
||||
|
||||
@@ -174,8 +183,76 @@ pub struct LocalsAddCommand {
|
||||
pub struct LocalsRemoveCommand {
|
||||
#[clap(
|
||||
long = "cert",
|
||||
help = "Certificate to be removed."
|
||||
help = "Certificate to be removed.",
|
||||
value_name = "FINGERPRINT",
|
||||
)]
|
||||
pub cert: String,
|
||||
}
|
||||
|
||||
// Signkey subcommands
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "signkey",
|
||||
about = "Manage the signing key.",
|
||||
subcommand_required = true,
|
||||
arg_required_else_help = true,
|
||||
disable_colored_help = true,
|
||||
disable_version_flag = true,
|
||||
)]
|
||||
pub struct SignkeyCommand {
|
||||
#[clap(subcommand)]
|
||||
pub subcommand: SignkeySubcommand,
|
||||
}
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum SignkeySubcommand {
|
||||
Set(SignkeySetCommand),
|
||||
Create(SignkeyCreateCommand),
|
||||
#[clap(
|
||||
about = "Show the fingerprint of the signing key."
|
||||
)]
|
||||
Show,
|
||||
#[clap(
|
||||
about = "Print the certificate of the signing key."
|
||||
)]
|
||||
Dump,
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
about = "Set a new signing key.",
|
||||
)]
|
||||
pub struct SignkeySetCommand {
|
||||
#[clap(
|
||||
long,
|
||||
help = "Load a new signing key from <FILE>.",
|
||||
value_name = "FILE",
|
||||
conflicts_with = "cert"
|
||||
)]
|
||||
pub cert_file: Option<String>,
|
||||
|
||||
#[clap(
|
||||
long = "cert",
|
||||
help = "Certificate to declare as signing key.",
|
||||
conflicts_with = "cert_file",
|
||||
value_name = "FINGERPRINT",
|
||||
)]
|
||||
pub cert: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
about = "Create a new signing key.",
|
||||
)]
|
||||
pub struct SignkeyCreateCommand {
|
||||
#[clap(
|
||||
long,
|
||||
help = "Set the userid of the new key.",
|
||||
value_name = "USERID",
|
||||
default_value = "Husk signing key",
|
||||
)]
|
||||
pub userid: String,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::config::HuskConfigContainer;
|
||||
pub mod daemon;
|
||||
pub mod introducer;
|
||||
pub mod locals;
|
||||
pub mod signkey;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum CommandError {
|
||||
@@ -20,6 +21,8 @@ pub enum CommandError {
|
||||
CertNotAlive(Fingerprint),
|
||||
#[error("Certificate is revoked: {0}")]
|
||||
CertRevoked(Fingerprint),
|
||||
#[error("No signing key available")]
|
||||
SigningKeyNotAvailable,
|
||||
}
|
||||
|
||||
pub async fn dispatch(cli: CliArgs, config: HuskConfigContainer) -> Result<()> {
|
||||
@@ -34,6 +37,9 @@ pub async fn dispatch(cli: CliArgs, config: HuskConfigContainer) -> Result<()> {
|
||||
HuskSubcommands::Locals(subcmd) => {
|
||||
commands::locals::dispatch(subcmd, config).await?;
|
||||
},
|
||||
HuskSubcommands::Signkey(subcmd) => {
|
||||
commands::signkey::dispatch(subcmd, config).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -45,6 +45,7 @@ pub async fn dispatch(cmd: IntroducerAddCommand, config: HuskConfigContainer) ->
|
||||
.filter_map(|c|
|
||||
c.with_policy(policy, None).ok()
|
||||
)
|
||||
.filter(|vc| crypto::can_certify(vc))
|
||||
.collect();
|
||||
|
||||
if let Some(cert) = certs.first() {
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
//! signkey subcommand
|
||||
|
||||
use anyhow::Result;
|
||||
use sequoia_cert_store::Store;
|
||||
use sequoia_openpgp::serialize::SerializeInto;
|
||||
|
||||
use crate::cli::cli_args::{SignkeyCommand, SignkeySubcommand};
|
||||
use crate::config::HuskConfigContainer;
|
||||
use crate::types::husk_context::HuskContext;
|
||||
use crate::commands::CommandError;
|
||||
|
||||
pub mod set;
|
||||
pub mod create;
|
||||
|
||||
pub async fn dispatch(cmd: SignkeyCommand, config: HuskConfigContainer)
|
||||
-> Result<()> {
|
||||
match cmd.subcommand {
|
||||
SignkeySubcommand::Set(set_cmd) => {
|
||||
set::dispatch(set_cmd, config).await?;
|
||||
},
|
||||
SignkeySubcommand::Create(create_cmd) => {
|
||||
create::dispatch(create_cmd, config).await?;
|
||||
}
|
||||
SignkeySubcommand::Show => {
|
||||
let context = HuskContext::new(&config.into())?;
|
||||
if let Some(key) = context.signing_key {
|
||||
println!("Fingerprint: {}", key.fingerprint());
|
||||
} else {
|
||||
return Err(CommandError::SigningKeyNotAvailable.into());
|
||||
}
|
||||
},
|
||||
SignkeySubcommand::Dump => {
|
||||
let context = HuskContext::new(&config.into())?;
|
||||
if let Some(key) = context.signing_key {
|
||||
|
||||
if let Ok(cert) = &context.cert_store
|
||||
.lookup_by_cert_fpr(&key.fingerprint()) {
|
||||
if let Ok(cert) = cert.to_cert() {
|
||||
let armored = cert.armored().to_vec()?;
|
||||
println!("{}", String::from_utf8_lossy(&armored[..]));
|
||||
} else {
|
||||
return Err(
|
||||
CommandError::CertNotUsable(cert.fingerprint())
|
||||
.into());
|
||||
}
|
||||
} else {
|
||||
return Err(
|
||||
CommandError::CertNotFound(key.fingerprint())
|
||||
.into());
|
||||
}
|
||||
} else {
|
||||
return Err(CommandError::SigningKeyNotAvailable.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//! signkey create command
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use sequoia_openpgp::cert::CertBuilder;
|
||||
use sequoia_directories::Home;
|
||||
|
||||
use crate::cli::cli_args::SignkeyCreateCommand;
|
||||
use crate::common::crypto;
|
||||
use crate::config::{HuskConfig, HuskConfigContainer};
|
||||
use crate::types::husk_context::HuskContext;
|
||||
|
||||
pub async fn dispatch(cmd: SignkeyCreateCommand, config: HuskConfigContainer)
|
||||
-> Result<()> {
|
||||
|
||||
let config: HuskConfig = config.into();
|
||||
let context = HuskContext::new(&config)?;
|
||||
let sequoia_home = Home::new(PathBuf::from(&config.sequoia_home))?;
|
||||
|
||||
// XXX: cleanup the userid before using
|
||||
let (cert, _) = CertBuilder::new()
|
||||
.add_signing_subkey()
|
||||
.add_userid(cmd.userid)
|
||||
.generate()?;
|
||||
|
||||
if crypto::import_key(sequoia_home, &cert) {
|
||||
let vc = cert.with_policy(&context.policy, None)?;
|
||||
crypto::set_signing_key(&context, &vc)?;
|
||||
|
||||
println!("New signing key: {}", vc.fingerprint());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
//! signkey set command
|
||||
|
||||
use anyhow::Result;
|
||||
use sequoia_cert_store::Store;
|
||||
use sequoia_openpgp::cert::ValidCert;
|
||||
use sequoia_openpgp::cert;
|
||||
use sequoia_openpgp::parse::Parse;
|
||||
use sequoia_openpgp::{Fingerprint, KeyHandle};
|
||||
use sequoia_openpgp::types::RevocationStatus;
|
||||
|
||||
use crate::cli::cli_args::SignkeySetCommand;
|
||||
use crate::commands::CommandError;
|
||||
use crate::common::crypto;
|
||||
use crate::config::HuskConfigContainer;
|
||||
use crate::types::husk_context::HuskContext;
|
||||
|
||||
pub async fn dispatch(cmd: SignkeySetCommand, config: HuskConfigContainer)
|
||||
-> Result<()> {
|
||||
let context = HuskContext::new(&config.into())?;
|
||||
let policy = &context.policy;
|
||||
let cert_store = &context.cert_store;
|
||||
|
||||
if let Some(cert_file) = cmd.cert_file {
|
||||
let c = cert::Cert::from_file(cert_file)?;
|
||||
let vc = c.with_policy(policy, None)?;
|
||||
|
||||
// guards
|
||||
if vc.alive().is_err() {
|
||||
return Err(CommandError::CertNotAlive(vc.fingerprint()).into());
|
||||
}
|
||||
if matches!(vc.revocation_status(), RevocationStatus::Revoked(_)) {
|
||||
return Err(CommandError::CertRevoked(vc.fingerprint()).into());
|
||||
}
|
||||
if !crypto::can_sign(&vc) {
|
||||
return Err(CommandError::CertNotUsable(vc.fingerprint()).into());
|
||||
}
|
||||
|
||||
// set the signing key
|
||||
crypto::set_signing_key(
|
||||
&context,
|
||||
&vc
|
||||
)?;
|
||||
|
||||
} else if let Some(cert) = cmd.cert {
|
||||
let fpr = Fingerprint::from_hex(cert.as_str())?;
|
||||
let certs = cert_store
|
||||
.lookup_by_cert(&KeyHandle::from(&fpr))
|
||||
.map_err(|_| CommandError::CertNotFound(fpr.clone()))?;
|
||||
|
||||
let certs: Vec<ValidCert> = certs.iter()
|
||||
.filter_map(|c|
|
||||
c.with_policy(policy, None).ok()
|
||||
)
|
||||
.filter(|vc| crypto::can_sign(vc))
|
||||
.collect();
|
||||
|
||||
if let Some(cert) = certs.first() {
|
||||
crypto::set_signing_key(
|
||||
&context,
|
||||
cert
|
||||
)?;
|
||||
} else {
|
||||
return Err(CommandError::CertNotUsable(fpr).into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+255
-7
@@ -3,19 +3,25 @@
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::SystemTime;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::hash_map::Entry::{Occupied, Vacant};
|
||||
|
||||
use anyhow;
|
||||
use sequoia_openpgp::cert::ValidCert;
|
||||
use sequoia_openpgp::packet::signature::subpacket::NotationDataFlags;
|
||||
use tokio::task::JoinSet;
|
||||
use bytes::Bytes;
|
||||
use sequoia_net::{KeyServer, wkd, dane};
|
||||
use sequoia_directories::Home;
|
||||
use sequoia_keystore::Keystore;
|
||||
use sequoia_keystore::Context;
|
||||
use sequoia_keystore::Key;
|
||||
use sequoia_openpgp::packet::prelude::SignatureBuilder;
|
||||
use sequoia_openpgp::parse::{PacketParser, PacketParserResult, Parse};
|
||||
use sequoia_openpgp::policy::StandardPolicy;
|
||||
use sequoia_openpgp::serialize::stream::{Message, Armorer, Encryptor, LiteralWriter};
|
||||
use sequoia_openpgp::serialize::stream::{Message, Armorer, Encryptor, Signer, LiteralWriter};
|
||||
use sequoia_openpgp::{Fingerprint, Cert, Packet};
|
||||
use sequoia_openpgp::cert::raw::RawCertParser;
|
||||
use sequoia_openpgp::types::SignatureType;
|
||||
@@ -32,6 +38,10 @@ use crate::types::recipient::Recipient;
|
||||
use crate::mail;
|
||||
use crate::common::escape;
|
||||
|
||||
/// Signing keys used by Husk must have a certification with a
|
||||
/// notation of the name `HUSK_SIG_NOTATION`.
|
||||
const HUSK_SIG_NOTATION: &str = "signing_key@husk-project.org";
|
||||
|
||||
/// Returns the local trust root from the cert_store. If the trust root
|
||||
/// cannot be found, it returns an error.
|
||||
pub fn get_local_trust_root(cert_store: &CertStore) -> anyhow::Result<Cert> {
|
||||
@@ -413,10 +423,21 @@ pub fn encrypt(context: &HuskContext<'_>, body: &[u8], recipients: &Vec<Recipien
|
||||
let mut sink = Vec::new();
|
||||
let message = Message::new(&mut sink);
|
||||
let message = Armorer::new(message).build()?;
|
||||
let message = Encryptor::for_recipients(message, keys).build()?;
|
||||
let mut message = LiteralWriter::new(message).build()?;
|
||||
message.write_all(&body[..])?;
|
||||
message.finalize()?;
|
||||
let mut message = Encryptor::for_recipients(message, keys).build()?;
|
||||
if let Some(key) = &context.signing_key {
|
||||
log::debug!("use signing key {}", key.fingerprint());
|
||||
message = Signer::new(message, key.clone())?.build()?;
|
||||
}
|
||||
message = LiteralWriter::new(message).build()?;
|
||||
|
||||
// XXX: error handling!!!
|
||||
std::thread::scope(|s| {
|
||||
s.spawn(|| -> anyhow::Result<()> {
|
||||
message.write_all(body)?;
|
||||
message.finalize()?;
|
||||
Ok(())
|
||||
});
|
||||
});
|
||||
|
||||
Ok(Bytes::from(sink))
|
||||
} else {
|
||||
@@ -429,6 +450,7 @@ pub fn encrypt(context: &HuskContext<'_>, body: &[u8], recipients: &Vec<Recipien
|
||||
pub enum Role {
|
||||
Local,
|
||||
Introducer,
|
||||
SigningKey,
|
||||
}
|
||||
|
||||
/// Authenticate a certificate.
|
||||
@@ -437,7 +459,17 @@ pub enum Role {
|
||||
/// `domains`. Passing `None` or an empty list in this case is an error.
|
||||
/// If `activate` is false, create a certifiaction with minimal trust_roots
|
||||
/// 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>>) -> anyhow::Result<()> {
|
||||
internal_authenticate(context, cert, role, activate, domains, None)
|
||||
}
|
||||
|
||||
/// Make `cert` the new signing key.
|
||||
pub fn set_signing_key(context: &HuskContext, cert: &ValidCert) -> anyhow::Result<()> {
|
||||
internal_authenticate(context, cert, Role::SigningKey, true, None, Some((HUSK_SIG_NOTATION.to_string(), "placeholder".to_string())))
|
||||
}
|
||||
|
||||
/// Internal authentication method.
|
||||
fn internal_authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activate: bool, domains: Option<Vec<String>>, notation: Option<(String, String)>)
|
||||
-> anyhow::Result<()> {
|
||||
|
||||
let policy = &context.policy;
|
||||
@@ -470,7 +502,7 @@ pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activat
|
||||
|
||||
if activate {
|
||||
// set the trust level and depth for an active local/introducer.
|
||||
if role == Role::Local {
|
||||
if role == Role::Local || role == Role::SigningKey {
|
||||
builder = builder.set_trust_signature(0, 120)?;
|
||||
} else {
|
||||
// Introducer
|
||||
@@ -502,6 +534,14 @@ pub fn authenticate(context: &HuskContext, cert: &ValidCert, role: Role, activat
|
||||
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
|
||||
// valid (non self revoked) user ids.
|
||||
let user_ids: Vec<_> = cert.userids()
|
||||
@@ -559,9 +599,140 @@ pub fn can_encrypt(cert: &ValidCert) -> bool {
|
||||
.count() > 0
|
||||
}
|
||||
|
||||
/// Returns true if `cert` can be used for signing.
|
||||
pub fn can_sign(cert: &ValidCert) -> bool {
|
||||
cert.keys()
|
||||
.for_signing()
|
||||
.alive()
|
||||
.supported()
|
||||
.revoked(false)
|
||||
.count() > 0
|
||||
}
|
||||
|
||||
|
||||
/// Get Husks signing key
|
||||
pub fn get_signing_key(sequoia_home: Home, cert_store: &CertStore<'_>, policy: &StandardPolicy<'_>, local_trust_root: &Cert) -> anyhow::Result<Option<Key>> {
|
||||
|
||||
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;
|
||||
|
||||
let keys = get_all_keys(sequoia_home);
|
||||
for key in keys {
|
||||
if let Ok(cert) = cert_store.lookup_by_cert_fpr(&key.fingerprint()) {
|
||||
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(key.clone());
|
||||
}
|
||||
} else { // first detected key
|
||||
newest = Some(time);
|
||||
result = Some(key.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// If `vc` is a certificate marked as a Husk signing key, return the
|
||||
/// creation time of the signature - else return `None`.
|
||||
pub fn contains_signing_key(vc: &ValidCert, local_root_fpr: &Fingerprint) -> Option<SystemTime> {
|
||||
log::debug!("check {} for signing key", vc.fingerprint());
|
||||
for uid in vc.userids() {
|
||||
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() {
|
||||
log::debug!("found");
|
||||
return Some(time);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get all keys from all backends
|
||||
// XXX: Maybe the softkeys backend is sufficient
|
||||
pub fn get_all_keys(sequoia_home: Home) -> Vec<Key> {
|
||||
|
||||
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 {
|
||||
log::debug!("scanning backend {:?}", backend.id());
|
||||
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.clone());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
pub fn import_key(sequoia_home: Home, cert: &Cert) -> bool {
|
||||
let mut imported = false;
|
||||
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(id) = backend.id() {
|
||||
if id == "softkeys"
|
||||
&& backend.import(cert).is_ok() {
|
||||
imported = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
imported
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
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::get_all_keys;
|
||||
use crate::types::{husk_context::HuskContext, mail_context::MailContext};
|
||||
|
||||
use super::get_signing_key;
|
||||
|
||||
#[test]
|
||||
pub fn test_is_encrypted() {
|
||||
@@ -644,4 +815,81 @@ 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(),
|
||||
subject_replacement: "...".into(),
|
||||
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());
|
||||
for key in keys { eprintln!("{}", key.fingerprint()); }
|
||||
|
||||
super::set_signing_key(&context, &valid_signing_cert)?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
-1
@@ -9,13 +9,14 @@ use toml;
|
||||
use log4rs;
|
||||
|
||||
use crate::types::errors::HuskError;
|
||||
use crate::types::defaults::CONSOLE_LOGGING;
|
||||
use crate::types::defaults::{CONSOLE_LOGGING, SUBJECT_REPLACEMENT};
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct HuskConfig {
|
||||
pub connection: String,
|
||||
pub sequoia_home: String,
|
||||
pub keyservers: Option<Vec<String>>,
|
||||
pub subject_replacement: Option<String>,
|
||||
logging: Option<toml::Value>,
|
||||
}
|
||||
|
||||
@@ -68,6 +69,13 @@ impl HuskConfig {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_subject_replacement(&self) -> String {
|
||||
if let Some(replacement) = &self.subject_replacement {
|
||||
replacement.clone()
|
||||
} else {
|
||||
SUBJECT_REPLACEMENT.to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type HuskConfigContainer = Arc<Mutex<HuskConfig>>;
|
||||
@@ -79,3 +87,23 @@ impl From<HuskConfigContainer> for HuskConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::types::defaults;
|
||||
|
||||
use super::HuskConfig;
|
||||
|
||||
|
||||
#[test]
|
||||
fn subject_line() {
|
||||
let config = HuskConfig {
|
||||
connection: "".into(),
|
||||
sequoia_home: "".into(),
|
||||
keyservers: None,
|
||||
subject_replacement: None,
|
||||
logging: None
|
||||
};
|
||||
|
||||
assert_eq!(config.get_subject_replacement(), defaults::SUBJECT_REPLACEMENT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +241,11 @@ impl Daemon {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Change the subject
|
||||
let _ = cx.actions.change_header(
|
||||
"Subject",
|
||||
1,
|
||||
Some(context.subject_replacement.clone())).await;
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("{}", e);
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
//! Defaults
|
||||
|
||||
pub const CONFIG_FILE_LOCATION: &'static str = "/etc/husk/config.toml";
|
||||
pub const CONFIG_FILE_LOCATION: &str = "/etc/husk/config.toml";
|
||||
|
||||
/// If encrypting a mail, the subject is moved into the encrypted body
|
||||
/// and replaced by `SUBJECT_REPLACEMENT` (unless configured otherwise).
|
||||
pub const SUBJECT_REPLACEMENT: &str = "...";
|
||||
|
||||
/// Default logging configuration.
|
||||
pub const CONSOLE_LOGGING: &'static str = "
|
||||
pub const CONSOLE_LOGGING: &str = "
|
||||
[appenders.console]
|
||||
kind = \"console\"
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ use sequoia_openpgp::policy::StandardPolicy;
|
||||
use sequoia_openpgp::Cert;
|
||||
use sequoia_cert_store::CertStore;
|
||||
use sequoia_directories::Home;
|
||||
use sequoia_keystore::Key;
|
||||
|
||||
use crate::config::HuskConfig;
|
||||
use crate::common::crypto;
|
||||
@@ -26,8 +27,10 @@ pub struct HuskContext<'hc> {
|
||||
pub policy: StandardPolicy<'hc>,
|
||||
pub cert_store: CertStore<'hc>,
|
||||
pub local_trust_root: Cert,
|
||||
pub signing_key: Option<Key>,
|
||||
pub introducers: Vec<Introducer>,
|
||||
pub keyservers: Vec<String>,
|
||||
pub subject_replacement: String,
|
||||
pub mail: MailContext<'hc>,
|
||||
}
|
||||
|
||||
@@ -45,12 +48,26 @@ impl<'hc> HuskContext<'hc> {
|
||||
let local_trust_root = crypto::get_local_trust_root(&cert_store)?;
|
||||
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),
|
||||
Ok(None) => {
|
||||
log::warn!("No signing key found");
|
||||
None
|
||||
},
|
||||
Err(e) => {
|
||||
log::error!("Error while fetching signing key: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
Ok(HuskContext {
|
||||
policy,
|
||||
cert_store,
|
||||
local_trust_root,
|
||||
signing_key,
|
||||
introducers,
|
||||
keyservers: config.get_keyservers(),
|
||||
subject_replacement: config.get_subject_replacement(),
|
||||
mail: MailContext::new(),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user