diff --git a/Cargo.lock b/Cargo.lock index 950820d..a3f2e95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,7 +391,7 @@ checksum = "83fdaf97f4804dcebfa5862639bc9ce4121e82140bec2a987ac5140294865b5b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.77", ] [[package]] @@ -418,7 +418,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.15", + "syn 2.0.77", ] [[package]] @@ -435,7 +435,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.77", ] [[package]] @@ -513,6 +513,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -691,9 +702,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -725,14 +736,134 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "idna" -version = "0.4.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "bd69211b9b519e98303c015e21a007e293db403b6c85b9b124e133d25e242cdd" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] @@ -804,7 +935,7 @@ dependencies = [ [[package]] name = "johnnycanencrypt" -version = "0.14.1" +version = "0.15.0" dependencies = [ "anyhow", "chrono", @@ -906,6 +1037,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f508063cc7bb32987c71511216bd5a32be15bccb6a80b52df8b9d7f01fc3aa2" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.9" @@ -1169,16 +1306,16 @@ dependencies = [ ] [[package]] -name = "powerfmt" -version = "0.2.0" +name = "portable-atomic" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "powerfmt" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "precomputed-hash" @@ -1188,24 +1325,25 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" -version = "0.20.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8453b658fe480c3e70c8ed4e3d3ec33eb74988bd186561b0cc66b85c3bc4b" +checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", - "parking_lot", + "once_cell", + "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", @@ -1214,9 +1352,9 @@ dependencies = [ [[package]] name = "pyo3-build-config" -version = "0.20.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96fe70b176a89cff78f2fa7b3c930081e163d5379b4dcdf993e3ae29ca662e5" +checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" dependencies = [ "once_cell", "target-lexicon", @@ -1224,9 +1362,9 @@ dependencies = [ [[package]] name = "pyo3-ffi" -version = "0.20.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "214929900fd25e6604661ed9cf349727c8920d47deff196c4e28165a6ef2a96b" +checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" dependencies = [ "libc", "pyo3-build-config", @@ -1234,58 +1372,38 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.20.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac53072f717aa1bfa4db832b39de8c875b7c7af4f4a6fe93cdbf9264cf8383b" +checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.15", + "syn 2.0.77", ] [[package]] name = "pyo3-macros-backend" -version = "0.20.0" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7774b5a8282bd4f25f803b1f0d945120be959a36c72e08e7cd031c792fdfd424" +checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372" dependencies = [ "heck", "proc-macro2", + "pyo3-build-config", "quote", - "syn 2.0.15", + "syn 2.0.77", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -1408,9 +1526,9 @@ checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" [[package]] name = "sequoia-openpgp" -version = "1.17.0" +version = "1.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ea026cf8a70d331c742e3ad7e68fd405d0743ff86630fb4334a1bf8d0e194c7" +checksum = "13261ee216b44d932ef93b2d4a75d45199bef77864bcc5b77ecfc7bc0ecb02d6" dependencies = [ "aes-gcm", "anyhow", @@ -1434,7 +1552,6 @@ dependencies = [ "nettle", "num-bigint-dig", "once_cell", - "rand", "rand_core", "regex", "regex-syntax 0.8.2", @@ -1504,9 +1621,9 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spin" @@ -1535,6 +1652,12 @@ dependencies = [ "sha2 0.8.2", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "string_cache" version = "0.8.7" @@ -1567,15 +1690,26 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "talktosc" version = "0.2.0" @@ -1588,9 +1722,9 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.6" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" @@ -1642,7 +1776,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.77", ] [[package]] @@ -1684,47 +1818,27 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - [[package]] name = "unicode-ident" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-width" version = "0.1.10" @@ -1753,6 +1867,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2016,14 +2142,93 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "xxhash-rust" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "synstructure", +] + [[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/Cargo.toml b/Cargo.toml index c889364..7610fc4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "johnnycanencrypt" -version = "0.14.1" +version = "0.15.0" authors = ["Kushal Das "] edition = "2021" description = "Python module for OpenPGP." @@ -32,14 +32,14 @@ regex = "1" time = "0.3" [target.'cfg(not(target_os = "windows"))'.dependencies] -sequoia-openpgp = { default-features = false, features = ["crypto-nettle", "compression"] } +sequoia-openpgp = { version="1.21.2", default-features = false, features = ["crypto-nettle", "compression"] } [target.'cfg(target_os = "windows")'.dependencies] -sequoia-openpgp = { default-features = false, features = ["crypto-cng", "compression"] } +sequoia-openpgp = { version="1.21.2", default-features = false, features = ["crypto-cng", "compression"] } [dependencies.pyo3] -version = "0.20.0" +version = "0.22.2" diff --git a/changelog.md b/changelog.md index 9a6729d..c33d124 100644 --- a/changelog.md +++ b/changelog.md @@ -11,8 +11,12 @@ - Adds `pathlib.Path` support to `KeyStore` #148. - Adds Windows CI support #144. -- Updates pyo3 to `0.20.0` -- Updates time to `0.3.30` +- Updates pyo3 to `0.20.0`. +- Updates time to `0.3.30`. +- Adds API for changing primary key expiry date via rjce #151. +- Adds API for changing subkeys expiry date via rjce #152. +- Updates sequoia dependency to 1.21.2. +- Updates pyo3 dependency to 0.22.2. ## [0.14.1] - 2023-05-21 diff --git a/docs/rustimplementation.rst b/docs/rustimplementation.rst index e2a4831..0cbc191 100644 --- a/docs/rustimplementation.rst +++ b/docs/rustimplementation.rst @@ -5,7 +5,7 @@ You can access the low level functions or `Johnny` class by the following way: :: - >>> from johnnycanencrypt import johnnycanencrypt as jce + >>> from johnnycanencrypt import johnnycanencrypt as rjce In most cases you don't have to use these, but if you have a reason, feel free to use them. @@ -16,7 +16,7 @@ In most cases you don't have to use these, but if you have a reason, feel free t :: - >>> jce.encrypt_bytes_to_file(["tests/files/public.asc", "tests/files/hellopublic.asc"], b"Hello clear text", b"/tmp/encrypted_text.asc", armor=True) + >>> rjce.encrypt_bytes_to_file(["tests/files/public.asc", "tests/files/hellopublic.asc"], b"Hello clear text", b"/tmp/encrypted_text.asc", armor=True) .. note:: Use this function if you have to encrypt for multiple recipents. @@ -43,7 +43,17 @@ In most cases you don't have to use these, but if you have a reason, feel free t >>> rjce.get_key_cipher_details(key.keyvalue) [('F4F388BBB194925AE301F844C52B42177857DD79', 'EdDSA', 256), ('102EBD23BD5D2D340FBBDE0ADFD1C55926648D2F', 'EdDSA', 256), ('85B67F139D835FA56BA703DB5A7A1560D46ED4F6', 'ECDH', 256)] +.. function:: update_primary_expiry_on_card(certdata: bytes, expiry: int, pin: bytes) -> bytes: + This function updates the expiry date using the Yubikey and public key as `certdata`. You will have to pass the expiry as `int` number of seconds (after which the key will expire). + + .. versionadded:: 0.15.0 + +.. function:: update_subkeys_expiry_on_card(certdata: bytes, fingerprints: List[str], expiry: int, pin: bytes) -> bytes: + + This function updates the expiry date of the given subkeys using Yubikey (the primary key must be on the Yubikey). You will have to pass the expiry as `int` number of seconds (after which the key will expire). + + .. versionadded:: 0.15.0 .. class:: Johnny(filepath) diff --git a/pyproject.toml b/pyproject.toml index 6720779..17cedbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["setuptools", "wheel", "setuptools-rust"] [project] name = "johnnycanencrypt" -version = "0.14.1" +version = "0.15.0" classifiers = [ "Development Status :: 4 - Beta", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", diff --git a/src/lib.rs b/src/lib.rs index a623cfe..16548c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,7 @@ use pyo3::types::PyBytes; use pyo3::types::PyTuple; use pyo3::types::{PyDateTime, PyDict, PyList}; use pyo3::wrap_pyfunction; -use pyo3::PyDowncastError; +use pyo3::DowncastError; use std::collections::HashMap; use std::fmt; @@ -136,10 +136,10 @@ impl JceError { /// Finds all keys in a cert and creates a list of fingerprint, algo, bitsize #[pyfunction] -#[pyo3(text_signature = "(certdata)")] -pub fn get_key_cipher_details(py: Python, certdata: Vec) -> Result { +#[pyo3(signature = (certdata))] +pub fn get_key_cipher_details<'py>(py: Python, certdata: Vec) -> Result { let cert = openpgp::Cert::from_bytes(&certdata)?; - let list = PyList::empty(py); + let list = PyList::empty_bound(py); let p = &P::new(); for key in cert.with_policy(p, None)?.keys() { @@ -147,10 +147,13 @@ pub fn get_key_cipher_details(py: Python, certdata: Vec) -> Result let key_algo = key.pk_algo(); let bits = key.mpis().bits(); let algo = key_algo.to_string().clone(); - let key_tuple = (fp.clone(), algo, bits.clone()).to_object(py); + let key_tuple: Py = (fp.clone(), algo, bits).to_object(py); + + // let key_tuple: std::result::Result<&PyTuple, PyDowncastError> = + // key_tuple.downcast::(py); - let key_tuple: std::result::Result<&PyTuple, PyDowncastError> = - key_tuple.downcast::(py); + let key_tuple: std::result::Result<&Bound<'_, PyTuple>, DowncastError> = + key_tuple.downcast_bound::(py); let kt = match key_tuple { Ok(value) => value, @@ -161,9 +164,9 @@ pub fn get_key_cipher_details(py: Python, certdata: Vec) -> Result ))) } }; + // list.append(<&PyTuple>::clone(&kt))?; list.append(kt.clone())?; } - Ok(list.into()) } @@ -216,7 +219,7 @@ pub fn update_subkeys_expiry_in_cert( writer.finalize()?; // Let us return the cert data which can be saved in the database - let res = PyBytes::new(py, &buf); + let res = PyBytes::new_bound(py, &buf); Ok(res.into()) } @@ -281,7 +284,7 @@ pub fn revoke_uid_in_cert( writer.finalize()?; // Let us return the cert data which can be saved in the database - let res = PyBytes::new(py, &buf); + let res = PyBytes::new_bound(py, &buf); Ok(res.into()) } @@ -339,7 +342,7 @@ pub fn add_uid_in_cert( writer.finalize()?; // Let us return the cert data which can be saved in the database - let res = PyBytes::new(py, &buf); + let res = PyBytes::new_bound(py, &buf); Ok(res.into()) } @@ -393,7 +396,7 @@ pub fn update_password( writer.write_all(&buffer)?; writer.finalize()?; // Let us return the cert data which can be saved in the database - let res = PyBytes::new(py, &buf); + let res = PyBytes::new_bound(py, &buf); Ok(res.into()) } @@ -493,7 +496,7 @@ fn decrypt_bytes_on_card( Err(msg) => return Err(JceError::new(format!("Failed to decrypt: {}", msg))), }; std::io::copy(&mut decryptor, &mut result)?; - let res = PyBytes::new(_py, &result); + let res = PyBytes::new_bound(_py, &result); Ok(res.into()) } @@ -523,7 +526,7 @@ pub fn decrypt_file_on_card( #[pyfunction] #[pyo3(text_signature = "(certdata, fh, output, pin)")] pub fn decrypt_filehandler_on_card( - _py: Python, + py: Python, certdata: Vec, fh: PyObject, output: Vec, @@ -531,10 +534,9 @@ pub fn decrypt_filehandler_on_card( ) -> Result { let p = P::new(); - let filedata = fh.call_method(_py, "read", (), None)?; - let pbytes: &PyBytes = filedata - .as_ref(_py) - .downcast::() + let filedata = fh.call_method_bound(py, "read", (), None)?; + let pbytes: &Bound<'_, PyBytes> = filedata + .downcast_bound::(py) .expect("Excepted bytes"); let data: Vec = Vec::from(pbytes.as_bytes()); @@ -644,7 +646,7 @@ fn get_card_details(py: Python) -> PyResult { Err(value) => return Err(CardError::new_err(format!("{}", value))), }; - let pd = PyDict::new(py); + let pd = PyDict::new_bound(py); pd.set_item("serial_number", tlvs::parse_card_serial(resp.get_data()))?; // Now the name of the card holder let resp = talktosc::send_and_parse(&card, apdus::create_apdu_personal_information()); @@ -1131,7 +1133,7 @@ fn certify_key( writer.finalize()?; // Let us return the cert data which can be saved in the database - let res = PyBytes::new(py, &buf); + let res = PyBytes::new_bound(py, &buf); Ok(res.into()) } @@ -1471,7 +1473,7 @@ fn sign_bytes_internal( sink.finalize()?; // Let us return the cert data which can be saved in the database - let res = PyBytes::new(py, &result); + let res = PyBytes::new_bound(py, &result); Ok(res.into()) } @@ -1534,7 +1536,7 @@ fn merge_keys( // Remember, the opposite is a security risk. let mergred_cert = cert.merge_public_and_secret(newcert)?; let cert_packets = mergred_cert.armored().to_vec()?; - let res = PyBytes::new(_py, &cert_packets); + let res = PyBytes::new_bound(_py, &cert_packets); Ok(res.into()) } @@ -1545,7 +1547,7 @@ fn merge_keys( #[pyo3(text_signature = "(filepath)")] fn file_encrypted_for(_py: Python, filepath: String) -> Result { let mut ppr = PacketParser::from_file(filepath)?; - let plist = PyList::empty(_py); + let plist = PyList::empty_bound(_py); while let PacketParserResult::Some(pp) = ppr { // Get the packet out of the parser and start parsing the next // packet, recursing. @@ -1567,7 +1569,7 @@ fn file_encrypted_for(_py: Python, filepath: String) -> Result { #[pyo3(text_signature = "(messagedata)")] fn bytes_encrypted_for(_py: Python, messagedata: Vec) -> Result { let mut ppr = PacketParser::from_bytes(&messagedata[..])?; - let plist = PyList::empty(_py); + let plist = PyList::empty_bound(_py); while let PacketParserResult::Some(pp) = ppr { // Get the packet out of the parser and start parsing the next // packet, recursing. @@ -1658,7 +1660,7 @@ fn upload_to_smartcard( fn get_signing_pubkey(py: Python, certdata: Vec) -> Result { // Note: For now we will return the first signing key (maybe the primary key). use std::fmt::Write; - let pd = PyDict::new(py); + let pd = PyDict::new_bound(py); let cert = openpgp::Cert::from_bytes(&certdata)?; let policy = P::new(); @@ -1698,7 +1700,7 @@ fn get_signing_pubkey(py: Python, certdata: Vec) -> Result { } #[pyfunction] -#[pyo3(text_signature = "(certdata, comment)")] +#[pyo3(signature = (certdata, comment=None))] fn get_ssh_pubkey(_py: Python, certdata: Vec, comment: Option) -> Result { let cert = openpgp::Cert::from_bytes(&certdata)?; @@ -2178,7 +2180,11 @@ fn internal_parse_cert( Ok(value) => { let ctime = value.creation_time(); let dt: DateTime = DateTime::from(ctime); - Some(PyDateTime::from_timestamp(py, dt.timestamp() as f64, None)?) + Some(PyDateTime::from_timestamp_bound( + py, + dt.timestamp() as f64, + None, + )?) } _ => None, }; @@ -2187,7 +2193,11 @@ fn internal_parse_cert( Ok(value) => match value.key_expiration_time() { Some(etime) => { let dt: DateTime = DateTime::from(etime); - Some(PyDateTime::from_timestamp(py, dt.timestamp() as f64, None)?) + Some(PyDateTime::from_timestamp_bound( + py, + dt.timestamp() as f64, + None, + )?) } _ => None, }, @@ -2200,9 +2210,9 @@ fn internal_parse_cert( return Err(JceError::new(err_msg.join(", "))); } }; - let plist = PyList::empty(py); + let plist = PyList::empty_bound(py); for ua in cert.userids() { - let pd = PyDict::new(py); + let pd = PyDict::new_bound(py); //println!(" {}", String::from_utf8_lossy(ua.value())); pd.set_item("value", String::from_utf8_lossy(ua.value()))?; // If we have a name part in the UID @@ -2232,7 +2242,7 @@ fn internal_parse_cert( pd.set_item("revoked", revoked)?; // This list contains a list of dictionary - let certification_list = PyList::empty(py); + let certification_list = PyList::empty_bound(py); // Now we will deal with the certifications, that is if anyone else signed this UID. for c in ua.certifications() { // This is the type of certification @@ -2249,7 +2259,11 @@ fn internal_parse_cert( match sct { Some(sct_value) => { let dt: DateTime = DateTime::from(sct_value); - Some(PyDateTime::from_timestamp(py, dt.timestamp() as f64, None)?) + Some(PyDateTime::from_timestamp_bound( + py, + dt.timestamp() as f64, + None, + )?) } None => None, } @@ -2257,21 +2271,21 @@ fn internal_parse_cert( // Now we need a list of issuers for this certification // - let issuer_list = PyList::empty(py); + let issuer_list = PyList::empty_bound(py); for issuer in c.get_issuers() { //let fp_keyid = PyTuple::new(py, &[issuer.]) match issuer { - KeyHandle::Fingerprint(finger) => issuer_list.append(PyTuple::new( + KeyHandle::Fingerprint(finger) => issuer_list.append(PyTuple::new_bound( py, &["fingerprint".to_string(), finger.to_hex()], ))?, KeyHandle::KeyID(kid) => issuer_list - .append(PyTuple::new(py, &["keyid".to_string(), kid.to_hex()]))?, + .append(PyTuple::new_bound(py, &["keyid".to_string(), kid.to_hex()]))?, } } // Now add this one certification - let ud_dict = PyDict::new(py); + let ud_dict = PyDict::new_bound(py); ud_dict.set_item("certification_type", c_type)?; ud_dict.set_item("certification_list", issuer_list)?; ud_dict.set_item("creationtime", creationtime)?; @@ -2285,19 +2299,27 @@ fn internal_parse_cert( plist.append(pd)?; } - let subkeys = PyList::empty(py); + let subkeys = PyList::empty_bound(py); for ka in cert.keys().with_policy(&p, None).subkeys() { let expirationtime = match ka.key_expiration_time() { Some(etime) => { let dt: DateTime = DateTime::from(etime); - Some(PyDateTime::from_timestamp(py, dt.timestamp() as f64, None)?) + Some(PyDateTime::from_timestamp_bound( + py, + dt.timestamp() as f64, + None, + )?) } _ => None, }; let creationtime = { let dt: DateTime = DateTime::from(ka.creation_time()); - Some(PyDateTime::from_timestamp(py, dt.timestamp() as f64, None)?) + Some(PyDateTime::from_timestamp_bound( + py, + dt.timestamp() as f64, + None, + )?) }; // To find what kind of subkey is this. @@ -2335,7 +2357,7 @@ fn internal_parse_cert( _ => false, }; - let othervalues = PyDict::new(py); + let othervalues = PyDict::new_bound(py); othervalues.set_item("keyid", cert.primary_key().keyid().to_hex())?; othervalues.set_item("subkeys", subkeys)?; othervalues.set_item("can_primary_sign", can_primary_sign)?; @@ -2500,19 +2522,16 @@ fn create_key( /// Always remember to open the file in the Python side in "rb" mode, so that the `read()` call can /// return bytes. #[pyfunction] -#[pyo3(text_signature = "(publickeys, fh, output, armor=False)")] +#[pyo3(signature = (publickeys, fh, output, armor=false))] fn encrypt_filehandler_to_file( - _py: Python, + py: Python, publickeys: Vec>, fh: PyObject, output: Vec, armor: Option, ) -> Result { - let data = fh.call_method(_py, "read", (), None)?; - let pbytes: &PyBytes = data - .as_ref(_py) - .downcast::() - .expect("Excepted bytes"); + let data = fh.call_method_bound(py, "read", (), None)?; + let pbytes: &Bound<'_, PyBytes> = data.downcast_bound::(py).expect("Excepted bytes"); let filedata: Vec = Vec::from(pbytes.as_bytes()); encrypt_bytes_to_file(publickeys, filedata, output, armor) } @@ -2520,7 +2539,7 @@ fn encrypt_filehandler_to_file( /// This function takes a list of public key paths, and encrypts the given data in bytes to an output /// file. You can also pass boolen flag armor for armored output. #[pyfunction] -#[pyo3(text_signature = "(publickeys, data, output, armor=False)")] +#[pyo3(signature = (publickeys, data, output, armor=false))] fn encrypt_bytes_to_file( publickeys: Vec>, data: Vec, @@ -2599,7 +2618,7 @@ fn encrypt_bytes_to_file( /// This function takes a list of public key paths, and encrypts the given filepath to an output /// file. You can also pass boolen flag armor for armored output. #[pyfunction] -#[pyo3(text_signature = "(publickeys, filepath, output, armor=False)")] +#[pyo3(signature = (publickeys, filepath, output, armor=false))] fn encrypt_file_internal( publickeys: Vec>, filepath: Vec, @@ -2680,7 +2699,7 @@ fn encrypt_file_internal( /// This function takes a list of public key paths, and encrypts the given data in bytes and returns it. /// You can also pass boolen flag armor for armored output. #[pyfunction] -#[pyo3(text_signature = "(publickeys, data, armor=False)")] +#[pyo3(signature = (publickeys, data, armor=false))] fn encrypt_bytes_to_bytes( py: Python, publickeys: Vec>, @@ -2732,11 +2751,11 @@ fn encrypt_bytes_to_bytes( Some(true) => { // Finalize the armor writer. sink.finalize().expect("Failed to write data"); - let res = PyBytes::new(py, &result2); + let res = PyBytes::new_bound(py, &result2); Ok(res.into()) } _ => { - let res = PyBytes::new(py, &result); + let res = PyBytes::new_bound(py, &result); Ok(res.into()) } } @@ -2756,6 +2775,7 @@ impl Johnny { Ok(Johnny { cert }) } + #[pyo3(signature = (data, armor=false))] pub fn encrypt_bytes( &self, py: Python, @@ -2801,11 +2821,11 @@ impl Johnny { Some(true) => { // Finalize the armor writer. sink.finalize().expect("Failed to write data"); - let res = PyBytes::new(py, &result2); + let res = PyBytes::new_bound(py, &result2); Ok(res.into()) } _ => { - let res = PyBytes::new(py, &result); + let res = PyBytes::new_bound(py, &result); Ok(res.into()) } } @@ -2828,9 +2848,10 @@ impl Johnny { Err(msg) => return Err(JceError::new(format!("Failed to decrypt: {}", msg))), }; std::io::copy(&mut decryptor, &mut result)?; - let res = PyBytes::new(py, &result); + let res = PyBytes::new_bound(py, &result); Ok(res.into()) } + #[pyo3(signature = (filepath, output, armor=false))] pub fn encrypt_file( &self, filepath: Vec, @@ -2923,17 +2944,16 @@ impl Johnny { pub fn decrypt_filehandler( &self, - _py: Python, + py: Python, fh: PyObject, output: Vec, password: String, ) -> Result { let p = P::new(); - let filedata = fh.call_method(_py, "read", (), None)?; - let pbytes: &PyBytes = filedata - .as_ref(_py) - .downcast::() + let filedata = fh.call_method_bound(py, "read", (), None)?; + let pbytes: &Bound<'_, PyBytes> = filedata + .downcast_bound::(py) .expect("Excepted bytes"); let data: Vec = Vec::from(pbytes.as_bytes()); @@ -3037,7 +3057,7 @@ impl Johnny { tmp.seek(SeekFrom::Start(0))?; let mut inside: Vec = Vec::new(); let _ = tmp.read_to_end(&mut inside)?; - let output = PyBytes::new(py, &inside); + let output = PyBytes::new_bound(py, &inside); Ok(output.into()) } @@ -3077,13 +3097,13 @@ pub fn get_card_version(py: Python) -> Result { Ok(value) => value, Err(_) => return Err(JceError::new("Can not get Yubikey version".to_string())), }; - let result = PyTuple::new(py, data.iter()); + let result = PyTuple::new_bound(py, data.iter()); Ok(result.into()) } /// TouchMode for Yubikeys -#[pyclass] -#[derive(Clone, Debug)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Debug, PartialEq)] pub enum TouchMode { Off = 0x00, On = 0x01, @@ -3092,8 +3112,8 @@ pub enum TouchMode { CachedFixed = 0x04, } -#[pyclass] -#[derive(Clone, Debug)] +#[pyclass(eq, eq_int)] +#[derive(Clone, Debug, PartialEq)] pub enum KeySlot { Signature = 0xD6, Encryption = 0xD7, @@ -3157,9 +3177,95 @@ pub fn disable_otp_usb() -> Result { } } +#[pyfunction] +#[pyo3(text_signature = "(certdata, expirytime, pin)")] +pub fn update_primary_expiry_on_card( + py: Python, + certdata: Vec, + expirytime: u64, + pin: Vec, +) -> Result { + let cert = openpgp::Cert::from_bytes(&certdata)?; + + let p = &P::new(); + let vc = cert.with_policy(p, None)?; + let pk = cert.primary_key().key(); + // We will use the primary key to sign. + let mut signer = scard::KeyPair::new(pin, pk)?; + // New expiry time. + let t = SystemTime::now() + Duration::new(expirytime, 0); + // Here we do the real sign. + let sigs = vc.primary_key().set_expiration_time(&mut signer, Some(t))?; + let cert = cert.insert_packets(sigs)?; + // Now let us return the secret key as Python Bytes + let mut buf = Vec::new(); + let mut buffer = Vec::new(); + + // For the keys on card, we are writing (on memory) only the public key. + let mut writer = Writer::new(&mut buf, Kind::PublicKey)?; + cert.as_tsk().serialize(&mut buffer)?; + writer.write_all(&buffer)?; + writer.finalize()?; + + // Let us return the cert data as bytes which can be saved in the database + let res = PyBytes::new_bound(py, &buf); + Ok(res.into()) +} + +/// Returns updated key with new expiration time for subkeys +/// Takes the public key, a list of subkey fingerprints as str, +/// expirytime as the duration to be added as integer, and the pin for the card. +#[pyfunction] +#[pyo3(text_signature = "(certdata, fingerprints, expirytime, pin)")] +pub fn update_subkeys_expiry_on_card( + py: Python, + certdata: Vec, + fingerprints: Vec, + expirytime: u64, + pin: Vec, +) -> Result { + let cert = openpgp::Cert::from_bytes(&certdata)?; + + let p = &P::new(); + let pk = cert.primary_key().key(); + let mut signer = scard::KeyPair::new(pin, pk)?; + // Create the binding signatures. + let mut sigs = Vec::new(); + + // New expiry time. + let t = SystemTime::now() + Duration::new(expirytime, 0); + for key in cert.with_policy(p, None)?.keys().subkeys() { + let fp = key.fingerprint().to_hex(); + if !fingerprints.contains(&fp) { + continue; + } + // This reuses any existing backsignature. + let sig = + openpgp::packet::signature::SignatureBuilder::from(key.binding_signature().clone()) + .set_key_expiration_time(&key, t)? + .sign_subkey_binding(&mut signer, pk, &key)?; + sigs.push(sig); + } + + let cert = cert.insert_packets(sigs)?; + + // Get ready to write the updated cert as public key. + let mut buf = Vec::new(); + let mut buffer = Vec::new(); + + let mut writer = Writer::new(&mut buf, Kind::PublicKey)?; + cert.as_tsk().serialize(&mut buffer)?; + writer.write_all(&buffer)?; + writer.finalize()?; + + // Let us return the cert data which can be saved in the database + let res = PyBytes::new_bound(py, &buf); + Ok(res.into()) +} + /// A Python module implemented in Rust. #[pymodule] -fn johnnycanencrypt(_py: Python, m: &PyModule) -> PyResult<()> { +fn johnnycanencrypt(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(is_smartcard_connected))?; m.add_wrapped(wrap_pyfunction!(reset_yubikey))?; m.add_wrapped(wrap_pyfunction!(change_admin_pin))?; @@ -3190,6 +3296,8 @@ fn johnnycanencrypt(_py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(add_uid_in_cert))?; m.add_wrapped(wrap_pyfunction!(revoke_uid_in_cert))?; m.add_wrapped(wrap_pyfunction!(update_subkeys_expiry_in_cert))?; + m.add_wrapped(wrap_pyfunction!(update_subkeys_expiry_on_card))?; + m.add_wrapped(wrap_pyfunction!(update_primary_expiry_on_card))?; m.add_wrapped(wrap_pyfunction!(certify_key))?; m.add_wrapped(wrap_pyfunction!(get_ssh_pubkey))?; m.add_wrapped(wrap_pyfunction!(get_signing_pubkey))?; @@ -3199,8 +3307,8 @@ fn johnnycanencrypt(_py: Python, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pyfunction!(enable_otp_usb))?; m.add_wrapped(wrap_pyfunction!(disable_otp_usb))?; m.add_wrapped(wrap_pyfunction!(get_key_cipher_details))?; - m.add("CryptoError", _py.get_type::())?; - m.add("SameKeyError", _py.get_type::())?; + m.add("CryptoError", py.get_type_bound::())?; + m.add("SameKeyError", py.get_type_bound::())?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/src/scard.rs b/src/scard.rs index b40f0dc..940eee1 100644 --- a/src/scard.rs +++ b/src/scard.rs @@ -53,16 +53,18 @@ pub fn change_otp(enable: bool) -> Result { // For Yubikey 4 we have to send in different data let send_apdu = if (major < 5) { // We assume these are the YubiKey 4 - let inside = if enable { + if enable { apdus::APDU::new(0x00, 0x16, 0x11, 0x00, Some(vec![0x06, 0x00, 0x00, 0x00])) } else { apdus::APDU::new(0x00, 0x16, 0x11, 0x00, Some(vec![0x05, 0x00, 0x00, 0x00])) - }; - inside + } } else { // We assume these are the YubiKey 5 - let inside = if enable { enable_apdu } else { disable_apdu }; - inside + if enable { + enable_apdu + } else { + disable_apdu + } }; // Send in the real APDU to the card let resp = talktosc::send_and_parse(&card, send_apdu);