From d9756ed09a8cce6474c2cd5d2077c162b5b201f0 Mon Sep 17 00:00:00 2001 From: Wiktor Kwapisiewicz Date: Fri, 10 May 2024 11:21:53 +0200 Subject: [PATCH] Add `pgp-wrapper` example Creates an OpenPGP certificate based on the SSH key and allows signing files emitting OpenPGP framed packets. Requires that the first key in SSH is ed25519 (see `ssh-add -L`). Generate a key with: `cargo run --example pgp-wrapper generate "John Doe " > key.pgp` Sign data using: `cargo run --example pgp-wrapper sign < Cargo.toml > Cargo.toml.sig` Import the certificate using GnuPG: ```sh $ gpg --import key.pgp gpg: key A142E92C91BE3AD5: public key "John Doe " imported gpg: Total number processed: 1 gpg: imported: 1 ``` Verify the signature using GnuPG: ```sh $ gpg --verify Cargo.toml.sig gpg: assuming signed data in 'Cargo.toml' gpg: Signature made Fri May 10 11:15:53 2024 CEST gpg: using EDDSA key 4EB27E153DDC454364B36B59A142E92C91BE3AD5 gpg: Good signature from "John Doe " [unknown] gpg: WARNING: This key is not certified with a trusted signature! gpg: There is no indication that the signature belongs to the owner. Primary key fingerprint: 4EB2 7E15 3DDC 4543 64B3 6B59 A142 E92C 91BE 3AD5 ``` Works perfectly in conjunction with `openpgp-card-agent.rs`! Signed-off-by: Wiktor Kwapisiewicz --- Cargo.lock | 689 +++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 + examples/pgp-wrapper.rs | 287 +++++++++++++++++ 3 files changed, 968 insertions(+), 10 deletions(-) create mode 100644 examples/pgp-wrapper.rs diff --git a/Cargo.lock b/Cargo.lock index 1d91132..3d8280d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -95,6 +130,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-lock" version = "2.8.0" @@ -123,7 +170,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -153,18 +200,39 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -174,6 +242,44 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "buffer-redux" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9f8ddd22e0a12391d1e7ada69ec3b0da1914f1cec39c5cf977143c5b2854f5" +dependencies = [ + "memchr", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -192,6 +298,16 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "camellia" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "card-backend" version = "0.2.0" @@ -213,6 +329,15 @@ dependencies = [ "pcsc", ] +[[package]] +name = "cast5" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.0.90" @@ -223,6 +348,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cfb-mode" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" +dependencies = [ + "cipher", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -272,7 +406,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] @@ -284,7 +418,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -293,6 +427,17 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "cmac" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" +dependencies = [ + "cipher", + "dbl", + "digest", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -326,6 +471,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc24" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -345,9 +505,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "curve25519-dalek" version = "4.1.2" @@ -362,6 +532,7 @@ dependencies = [ "platforms", "rustc_version", "subtle", + "zeroize", ] [[package]] @@ -372,7 +543,51 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dbl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" +dependencies = [ + "generic-array", ] [[package]] @@ -386,6 +601,46 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + +[[package]] +name = "des" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" +dependencies = [ + "cipher", +] + [[package]] name = "digest" version = "0.10.7" @@ -398,6 +653,35 @@ dependencies = [ "subtle", ] +[[package]] +name = "dsa" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" +dependencies = [ + "digest", + "num-bigint-dig", + "num-traits", + "pkcs8", + "rfc6979", + "sha2", + "signature", + "zeroize", +] + +[[package]] +name = "eax" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" +dependencies = [ + "aead", + "cipher", + "cmac", + "ctr", + "subtle", +] + [[package]] name = "ecdsa" version = "0.16.9" @@ -418,6 +702,7 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ + "pkcs8", "signature", ] @@ -429,8 +714,10 @@ checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", + "serde", "sha2", "subtle", + "zeroize", ] [[package]] @@ -445,6 +732,7 @@ dependencies = [ "ff", "generic-array", "group", + "hkdf", "pem-rfc7468", "pkcs8", "rand_core", @@ -476,6 +764,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "event-listener" version = "2.5.3" @@ -498,6 +792,22 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38793c55593b33412e3ae40c2c9781ffaa6f438f6f8c10f24e71846fbd7ae01e" +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "futures" version = "0.3.30" @@ -554,7 +864,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -615,6 +925,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -638,6 +958,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.5.0" @@ -650,6 +976,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hex-literal" version = "0.4.1" @@ -662,6 +994,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5491a308e0214554f07a81d8944abe45f552871c12e3c3c6e7e5d354039a6c4c" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -700,6 +1041,31 @@ dependencies = [ "cc", ] +[[package]] +name = "idea" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" +dependencies = [ + "cipher", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inout" version = "0.1.3" @@ -718,6 +1084,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "iter-read" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a598c1abae8e3456ebda517868b254b6bc2a9bb6501ffd5b9d0875bf332e048b" + [[package]] name = "jobserver" version = "0.1.28" @@ -736,6 +1108,29 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -774,6 +1169,16 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.2" @@ -829,6 +1234,7 @@ dependencies = [ "num-iter", "num-traits", "rand", + "serde", "smallvec", "zeroize", ] @@ -873,6 +1279,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "object" version = "0.32.2" @@ -882,12 +1309,30 @@ dependencies = [ "memchr", ] +[[package]] +name = "ocb3" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openpgp-card" version = "0.4.2" @@ -940,6 +1385,17 @@ dependencies = [ "sha2", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "pcsc" version = "2.8.2" @@ -968,6 +1424,69 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pgp" +version = "0.12.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3ade0b42d408bca5a520179f2ef77fb30b64083f0802e167a509ceba4abc2" +dependencies = [ + "aes", + "aes-gcm", + "argon2", + "base64", + "bitfield", + "block-padding", + "blowfish", + "bstr", + "buffer-redux", + "byteorder", + "camellia", + "cast5", + "cfb-mode", + "chrono", + "cipher", + "const-oid", + "crc24", + "curve25519-dalek", + "derive_builder", + "des", + "digest", + "dsa", + "eax", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "flate2", + "generic-array", + "hex", + "hkdf", + "idea", + "iter-read", + "k256", + "log", + "md-5", + "nom", + "num-bigint-dig", + "num-traits", + "num_enum", + "ocb3", + "p256", + "p384", + "p521", + "rand", + "ripemd", + "rsa", + "sha1", + "sha2", + "sha3", + "signature", + "smallvec", + "thiserror", + "twofish", + "x25519-dalek", + "zeroize", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1013,6 +1532,18 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1028,6 +1559,15 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -1142,6 +1682,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + [[package]] name = "rsa" version = "0.9.6" @@ -1189,7 +1738,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.58", "unicode-ident", ] @@ -1237,6 +1786,26 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +[[package]] +name = "serde" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "service-binding" version = "2.1.0" @@ -1268,6 +1837,16 @@ dependencies = [ "digest", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "signature" version = "2.2.0" @@ -1326,6 +1905,7 @@ dependencies = [ "async-trait", "byteorder", "card-backend-pcsc", + "chrono", "clap", "const-str", "env_logger", @@ -1334,6 +1914,7 @@ dependencies = [ "log", "openpgp-card", "p256", + "pgp", "rand", "retainer", "rsa", @@ -1403,6 +1984,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -1415,6 +2002,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.58" @@ -1449,7 +2047,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1476,7 +2074,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1493,6 +2091,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -1512,6 +2127,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "twofish" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" +dependencies = [ + "cipher", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1524,6 +2148,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -1569,7 +2203,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -1591,7 +2225,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1765,8 +2399,43 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + [[package]] name = "zeroize" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] diff --git a/Cargo.toml b/Cargo.toml index 209bd0f..92dc2fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,3 +59,5 @@ card-backend-pcsc = "0.5.0" clap = { version = "4.5.4", features = ["derive"] } secrecy = "0.8.0" retainer = "0.3.0" +pgp = "0.12.0-alpha.2" +chrono = "0.4.38" diff --git a/examples/pgp-wrapper.rs b/examples/pgp-wrapper.rs new file mode 100644 index 0000000..e597c4e --- /dev/null +++ b/examples/pgp-wrapper.rs @@ -0,0 +1,287 @@ +//! OpenPGP wrapper for SSH keys +//! +//! Creates an OpenPGP certificate based on the SSH key and allows signing files +//! emitting OpenPGP framed packets. +//! +//! Requires that the first key in SSH is ed25519 (see `ssh-add -L`). +//! +//! Generate a key with: +//! `cargo run --example pgp-wrapper generate "John Doe " > key.pgp` +//! +//! Sign data using: +//! `cargo run --example pgp-wrapper sign < Cargo.toml > Cargo.toml.sig` +//! +//! Import the certificate using GnuPG: +//! ```sh +//! $ gpg --import key.pgp +//! gpg: key A142E92C91BE3AD5: public key "John Doe " imported +//! gpg: Total number processed: 1 +//! gpg: imported: 1 +//! ``` +//! +//! Verify the signature using GnuPG: +//! ```sh +//! gpg --verify Cargo.toml.sig +//! gpg: assuming signed data in 'Cargo.toml' +//! gpg: Signature made Fri May 10 11:15:53 2024 CEST +//! gpg: using EDDSA key 4EB27E153DDC454364B36B59A142E92C91BE3AD5 +//! gpg: Good signature from "John Doe " [unknown] +//! gpg: WARNING: This key is not certified with a trusted signature! +//! gpg: There is no indication that the signature belongs to the owner. +//! Primary key fingerprint: 4EB2 7E15 3DDC 4543 64B3 6B59 A142 E92C 91BE 3AD5 +//! ``` +//! +//! Works perfectly in conjunction with `openpgp-card-agent.rs`! + +use std::{cell::RefCell, pin::Pin}; + +use chrono::DateTime; +use clap::Parser; +use pgp::{ + crypto::{ecc_curve::ECCCurve, hash::HashAlgorithm, public_key::PublicKeyAlgorithm}, + packet::{ + KeyFlags, PublicKey, SignatureConfig, SignatureType, SignatureVersion, Subpacket, + SubpacketData, UserId, + }, + ser::Serialize, + types::{KeyTrait, KeyVersion, Mpi, PublicKeyTrait, PublicParams, SecretKeyTrait, Version}, + KeyDetails, Signature, +}; +use service_binding::Binding; +use ssh_agent_lib::{agent::Session, client::connect, proto::SignRequest}; +use ssh_key::public::KeyData; +use tokio::runtime::Runtime; + +struct WrappedKey { + public_key: PublicKey, + pubkey: KeyData, + client: RefCell>>, +} + +impl WrappedKey { + fn new(pubkey: KeyData, client: Pin>) -> Self { + let KeyData::Ed25519(key) = pubkey.clone() else { + panic!("The first key was not ed25519!"); + }; + + let mut key_bytes = key.0.to_vec(); + // Add prefix to mark that this MPI uses EdDSA point representation. + // See https://datatracker.ietf.org/doc/draft-koch-eddsa-for-openpgp/ + key_bytes.insert(0, 0x40); + + let public_key = PublicKey::new( + Version::New, + KeyVersion::V4, + PublicKeyAlgorithm::EdDSA, + // use fixed date so that the fingerprint generation is deterministic + DateTime::parse_from_rfc3339("2016-09-06T17:00:00+02:00") + .expect("date to be valid") + .into(), + None, + PublicParams::EdDSA { + curve: ECCCurve::Ed25519, + q: key_bytes.into(), + }, + ) + .expect("key to be valid"); + + Self { + pubkey, + client: RefCell::new(client), + public_key, + } + } +} + +impl std::fmt::Debug for WrappedKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "WrappedKey") + } +} + +impl KeyTrait for WrappedKey { + fn fingerprint(&self) -> Vec { + self.public_key.fingerprint() + } + + fn key_id(&self) -> pgp::types::KeyId { + self.public_key.key_id() + } + + fn algorithm(&self) -> pgp::crypto::public_key::PublicKeyAlgorithm { + self.public_key.algorithm() + } +} + +impl PublicKeyTrait for WrappedKey { + fn verify_signature( + &self, + hash: pgp::crypto::hash::HashAlgorithm, + data: &[u8], + sig: &[pgp::types::Mpi], + ) -> pgp::errors::Result<()> { + self.public_key.verify_signature(hash, data, sig) + } + + fn encrypt( + &self, + rng: &mut R, + plain: &[u8], + ) -> pgp::errors::Result> { + self.public_key.encrypt(rng, plain) + } + + fn to_writer_old(&self, writer: &mut impl std::io::Write) -> pgp::errors::Result<()> { + self.public_key.to_writer_old(writer) + } +} + +impl SecretKeyTrait for WrappedKey { + type PublicKey = PublicKey; + + type Unlocked = Self; + + fn unlock(&self, _pw: F, _work: G) -> pgp::errors::Result + where + F: FnOnce() -> String, + G: FnOnce(&Self::Unlocked) -> pgp::errors::Result, + { + unimplemented!("key unlock is implemented in the ssh agent") + } + + #[allow(clippy::await_holding_refcell_ref)] + fn create_signature( + &self, + _key_pw: F, + _hash: pgp::crypto::hash::HashAlgorithm, + data: &[u8], + ) -> pgp::errors::Result> + where + F: FnOnce() -> String, + { + let signature = Runtime::new() + .expect("creating runtime to succeed") + .handle() + .block_on(async { + let mut client = self.client.try_borrow_mut().expect("not to be shared"); + let result = client.sign(SignRequest { + pubkey: self.pubkey.clone(), + data: data.to_vec(), + flags: 0, + }); + result.await + }) + .expect("signing to succeed"); + + let sig = &signature.as_bytes(); + + assert_eq!(sig.len(), 64); + + Ok(vec![ + Mpi::from_raw_slice(&sig[..32]), + Mpi::from_raw_slice(&sig[32..]), + ]) + } + + fn public_key(&self) -> Self::PublicKey { + self.public_key.clone() + } + + fn public_params(&self) -> &pgp::types::PublicParams { + self.public_key.public_params() + } +} + +#[derive(Debug, Parser)] +enum Args { + Generate { userid: String }, + Sign, +} + +fn main() -> testresult::TestResult { + let args = Args::parse(); + + let rt = Runtime::new()?; + + let (client, identities) = rt.block_on(async move { + #[cfg(unix)] + let mut client = + connect(Binding::FilePath(std::env::var("SSH_AUTH_SOCK")?.into()).try_into()?).await?; + + #[cfg(windows)] + let mut client = + connect(Binding::NamedPipe(std::env::var("SSH_AUTH_SOCK")?.into()).try_into()?).await?; + + let identities = client.request_identities().await?; + + if identities.is_empty() { + panic!("We need at least one ed25519 identity!"); + } + + Ok::<_, testresult::TestError>((client, identities)) + })?; + + let pubkey = &identities[0].pubkey; + + let signer = WrappedKey::new(pubkey.clone(), client); + + match args { + Args::Generate { userid } => { + let mut keyflags = KeyFlags::default(); + keyflags.set_sign(true); + keyflags.set_certify(true); + + let composed_pk = pgp::PublicKey::new( + signer.public_key(), + KeyDetails::new( + UserId::from_str(Default::default(), &userid), + vec![], + vec![], + keyflags, + Default::default(), + Default::default(), + Default::default(), + None, + ), + vec![], + ); + let signed_pk = composed_pk.sign(&signer, String::new)?; + signed_pk.to_writer(&mut std::io::stdout())?; + } + Args::Sign => { + let signature = SignatureConfig::new_v4( + SignatureVersion::V4, + SignatureType::Binary, + signer.algorithm(), + HashAlgorithm::SHA2_256, + vec![ + Subpacket::regular(SubpacketData::SignatureCreationTime( + std::time::SystemTime::now().into(), + )), + Subpacket::regular(SubpacketData::Issuer(signer.key_id())), + Subpacket::regular(SubpacketData::IssuerFingerprint( + KeyVersion::V4, + signer.fingerprint().into(), + )), + ], + vec![], + ); + + let mut hasher = signature.hash_alg.new_hasher()?; + + signature.hash_data_to_sign(&mut *hasher, std::io::stdin())?; + let len = signature.hash_signature_data(&mut *hasher)?; + hasher.update(&signature.trailer(len)?); + + let hash = &hasher.finish()[..]; + + let signed_hash_value = [hash[0], hash[1]]; + let raw_sig = signer.create_signature(String::new, HashAlgorithm::SHA2_256, hash)?; + + let signature = Signature::from_config(signature, signed_hash_value, raw_sig); + pgp::packet::write_packet(&mut std::io::stdout(), &signature)?; + } + } + + Ok(()) +}