openpgp-card-state-0.3.1/.cargo_vcs_info.json0000644000000001360000000000100145320ustar { "git": { "sha1": "073b1791913ed0d1d9466c230608a55f4324d7ef" }, "path_in_vcs": "" }openpgp-card-state-0.3.1/.gitignore000064400000000000000000000002001046102023000153020ustar 00000000000000# SPDX-FileCopyrightText: Heiko Schaefer # SPDX-License-Identifier: CC0-1.0 /.idea /target /Cargo.lock openpgp-card-state-0.3.1/Cargo.lock0000644000000556650000000000100125260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "anyhow" version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "confy" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45b1f4c00870f07dc34adcac82bb6a72cc5aabca8536ba1797e01df51d2ce9a0" dependencies = [ "directories", "serde", "thiserror", "toml", ] [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "dbus" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" dependencies = [ "libc", "libdbus-sys", "winapi", ] [[package]] name = "dbus-secret-service" version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1caa0c241c01ad8d99a78d553567d38f873dd3ac16eca33a5370d650ab25584e" dependencies = [ "dbus", "futures-util", "num", "once_cell", "rand", ] [[package]] name = "directories" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users", "windows-sys 0.48.0", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-macro" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-macro", "futures-task", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "keyring" version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "030a9b84bb2a2f3673d4c8b8236091ed5d8f6b66a56d8085471d8abd5f3c6a80" dependencies = [ "byteorder", "dbus-secret-service", "security-framework", "windows-sys 0.59.0", ] [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libdbus-sys" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ "pkg-config", ] [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags", "libc", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "num" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-bigint" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openpgp-card-state" version = "0.3.1" dependencies = [ "anyhow", "clap", "confy", "keyring", "rpassword", "serde", ] [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" 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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", "thiserror", ] [[package]] name = "rpassword" version = "7.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" dependencies = [ "libc", "rtoolbox", "windows-sys 0.48.0", ] [[package]] name = "rtoolbox" version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" dependencies = [ "libc", "windows-sys 0.48.0", ] [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_spanned" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] openpgp-card-state-0.3.1/Cargo.toml0000644000000025470000000000100125400ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "openpgp-card-state" version = "0.3.1" authors = ["Heiko Schaefer "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Experimental storage mechanism for openpgp-card device state" documentation = "https://docs.rs/openpgp-card-state" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://codeberg.org/openpgp-card/state/" [lib] name = "openpgp_card_state" path = "src/lib.rs" [[bin]] name = "openpgp-card-state" path = "src/main.rs" [dependencies.anyhow] version = "1" [dependencies.clap] version = "4" features = ["derive"] [dependencies.confy] version = "0.6" [dependencies.keyring] version = "3" features = [ "apple-native", "windows-native", "sync-secret-service", ] [dependencies.rpassword] version = "7" [dependencies.serde] version = "1" features = ["derive"] openpgp-card-state-0.3.1/Cargo.toml.orig000064400000000000000000000013031046102023000162060ustar 00000000000000# SPDX-FileCopyrightText: Heiko Schaefer # SPDX-License-Identifier: CC0-1.0 [package] name = "openpgp-card-state" description = "Experimental storage mechanism for openpgp-card device state" version = "0.3.1" authors = ["Heiko Schaefer "] edition = "2021" license = "MIT OR Apache-2.0" repository = "https://codeberg.org/openpgp-card/state/" documentation = "https://docs.rs/openpgp-card-state" [dependencies] anyhow = "1" clap = { version = "4", features = ["derive"] } confy = "0.6" keyring = { version = "3", features = [ "apple-native", "windows-native", "sync-secret-service", ] } rpassword = "7" serde = { version = "1", features = ["derive"] }openpgp-card-state-0.3.1/LICENSES/Apache-2.0.txt000064400000000000000000000240451046102023000167530ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2021 Heiko Schäfer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. openpgp-card-state-0.3.1/LICENSES/CC0-1.0.txt000064400000000000000000000154041046102023000161350ustar 00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. openpgp-card-state-0.3.1/LICENSES/MIT.txt000064400000000000000000000020701046102023000157200ustar 00000000000000The MIT License (MIT) Copyright (c) 2021 Heiko Schäfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. openpgp-card-state-0.3.1/README.md000064400000000000000000000327531046102023000146130ustar 00000000000000 # Shared state for applications that use OpenPGP cards This crate facilitates use of [OpenPGP card](https://en.wikipedia.org/wiki/OpenPGP_card) devices by applications. In particular, it enables applications to perform operations on the hardware device without requiring any user interaction for PIN entry. Instead, applications obtain User PINs via this library. ```mermaid graph TB Application --> CARD["OpenPGP card device
(performs cryptographic operations
after User PIN presentation)"] Application --> STATE["openpgp-card-state library
(config and PIN storage backend access)"] STATE --> PINS["PIN storage backend
(makes User PINs available to applications)"] ``` This crate uses a combination of two mechanisms: - A regular config file: Stores non-sensitive card metadata (e.g. an optional nickname for cards), and specifies which PIN storage backend is used by default, and per card. - PIN storage backend: Stores the (sensitive) User PIN of OpenPGP card devices on behalf of applications. Different types of PIN storage backend exist (see below). Users can pick the appropriate backend for their use case. ## Use and architecture This crate is used as a library by applications. The library facilitates read- and write-access to both the config file and the PIN storage backend. Generally speaking, the `openpgp-card-state` library doesn't require a long-running process (however, some PIN storage backends may consist of a long-running process). From a user perspective, `openpgp-card-state` is usually an implementation detail of applications. However, it may be useful for users to understand how the mechanism works, to have a good mental model of the facility. In `openpgp-card-state`, cards are addressed using the ["ident"](https://docs.rs/openpgp-card/latest/openpgp_card/card_do/struct.ApplicationIdentifier.html#method.ident)format, both for access to metadata in the config file, and for User PIN storage and retrieval. ## Config file The config file for `openpgp-card-state` is stored in a platform-specific default location. On Linux systems, this is typically `$HOME/.config/openpgp-card-state/config.toml` (the config file is handled using the [directories](https://crates.io/crates/directories/) crate, which uses platform specific standard locations, following the XDG specification on Linux systems). A typical configuration entry for a card looks like this: ```text [[cards]] ident = "0000:01234567" pin_storage = "Keyring" nickname = "my purple card" ``` This configuration entry specifies that the card with the ident `0000:01234567` is using the "Keyring" PIN storage backend for the User PIN. Additionally, the nickname `my purple card` is defined for the card. ## Background, design tradeoffs and threat modeling Historically, OpenPGP card devices were typically used via [GnuPG](https://en.wikipedia.org/wiki/GNU_Privacy_Guard). This crate acts as shared infrastructure for non-GnuPG applications that use OpenPGP cards. However, to understand the design space, we'll first look at some details of OpenPGP card use. In particular authorization of cryptographic operations. Then we outline how GnuPG interacts with OpenPGP cards. Finally, we discuss the concepts of this crate and related threat modeling. ### OpenPGP card and User PINs One topic of particular interest for this discussion is the handling of "User PIN"s. To authorize private key operations on a card (signing or decryption), the User PIN must be presented to the card. The [OpenPGP card specification](https://gnupg.org/ftp/specs/OpenPGP-smart-card-application-3.4.1.pdf) is the canonical reference for PIN handling on OpenPGP card devices. The specification document refers to the User PIN as "PW1", and distinguishes two modes of using PW1: mode "81" for signing operations, and mode "82" for all other user operations (including decryption and authentication). Typically, the User PIN only needs to be presented to the card once, and is then valid for the duration of a connection to the card. As an exception, cards can be configured so that User PIN presentation is valid only for a single signing operation, to require the user to enter the User PIN once per signing operation. We'll put this special case aside, for the following discussion. #### PIN entry via the host computer vs. via a card reader with a pin pad Historically, OpenPGP card devices were typically actual physical smart cards, which were used in separate card reader devices. Some card readers feature a physical pin pad for entry of (numerical) PINs. This setup provides protection of the PIN from the host computer: Only the card reader itself sees the PIN that the card's owner enters on the card reader. In such setups, the host computer doesn't learn the PIN. Modern OpenPGP card devices, on the other hand, have shifted almost entirely to different types of hardware: USB tokens that present to the host computer as a smart card reader with an inserted OpenPGP card (for example, the free software [Gnuk](http://www.fsij.org/doc-gnuk/intro.html) running on an open hardware design, or various commercial devices by Nitrokey or Yubico). With such devices there is no way to present the User PIN to the "card" in a way that the host computer can't access. ### Architecture of GnuPG GnuPG is a venerable software suite. Its roots go back to 1997. It has pioneered the use of smart cards for private key operations for end users. Its architecture consists of multiple processes which are linked via ["assuan"](https://www.gnupg.org/documentation/manuals/assuan/) (a GnuPG-specific IPC protocol). The following diagram shows GnuPG's multi-process architecture, as well as some application software and how it accesses GnuPG: ```mermaid graph TB GPGME["GPGME
(GnuPG access library)"] --> GPG GPG["GnuPG
(CLI tool)"] --> GA["gpg-agent
(long running: private key operations)"] GA --> SCD["scdaemon
(long running: smart card access)"] GA --> pinentry["pinentry
(prompts users for PINs and other secrets)"] GIT[Git] -.-> GPG SSH["SSH
(can use OpenPGP cards via gpg-agent)"] -.-> GA TB[Thunderbird] -.-> GPGME classDef application fill:#808080,stroke-dasharray: 5 5; class TB,GIT,SSH application; ``` `scdaemon` is the GnuPG subsystem that handles access to OpenPGP card devices. It is designed to keep permanent and exclusive connections[^pcscshared] to any OpenPGP cards. [^pcscshared]: More recent versions of scdaemon allow offer optional support for "shared" connections to OpenPGP cards. This design has useful properties, especially when used with physical smart cards in readers with a physical pin pad: Without keeping a standing connection to such devices, the user would need to repeatedly re-enter their pin, on the physical pin pad of the reader. Possibly once for each operation, which would be prohibitive in many use cases. In such scenarios, keeping a permanent connection to the card is a necessity for a good user experience. However, the downside of this design is that no other applications (besides GnuPG's scdaemon) can reasonably use the cards, because GnuPG keeps them opened and assumes exclusive access (users have gone to some length to deal with the implications of this. Some have written shell scripts that strategically `kill` the `scdaemon` process as a workaround, to be able to access their card from other applications). ### User PIN storage with openpgp-card-state By contrast to GnuPG's approach (as outlined above), this crate makes different tradeoffs, and pursues different objectives. Our main design goals are: - Enable multiple applications to directly use OpenPGP cards, without mediating access through some long-running process. - Provide a smooth user experience. - Simplicity. Most users don't use an external pin pad, these days. This means that there is no strong reason to keep open a permanent connection to cards. Notice that modern OpenPGP card use necessitates disclosing the User PIN to the host computer, anyway. So the host computer can always send the User PIN to the card to authorize an operation. For more discussion of threat modeling, see below. Users should not be required to manually enter their User PIN for every single operation, so the User PIN needs to be available on the host computer, for applications, in some way. Acting as keeper of the User PIN is a central objective of this `openpgp-card-state` crate. ### Threat modeling As outlined above, this crate mainly deals with setups where the host computer has access to the User PIN (at least intermittently). This implies a threat model where the User PIN for cards doesn't require immense protection against the host computer. Two possible classes of approach for handling the User PIN suggest themselves: - Persisting the User PIN on the host computer. - Keeping the User PIN available to applications (for some, possibly finite, duration) in a long-running process, but not persisting it on disk. #### Persisting the User PIN This crate allows users to choose between different approaches to handling User PINs. We refer to these as "PIN storage backends". We propose that for most users it is reasonable and practical to persist the User PIN via a platform-specific, general purpose mechanisms for storage of secrets. Our default backend uses the "Keyring" User PIN storage backend (based on ) that implements this approach. It is backed by "secret-service" on Linux, "keychain" on Mac, and "credential manager" on Windows, respectively. This approach is not appropriate for all cases. However, we think it is appropriate in a majority of cases. The User PIN mostly serves as protection in the case of theft of the physical OpenPGP card device, without simultaneous loss or breach of the host computer. On the other hand, when protecting against remote attackers, "touch confirmation" for cryptographic operations is the most useful line of defense, with modern OpenPGP card devices. The User PIN is at best a weak defense in case of remote compromise of the host computer. #### Ephemeral User PIN caching For users whose threat model doesn't allow persisting the User PIN on disk, and who don't want to enter the User PIN for each operation, some kind of long-running process is required. In our architecture this will be a long-running process that serves as an ephemeral User PIN storage backend, shared between the applications of a user. The ephemeral PIN storage backend for openpgp-card-storage is not yet ready, but it is [on our roadmap](https://codeberg.org/openpgp-card/state/issues/3). ## PIN storage backends One main purpose of this crate is to store and obtain the User PINs of OpenPGP card devices, in particular to make User PINs available to local applications. Different users may have different requirements or priorities for their User PIN storage. So this crate supports different PIN storage mechanisms, at the user's choice. ### "Keyring": Platform-specific protected persistent storage By default, this crate uses the "Keyring" PIN storage backend. It is based on the https://crates.io/crates/keyring crate, and persists User PINs in platform-specific protected storage: - Linux: [secret-service](https://specifications.freedesktop.org/secret-service/) (this requires a service such as [GNOME Keyring](https://wiki.gnome.org/Projects/GnomeKeyring) to provide the secret-service facility). - macOS: the "keychain" subsystem - Windows: the "credential manager" subsystem. ### "Direct": Plaintext storage in the config file Some users may not require their User PIN to be handled by a security-conscious subsystem, and may prefer to avoid the additional complexity that comes with using such a subsystem. For such use cases, the User PIN can alternatively be stored directly in the config file, as plain text. This mode is especially convenient for use in contexts where protecting the User PIN is no concern, such as CI testing. ### More PIN storage backends to come A commonly requested feature is an ephemeral PIN storage backend that only keeps User PINs available in RAM, without persisting them to disk. An additional ephemeral PIN storage backend [is forthcoming](https://codeberg.org/openpgp-card/state/issues/3). ### Default User PIN storage backend The config file can explicitly define a default PIN storage backend. If this setting is present, the User PIN for any new cards will be stored using the specified `default_pin_storage` backend: ```text default_pin_storage = "Direct" [[cards]] ident = "0000:01234568" nickname = "my yellow card" [cards.pin_storage] Direct = "123456" ``` ## Use of this library by application developers This library is mainly aimed at application developers who want to implement OpenPGP card support. See [here](https://codeberg.org/openpgp-card/state/src/branch/main/doc/developers.md) for a discussion of how to use this library. NOTE: Please be aware that this library is young, and will likely go through some iterations before it stabilizes. ## CLI tool See [here](https://codeberg.org/openpgp-card/state/src/branch/main/doc/cli.md) for a description of the accompanying CLI tool, which is mainly intended for debugging purposes. # Funding This project has been funded in part through [NGI Assure](https://nlnet.nl/assure), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. [NGI Assure Logo](https://nlnet.nl/assure) openpgp-card-state-0.3.1/doc/cli.md000064400000000000000000000053701046102023000151650ustar 00000000000000 # CLI tool After installing this crate (e.g. by running `cargo install --path .`), you can use the `openpgp-card-state` CLI tool to inspect and alter stored state for your cards (including storing and lookup of User PINs in the PIN storage backend). Ideally, the `openpgp-card-state` CLI tool should not be needed by end users. Applications that leverage the `openpgp-card-state` library should provide appropriate built-in mechanisms to store the User PIN, if possible. However, for some applications, it may be impractical to handle PIN storage. In such cases, this tool can serve as a fallback. ## Storing a User PIN To store the User PIN for our example device, we use the "ident" format to address the correct card. One way to look up the `ident` of a card is to [use the `oct` tool](https://codeberg.org/openpgp-card/openpgp-card-tools/#list-cards): ``` $ oct list Available OpenPGP cards: 0000:01234567 ``` In this example, we use a card with the ident `0000:01234567`. You need to use the identifier of your own card, if you want to play along. Note: this tool doesn't require a card to be plugged in to store metadata about it, it doesn't perform any checks, including verification of the User PIN. Now that we know our card's ident, we can store its User PIN (the value of the PIN must be entered at the prompt "Enter User PIN"): ``` $ openpgp-card-state put 0000:01234567 Enter User PIN: stored. ``` ### Strong User PINs in the "Direct" backend To store a User PIN in a non-default backend, you can provide the `--pin-backend` parameter, like this: ``` $ openpgp-card-state put 0000:01234567 --pin-backend direct Enter User PIN: stored. ``` The resulting config file section looks like this: ```text [[cards]] ident = "0000:01234567" [cards.pin_storage] Direct = "123456" ``` ## Inspecting a stored User PIN We can now inspect the stored state for our card: ``` $ openpgp-card-state get 0000:01234567 Persisted User PIN for OpenPGP card 0000:01234567: '123456' ``` If we check in the config file, we'll see that the default `pin_storage` backend was used: ```text [[cards]] ident = "0000:01234567" pin_storage = "Keyring" ``` Now, we can use the card with applications that support `openpgp-card-state`, without needing to perform PIN entry. ## Removing all information about a card To drop stored information about a card from the backend, we can run: ``` $ openpgp-card-state delete 0000:01234567 deleted. ``` And indeed, when trying to `get` the PIN, the storage backend reports that no matching PIN is found. ``` $ openpgp-card-state get 0000:01234567 Error: Failed to get User PIN for card 0000:01234567: No matching entry found in secure storage ``` openpgp-card-state-0.3.1/doc/developers.md000064400000000000000000000052561046102023000165710ustar 00000000000000 # Use by application developers The main purpose of this crate is to be directly integrated into user-facing applications, as a library. One application to look at, for reference, is [openpgp-card-ssh-agent](https://crates.io/crates/openpgp-card-ssh-agent). Cards are named using the "ident" format, which combines the manufacturer id and serial number of cards (separated by a colon `:` symbol). ## Using a stored User PIN Applications can obtain a stored User PIN by calling `openpgp_card_state::get_pin(&ident)`. When this call returns an `Ok(Some(pin))`, the application can use `pin` to verify against the card. If the verification call succeeds, the application can use the card for cryptographic operations. ### Drop on verification error If the card is available, but the verification call fails, with a stored pin, the application should assume that the stored User PIN is invalid (e.g. because the user has changed the PIN for this card). In this case, the application should call `openpgp_card_state::drop_pin(&ident)` to drop the User PIN from the storage backend. Dropping the PIN from the backend prevents repeated use of a (presumably wrong) PIN, which may cause the card to lock the User PIN. ## Storing a User PIN Applications that can obtain a User PIN from the user, with application-appropriate UX can persist it by calling `openpgp_card_state::set_pin(&ident, &pin)`. Obtaining the User PIN from end-user facing applications and persisting them is the preferred pattern for storing the User PIN with `openpgp_card_state`. The application should only store a User PIN in `openpgp_card_state` after verifying it against the card, and receiving a positive result. # Desktop notifications Notifications are an orthogonal concern, but we think they are an important aspect of building user-friendly applications, especially for applications that aren't directly user-facing. We suggest that users should be notified of a number of situations that arise around OpenPGP card use: - missing User PINs, where an application fails to use a card because no User PIN can be found in the PIN storage backend - failure when attempting to verify a User PIN with a card (this probably means that the stored User PIN is incorrect, and should be dropped from the PIN storage backend, also see above) - whenever touch confirmation is required to perform a cryptographic operation on a card, the user should be notified of this requirement, including a message that outlines the nature of the operation that will be performed (e.g. which application is performing it, and additional details if possible)openpgp-card-state-0.3.1/rustfmt.toml000064400000000000000000000002501046102023000157200ustar 00000000000000# SPDX-FileCopyrightText: Heiko Schaefer # SPDX-License-Identifier: CC0-1.0 group_imports = "StdExternalCrate" format_code_in_doc_comments = trueopenpgp-card-state-0.3.1/src/config.rs000064400000000000000000000105611046102023000157270ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use serde::{Deserialize, Serialize}; const CONFIG_APP: &str = "openpgp-card-state"; const CONFIG_FILE_NAME: &str = "config"; #[derive(Default, Serialize, Deserialize)] pub(crate) struct Config { cards: Vec, /// If set, this applies when putting a PIN for a new card pub(crate) default_pin_storage: Option, } #[derive(Clone, Serialize, Deserialize)] pub(crate) struct CardSettings { pub(crate) ident: String, pub(crate) pin_storage: Option, pub(crate) nickname: Option, } /// Specifies a type of PIN storage backend, optionally combined with global configuration for /// that backend (e.g. a path to a binary) #[derive(Clone, Serialize, Deserialize, PartialEq)] pub enum PinStorageType { /// The keyring crate is used to store and retrieve the User PIN Keyring, /// The User PIN is stored directly in this config file. Direct, } /// Specifies a type of PIN storage backend, optionally combined with configuration for that /// backend, and/or a card-specific payload #[derive(Clone, Serialize, Deserialize)] pub(crate) enum PinStorage { /// The keyring crate is used to store and retrieve the User PIN Keyring, /// The User PIN is stored directly in this config file. /// /// An empty string signifies use of the "Direct" backend, but with no PIN currently stored /// (e.g. after the PIN has been dropped because it failed verification on the card) Direct(String), } impl Config { #[cfg(unix)] fn set_config_permissions(path: &std::path::Path) -> anyhow::Result<()> { let metadata = path.metadata()?; let mut permissions = metadata.permissions(); use std::os::unix::fs::PermissionsExt; permissions.set_mode(0o600); // Read/write for owner, no permissions for others. std::fs::set_permissions(path, permissions)?; Ok(()) } #[cfg(not(unix))] fn set_config_permissions(_: &std::path::Path) -> anyhow::Result<()> { Ok(()) } pub(crate) fn load() -> anyhow::Result { let path = confy::get_configuration_file_path(CONFIG_APP, Some(CONFIG_FILE_NAME))?; // Are we about to create a new config file? let exists = path.try_exists()?; let cfg = confy::load_path(&path)?; // TODO: this is a hack. // Ideally `confy` would expose a way to atomically set `PermissionsExt`. if !exists { // When creating a new config file, set permissions to 0600 on "unix" Self::set_config_permissions(&path)?; } Ok(cfg) } pub(crate) fn save(&self) -> anyhow::Result<()> { let path = confy::get_configuration_file_path(CONFIG_APP, Some(CONFIG_FILE_NAME))?; // Are we about to create a new config file? let exists = path.try_exists()?; confy::store_path(&path, self)?; // TODO: this is a hack. // Ideally `confy` would expose a way to atomically set `PermissionsExt`. // // (A very eager adversary could race for reading a new config file between the "store" // and permission setting calls). // if !exists { // When creating a new config file, set permissions to 0600 on "unix" Self::set_config_permissions(&path)?; } Ok(()) } pub(crate) fn push(&mut self, cs: CardSettings) -> anyhow::Result<()> { // don't allow duplicate entries for one card ident if self.cards.iter().any(|old| old.ident == cs.ident) { return Err(anyhow::anyhow!( "Trying to insert duplicate entry for {}", cs.ident )); } self.cards.push(cs); Ok(()) } pub(crate) fn remove(&mut self, ident: &str) -> anyhow::Result<()> { if let Some((pos, _)) = self .cards .iter() .enumerate() .find(|(_, cs)| cs.ident == ident) { self.cards.remove(pos); } Ok(()) } pub(crate) fn ident(&self, ident: &str) -> Option<&CardSettings> { self.cards.iter().find(|card| card.ident == ident) } pub(crate) fn ident_mut(&mut self, ident: &str) -> Option<&mut CardSettings> { self.cards.iter_mut().find(|card| card.ident == ident) } } openpgp-card-state-0.3.1/src/keyring.rs000064400000000000000000000013211046102023000161240ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use keyring::Entry; /// "Service" name to identify openpgp-card related entries const KEYRING_NAME: &str = "openpgp-card"; pub(crate) fn get(ident: &str) -> anyhow::Result> { let entry = Entry::new(KEYRING_NAME, ident)?; // FIXME: handle None case Ok(Some(entry.get_password()?)) } pub(crate) fn set(ident: &str, pin: &str) -> anyhow::Result<()> { let entry = Entry::new(KEYRING_NAME, ident)?; Ok(entry.set_password(pin)?) } pub(crate) fn delete(ident: &str) -> anyhow::Result<()> { let entry = Entry::new(KEYRING_NAME, ident)?; Ok(entry.delete_credential()?) } openpgp-card-state-0.3.1/src/lib.rs000064400000000000000000000143371046102023000152350ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 pub use config::PinStorageType; use crate::config::{CardSettings, Config, PinStorage}; const DEFAULT_PIN_BACKEND: PinStorageType = PinStorageType::Keyring; mod config; mod keyring; /// Get persisted User PIN information about one card "ident". pub fn get_pin(ident: &str) -> anyhow::Result> { let cfg = Config::load()?; let cs = cfg.ident(ident); let pinback_card = cs.and_then(|card| card.pin_storage.clone()); let pw = match pinback_card { Some(PinStorage::Keyring) | None => { let pin = keyring::get(ident)?; if pin.is_some() { if cs.is_none() { // FIXME: add config entry for this card to show we're using Keyring } else if pinback_card.is_none() { // FIXME: set backend in this config entry to show we're using Keyring } } pin } Some(PinStorage::Direct(pw)) => { if !pw.is_empty() { Some(pw.clone()) } else { None } } }; Ok(pw) } /// Get persisted nickname for the card "ident". pub fn get_nickname(ident: &str) -> anyhow::Result> { let cfg = Config::load()?; let cs = cfg.ident(ident); Ok(cs.and_then(|cs| cs.nickname.clone())) } /// Persist User PIN for a card. pub fn set_pin(ident: &str, pin: &str, backend: Option) -> anyhow::Result<()> { let mut cfg = Config::load()?; // the target pin storage backend depends on: // - the explicit "backend" parameter, if set // - the global default in the config file, if set // - the hard coded default backend let pinback_target = match backend { Some(ref typ) => typ.clone(), None => cfg .default_pin_storage .clone() .unwrap_or(DEFAULT_PIN_BACKEND), }; if let Some(cs) = cfg.ident_mut(ident) { // A configuration entry for this card exists. We update it. match cs.pin_storage { Some(PinStorage::Keyring) => { if backend.is_some() && backend != Some(PinStorageType::Keyring) { // switching to a different PinStorageType is currently unsupported return Err(anyhow::anyhow!("PinStorageType doesn't match existing setting. Please delete the current settings to start over.")); } keyring::set(ident, pin)? } Some(PinStorage::Direct(ref mut pw)) => { if backend.is_some() && backend != Some(PinStorageType::Direct) { // switching to a different PinStorageType is currently unsupported return Err(anyhow::anyhow!("PinStorageType doesn't match existing setting. Please delete the current settings to start over.")); } *pw = pin.to_string(); cfg.save()?; } None => { // pin_storage for this CardSettings is unset. We: // - persist the pin, and // - set pin_storage to `Keyring`. keyring::set(ident, pin)?; cs.pin_storage = Some(PinStorage::Keyring); cfg.save()?; } } } else { // we're making a new configuration entry for this card // Follow global PIN storage backend setting, if any let pin_storage = match pinback_target { PinStorageType::Keyring => { keyring::set(ident, pin)?; // store PIN in default backend Some(PinStorage::Keyring) } PinStorageType::Direct => Some(PinStorage::Direct(pin.to_string())), }; // add a new entry for this card to the config file let cs = CardSettings { ident: ident.to_string(), pin_storage, nickname: None, }; cfg.push(cs)?; cfg.save()?; } Ok(()) } /// Store a nickname for a card in the configuration file. pub fn set_nickname(ident: &str, nickname: Option<&str>) -> anyhow::Result<()> { let mut cfg = Config::load()?; if let Some(settings) = cfg.ident_mut(ident) { // updating an existing configuration entry for this card settings.nickname = nickname.map(ToString::to_string) } else { // the configuration entry for this card is new let cs = CardSettings { ident: ident.to_string(), pin_storage: None, nickname: nickname.map(|s| s.to_string()), }; cfg.push(cs)?; } cfg.save()?; Ok(()) } /// Forget the persisted User PIN for a card. /// /// This is the suggested action for applications when PIN validation fails. /// /// This should be done to avoid locking a card accidentally: If an application re-tries an invalid /// PIN repeatedly, the OpenPGP card will count down the allowed retries and eventually lock the /// User PIN. pub fn drop_pin(ident: &str) -> anyhow::Result<()> { let mut cfg = Config::load()?; if let Some(settings) = cfg.ident_mut(ident) { match settings.pin_storage { Some(PinStorage::Keyring) | None => keyring::delete(ident)?, Some(PinStorage::Direct(ref mut pw)) => { *pw = String::default(); cfg.save()?; } } } else { keyring::delete(ident)? // try deleting in the default backend, just to be safe } Ok(()) } /// Delete all config and User PIN storage for `ident` pub fn delete(ident: &str) -> anyhow::Result<()> { let mut cfg = Config::load()?; // if the pin is stored in a backend that persists it, drop it from there! match cfg.ident(ident).map(|cs| &cs.pin_storage) { None | Some(None) | Some(Some(PinStorage::Keyring)) => { let _ = keyring::delete(ident).map_err(|e| { eprintln!( "WARN: failed to drop pin for {} from PIN storage:\n{}\n", ident, e ) }); } _ => {} } // drop the entry for "ident" from our config file let _ = cfg.remove(ident); cfg.save()?; Ok(()) } openpgp-card-state-0.3.1/src/main.rs000064400000000000000000000061531046102023000154100ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use anyhow::{anyhow, Result}; use clap::{Parser, ValueEnum}; use openpgp_card_state::*; #[derive(Parser, Debug)] #[command(about, author, name = env!("CARGO_BIN_NAME"), version)] pub struct Cli { #[command(subcommand)] pub cmd: Command, } #[derive(Parser, Debug)] pub enum Command { /// Show state of one card Get(CardIdent), /// Store information for one card Put(PutCommand), /// Drop stored User PIN for one card DropPin(CardIdent), /// Delete information for one card Delete(CardIdent), } #[derive(Parser, Debug)] pub struct CardIdent { #[arg(name = "card ident", help = "Identifier of a card")] pub ident: String, } #[derive(Parser, Debug)] pub struct PutCommand { #[arg(name = "card ident", help = "Identifier of a card")] pub ident: String, #[arg(name = "An optional nickname for this card")] pub nickname: Option, #[arg( name = "PIN storage backend", short = 'b', long = "pin-backend", help = "Storage backend for this User PIN" )] pin_backend: Option, } #[derive(ValueEnum, Debug, Clone)] pub enum PinBackend { Keyring, Direct, } impl From for PinStorageType { fn from(value: PinBackend) -> Self { match value { PinBackend::Keyring => Self::Keyring, PinBackend::Direct => Self::Direct, } } } fn main() -> Result<()> { let cli = Cli::parse(); match cli.cmd { Command::Get(ident) => { show(&ident.ident)?; } Command::Put(put) => { let pin = rpassword::prompt_password("Enter User PIN: ")?; store(&put.ident, &pin, put.nickname, put.pin_backend)?; } Command::DropPin(ident) => { drop_pin(&ident.ident)?; } Command::Delete(ident) => { delete(&ident.ident)?; } } Ok(()) } fn show(ident: &str) -> Result<()> { let pin = get_pin(ident).map_err(|e| anyhow!("Failed to get User PIN for card {}: {}", ident, e))?; let nickname = get_nickname(ident) .map_err(|e| anyhow!("Failed to get User PIN for card {}: {}", ident, e))?; if let Some(pin) = pin { println!("Persisted User PIN for OpenPGP card {}: '{}'", ident, pin); } if let Some(nickname) = nickname { println!("Persisted nickname: '{}'", nickname); } Ok(()) } fn store( ident: &str, pin: &str, nickname: Option, pin_backend: Option, ) -> Result<()> { set_pin(ident, pin, pin_backend.map(Into::into)) .map_err(|e| anyhow!("Failed to set User PIN for card {}: {}", ident, e))?; set_nickname(ident, nickname.as_deref()) .map_err(|e| anyhow!("Failed to set nickname for card {}: {}", ident, e))?; println!("stored."); Ok(()) } fn delete(ident: &str) -> Result<()> { openpgp_card_state::delete(ident) .map_err(|e| anyhow!("Failed to delete User PIN for card {}: {}", ident, e))?; println!("deleted."); Ok(()) }