Compare commits
No commits in common. "main" and "ryansquared/staging-since-latest" have entirely different histories.
main
...
ryansquare
|
@ -1 +0,0 @@
|
|||
audits filter=lfs diff=lfs merge=lfs -text
|
231
CHANGELOG.md
231
CHANGELOG.md
|
@ -1,234 +1,3 @@
|
|||
# Keyfork v0.2.5
|
||||
|
||||
### Changes in keyfork:
|
||||
|
||||
```
|
||||
503c6fa keyfork derive key: initial commit
|
||||
c46f9e4 move things to use default handler mechanism
|
||||
92dde3d keyfork-prompt: make dyn Trait compatible in prep for allowing dynamic prompt handlers
|
||||
```
|
||||
|
||||
### Changes in keyfork-crossterm:
|
||||
|
||||
```
|
||||
6317cc9 Cargo.lock: bump deps, dupe generic-array :(
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-key:
|
||||
|
||||
```
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-openpgp:
|
||||
|
||||
```
|
||||
4ab1e8a add docs to make clippy extra happy
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-path-data:
|
||||
|
||||
```
|
||||
4ab1e8a add docs to make clippy extra happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-util:
|
||||
|
||||
```
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-entropy:
|
||||
|
||||
```
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-mnemonic:
|
||||
|
||||
```
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-prompt:
|
||||
|
||||
```
|
||||
f8db870 keyfork-prompt: add Headless
|
||||
92dde3d keyfork-prompt: make dyn Trait compatible in prep for allowing dynamic prompt handlers
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-qrcode:
|
||||
|
||||
```
|
||||
be6d562 keyfork-qrcode: use image::ImageReader over image::io::Reader (deprecated)
|
||||
305e070 Cargo.lock: bump multiple deps to deduplicate
|
||||
4ab1e8a add docs to make clippy extra happy
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyfork-shard:
|
||||
|
||||
```
|
||||
c46f9e4 move things to use default handler mechanism
|
||||
92dde3d keyfork-prompt: make dyn Trait compatible in prep for allowing dynamic prompt handlers
|
||||
d7bf3d1 keyfork-shard: move to blahaj
|
||||
a8b2814 make clippy happy
|
||||
c36fe0a keyfork-shard: re-enable standard policy, alive check still disabled, add check for encryption keys when discovering certs
|
||||
```
|
||||
|
||||
### Changes in keyfork-zbar:
|
||||
|
||||
```
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyforkd:
|
||||
|
||||
```
|
||||
c46f9e4 move things to use default handler mechanism
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
### Changes in keyforkd-client:
|
||||
|
||||
```
|
||||
a8b2814 make clippy happy
|
||||
```
|
||||
|
||||
# Keyfork v0.2.4
|
||||
|
||||
This release includes a lot of "maintenance" changes, without any changes in
|
||||
end-user functionality.
|
||||
|
||||
### Changes in keyfork:
|
||||
|
||||
The most significant change in this release is the reorganization of some of
|
||||
the subcommands, where they would be better as enum-traits, such as `keyfork
|
||||
derive` and `keyfork wizard`.
|
||||
|
||||
```
|
||||
b254ba7 cleanup post-merge
|
||||
58d3c34 Merge branch 'main' into ryansquared/staging-since-latest
|
||||
35f57fc Merge branch 'ryansquared/keyfork-mnemonic-refactors'
|
||||
a2eb5fd bump dependencies with listed vulnerabilities (not affected)
|
||||
5219c5a keyfork: enum-trait-ify choose-your-own commands
|
||||
b26f296 keyfork-derive-path-data: move all pathcrafting here
|
||||
35ab5e6 keyfork-mnemonic-util => keyfork-mnemonic
|
||||
f5627e5 keyfork-mnemonic-util: impl try_from_slice and from_array
|
||||
02e5b54 keyfork-mnemonic-util::generate_seed: return const size array
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-openpgp:
|
||||
|
||||
```
|
||||
b254ba7 cleanup post-merge
|
||||
35f57fc Merge branch 'ryansquared/keyfork-mnemonic-refactors'
|
||||
a2eb5fd bump dependencies with listed vulnerabilities (not affected)
|
||||
b26f296 keyfork-derive-path-data: move all pathcrafting here
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-path-data:
|
||||
|
||||
This change now centralizes all special Keyfork paths. This means crates should
|
||||
no longer be required to implement their own path parsing logic.
|
||||
|
||||
```
|
||||
b26f296 keyfork-derive-path-data: move all pathcrafting here
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-util:
|
||||
|
||||
```
|
||||
35ab5e6 keyfork-mnemonic-util => keyfork-mnemonic
|
||||
```
|
||||
|
||||
### Changes in keyfork-mnemonic:
|
||||
|
||||
`keyfork-mnemonic-util` has finally been renamed to `keyfork-mnemonic`. The
|
||||
method names `as_bytes() => as_slice()`, `to_bytes() => to_vec()`, and
|
||||
`into_bytes() => into_vec()`, and the function names
|
||||
`from_bytes() => try_from_slice()` and
|
||||
`from_nonstandard_bytes() => from_array()`, have been implemented to more
|
||||
closely represent the native types they are representing. Additionally,
|
||||
`Mnemonic::generate_seed()` has been modified to return a constant size array;
|
||||
this is a breaking change, but should have minimal impact.
|
||||
|
||||
```
|
||||
35ab5e6 keyfork-mnemonic-util => keyfork-mnemonic
|
||||
3ee81b6 keyfork-mnemonic-util: impl as_slice to_vec into_vec
|
||||
f5627e5 keyfork-mnemonic-util: impl try_from_slice and from_array
|
||||
02e5b54 keyfork-mnemonic-util::generate_seed: return const size array
|
||||
```
|
||||
|
||||
### Changes in keyfork-prompt:
|
||||
|
||||
```
|
||||
35ab5e6 keyfork-mnemonic-util => keyfork-mnemonic
|
||||
```
|
||||
|
||||
### Changes in keyfork-shard:
|
||||
|
||||
```
|
||||
58d3c34 Merge branch 'main' into ryansquared/staging-since-latest
|
||||
35ab5e6 keyfork-mnemonic-util => keyfork-mnemonic
|
||||
f5627e5 keyfork-mnemonic-util: impl try_from_slice and from_array
|
||||
```
|
||||
|
||||
### Changes in keyforkd:
|
||||
|
||||
```
|
||||
35ab5e6 keyfork-mnemonic-util => keyfork-mnemonic
|
||||
02e5b54 keyfork-mnemonic-util::generate_seed: return const size array
|
||||
536e6da keyforkd{,-client}: lots of documentationings
|
||||
```
|
||||
|
||||
### Changes in keyforkd-client:
|
||||
|
||||
```
|
||||
536e6da keyforkd{,-client}: lots of documentationings
|
||||
```
|
||||
|
||||
# Keyfork v0.2.3
|
||||
|
||||
This release includes a bugfix for the wizard where the wizard was too strict
|
||||
about when keys were "alive".
|
||||
|
||||
### Changes in keyfork:
|
||||
|
||||
```
|
||||
dd4354f keyfork: bump keyfork-shard
|
||||
```
|
||||
|
||||
### Changes in keyfork-shard:
|
||||
|
||||
```
|
||||
ba64db8 update Cargo.toml and Cargo.lock
|
||||
fa84a2a keyfork-shard: Be less strict about keys
|
||||
```
|
||||
|
||||
# Keyfork v0.2.2
|
||||
|
||||
This release adds a new wizard, intended to be used at DEFCON 32.
|
||||
|
||||
### Changes in keyfork:
|
||||
|
||||
```
|
||||
8d40d26 keyfork: add `bottoms-up` wizard
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-openpgp:
|
||||
|
||||
This change also includes a minor change, allowing the derivation path for
|
||||
`keyfork-derive-openpg` to derive further than two paths, which was useful in
|
||||
the testing of the wizard.
|
||||
|
||||
```
|
||||
8d40d26 keyfork: add `bottoms-up` wizard
|
||||
```
|
||||
|
||||
# Keyfork v0.2.1
|
||||
|
||||
This release contains an emergency bugfix for Keyfork Shard, which previously
|
||||
|
|
File diff suppressed because it is too large
Load Diff
52
Cargo.toml
52
Cargo.toml
|
@ -23,60 +23,8 @@ members = [
|
|||
"crates/util/keyfork-prompt",
|
||||
"crates/util/keyfork-slip10-test-data",
|
||||
"crates/util/smex",
|
||||
"crates/tests",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
||||
# Keyfork dependencies
|
||||
keyforkd = { version = "0.1.1", path = "crates/daemon/keyforkd", registry = "distrust", default-features = false }
|
||||
keyforkd-client = { version = "0.2.0", path = "crates/daemon/keyforkd-client", registry = "distrust", default-features = false }
|
||||
keyforkd-models = { version = "0.2.0", path = "crates/daemon/keyforkd-models", registry = "distrust", default-features = false }
|
||||
keyfork-derive-openpgp = { version = "0.1.2", path = "crates/derive/keyfork-derive-openpgp", registry = "distrust", default-features = false }
|
||||
keyfork-derive-path-data = { version = "0.1.1", path = "crates/derive/keyfork-derive-path-data", registry = "distrust", default-features = false }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "crates/derive/keyfork-derive-util", registry = "distrust", default-features = false }
|
||||
keyfork-shard = { version = "0.3.0", path = "crates/keyfork-shard", registry = "distrust", default-features = false }
|
||||
keyfork-qrcode = { version = "0.1.1", path = "crates/qrcode/keyfork-qrcode", registry = "distrust", default-features = false }
|
||||
keyfork-zbar = { version = "0.1.0", path = "crates/qrcode/keyfork-zbar", registry = "distrust", default-features = false }
|
||||
keyfork-zbar-sys = { version = "0.1.0", path = "crates/qrcode/keyfork-zbar-sys", registry = "distrust", default-features = false }
|
||||
keyfork-bin = { version = "0.1.0", path = "crates/util/keyfork-bin", registry = "distrust", default-features = false }
|
||||
keyfork-bug = { version = "0.1.0", path = "crates/util/keyfork-bug", registry = "distrust", default-features = false }
|
||||
keyfork-crossterm = { version = "0.27.1", path = "crates/util/keyfork-crossterm", registry = "distrust", default-features = false }
|
||||
keyfork-entropy = { version = "0.1.1", path = "crates/util/keyfork-entropy", registry = "distrust", default-features = false }
|
||||
keyfork-frame = { version = "0.1.0", path = "crates/util/keyfork-frame", registry = "distrust", default-features = false }
|
||||
keyfork-mnemonic = { version = "0.4.0", path = "crates/util/keyfork-mnemonic", registry = "distrust", default-features = false }
|
||||
keyfork-prompt = { version = "0.2.0", path = "crates/util/keyfork-prompt", registry = "distrust", default-features = false }
|
||||
keyfork-slip10-test-data = { version = "0.1.0", path = "crates/util/keyfork-slip10-test-data", registry = "distrust", default-features = false }
|
||||
smex = { version = "0.1.0", path = "crates/util/smex", registry = "distrust", default-features = false }
|
||||
|
||||
# External dependencies
|
||||
|
||||
# Cryptography
|
||||
ed25519-dalek = "2.1.1"
|
||||
hmac = "0.12.1"
|
||||
k256 = { version = "0.13.3", default-features = false, features = ["std"] }
|
||||
sha2 = "0.10.8"
|
||||
|
||||
# OpenPGP
|
||||
card-backend-pcsc = "0.5.0"
|
||||
openpgp-card = { version = "0.4.1" }
|
||||
openpgp-card-sequoia = { version = "0.2.0", default-features = false }
|
||||
sequoia-openpgp = { version = "1.21.2", default-features = false, features = ["compression"] }
|
||||
|
||||
# Serialization
|
||||
bincode = "1.3.3"
|
||||
serde = { version= "1.0.195", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
|
||||
# Misc.
|
||||
anyhow = "1.0.79"
|
||||
hex-literal = "0.4.1"
|
||||
image = { version = "0.25.2", default-features = false }
|
||||
thiserror = "1.0.56"
|
||||
tokio = "1.35.1"
|
||||
v4l = "0.14.0"
|
||||
base64 = "0.22.1"
|
||||
|
||||
[profile.dev.package.keyfork-qrcode]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
# Releasing new versions
|
||||
|
||||
* Add and review a new blurb to the changelog by running the
|
||||
`make-changelog-blurb.sh` script and appending the result to the top of
|
||||
the file.
|
||||
* Update all versions of crates listed in the changelog.
|
||||
* Commit changes.
|
||||
* Run the `sign-new-versions.sh` script to tag the new versions.
|
||||
* Run the `publish.sh` script to push the latest packages to the Distrust
|
||||
Cargo registry.
|
Binary file not shown.
Binary file not shown.
92
bacon.toml
92
bacon.toml
|
@ -1,92 +0,0 @@
|
|||
# This is a configuration file for the bacon tool
|
||||
#
|
||||
# Bacon repository: https://github.com/Canop/bacon
|
||||
# Complete help on configuration: https://dystroy.org/bacon/config/
|
||||
# You can also check bacon's own bacon.toml file
|
||||
# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml
|
||||
|
||||
default_job = "check"
|
||||
|
||||
[jobs.check]
|
||||
command = ["cargo", "check", "--color", "always"]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.check-all]
|
||||
command = ["cargo", "check", "--all-targets", "--color", "always"]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.clippy]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--all-targets",
|
||||
"--color", "always",
|
||||
]
|
||||
need_stdout = false
|
||||
|
||||
[jobs.clippy-unwrap]
|
||||
command = [
|
||||
"cargo", "clippy",
|
||||
"--lib",
|
||||
"--color", "always",
|
||||
"--",
|
||||
"-W",
|
||||
"clippy::unwrap_used",
|
||||
"-W",
|
||||
"clippy::expect_used",
|
||||
]
|
||||
need_stdout = false
|
||||
|
||||
# This job lets you run
|
||||
# - all tests: bacon test
|
||||
# - a specific test: bacon test -- config::test_default_files
|
||||
# - the tests of a package: bacon test -- -- -p config
|
||||
[jobs.test]
|
||||
command = [
|
||||
"cargo", "test", "--color", "always",
|
||||
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
|
||||
]
|
||||
need_stdout = true
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--color", "always", "--no-deps"]
|
||||
need_stdout = false
|
||||
|
||||
# If the doc compiles, then it opens in your browser and bacon switches
|
||||
# to the previous job
|
||||
[jobs.doc-open]
|
||||
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
|
||||
need_stdout = false
|
||||
on_success = "back" # so that we don't open the browser at each change
|
||||
|
||||
# You can run your application and have the result displayed in bacon,
|
||||
# *if* it makes sense for this crate.
|
||||
# Don't forget the `--color always` part or the errors won't be
|
||||
# properly parsed.
|
||||
# If your program never stops (eg a server), you may set `background`
|
||||
# to false to have the cargo run output immediately displayed instead
|
||||
# of waiting for program's end.
|
||||
[jobs.run]
|
||||
command = [
|
||||
"cargo", "run",
|
||||
"--color", "always",
|
||||
# put launch parameters for your program behind a `--` separator
|
||||
]
|
||||
need_stdout = true
|
||||
allow_warnings = true
|
||||
background = true
|
||||
|
||||
# This parameterized job runs the example of your choice, as soon
|
||||
# as the code compiles.
|
||||
# Call it as
|
||||
# bacon ex -- my-example
|
||||
[jobs.ex]
|
||||
command = ["cargo", "run", "--color", "always", "--example"]
|
||||
need_stdout = true
|
||||
allow_warnings = true
|
||||
|
||||
# You may define here keybindings that would be specific to
|
||||
# a project, for example a shortcut to launch a specific job.
|
||||
# Shortcuts to internal functions (scrolling, toggling, etc.)
|
||||
# should go in your personal global prefs.toml file instead.
|
||||
[keybindings]
|
||||
# alt-m = "job:my-job"
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyforkd-client"
|
||||
version = "0.2.2"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -12,14 +12,14 @@ ed25519 = ["keyfork-derive-util/ed25519", "ed25519-dalek"]
|
|||
secp256k1 = ["keyfork-derive-util/secp256k1", "k256"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-derive-util = { workspace = true, default-features = false }
|
||||
keyfork-frame = { workspace = true }
|
||||
keyforkd-models = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
k256 = { workspace = true, default-features = false, features = ["std"], optional = true }
|
||||
ed25519-dalek = { workspace = true, optional = true }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../../derive/keyfork-derive-util", default-features = false, registry = "distrust" }
|
||||
keyfork-frame = { version = "0.1.0", path = "../../util/keyfork-frame", registry = "distrust" }
|
||||
keyforkd-models = { version = "0.2.0", path = "../keyforkd-models", registry = "distrust" }
|
||||
bincode = "1.3.3"
|
||||
thiserror = "1.0.49"
|
||||
k256 = { version = "0.13.3", optional = true }
|
||||
ed25519-dalek = { version = "2.1.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
keyfork-slip10-test-data = { workspace = true }
|
||||
keyforkd = { workspace = true }
|
||||
keyfork-slip10-test-data = { path = "../../util/keyfork-slip10-test-data", registry = "distrust" }
|
||||
keyforkd = { path = "../keyforkd", registry = "distrust" }
|
||||
|
|
|
@ -11,7 +11,7 @@ fn secp256k1_test_suite() {
|
|||
|
||||
let tests = test_data()
|
||||
.unwrap()
|
||||
.remove("secp256k1")
|
||||
.remove(&"secp256k1".to_string())
|
||||
.unwrap();
|
||||
|
||||
for seed_test in tests {
|
||||
|
@ -70,7 +70,7 @@ fn secp256k1_test_suite() {
|
|||
fn ed25519_test_suite() {
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
let tests = test_data().unwrap().remove("ed25519").unwrap();
|
||||
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
||||
|
||||
for seed_test in tests {
|
||||
let seed = seed_test.seed;
|
||||
|
|
|
@ -7,6 +7,6 @@ license = "MIT"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
keyfork-derive-util = { workspace = true, default-features = false }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../../derive/keyfork-derive-util", default-features = false, registry = "distrust" }
|
||||
serde = { version = "1.0.190", features = ["derive"] }
|
||||
thiserror = "1.0.50"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyforkd"
|
||||
version = "0.1.3"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -12,28 +12,28 @@ tracing = ["tower/tracing", "tokio/tracing", "dep:tracing", "dep:tracing-subscri
|
|||
multithread = ["tokio/rt-multi-thread"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-bug = { workspace = true }
|
||||
keyfork-derive-util = { workspace = true }
|
||||
keyfork-frame = { workspace = true, features = ["async"] }
|
||||
keyfork-mnemonic = { workspace = true }
|
||||
keyfork-derive-path-data = { workspace = true }
|
||||
keyforkd-models = { workspace = true }
|
||||
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../../derive/keyfork-derive-util", registry = "distrust" }
|
||||
keyfork-frame = { version = "0.1.0", path = "../../util/keyfork-frame", features = ["async"], registry = "distrust" }
|
||||
keyfork-mnemonic = { version = "0.3.0", path = "../../util/keyfork-mnemonic", registry = "distrust" }
|
||||
keyfork-derive-path-data = { version = "0.1.0", path = "../../derive/keyfork-derive-path-data", registry = "distrust" }
|
||||
keyforkd-models = { version = "0.2.0", path = "../keyforkd-models", registry = "distrust" }
|
||||
|
||||
# Not personally audited
|
||||
bincode = { workspace = true }
|
||||
bincode = "1.3.3"
|
||||
|
||||
# Ecosystem trust, not personally audited
|
||||
tokio = { workspace = true, features = ["io-util", "macros", "rt", "io-std", "net", "fs", "signal"] }
|
||||
tokio = { version = "1.32.0", features = ["io-util", "macros", "rt", "io-std", "net", "fs", "signal"] }
|
||||
tracing = { version = "0.1.37", optional = true }
|
||||
tracing-error = { version = "0.2.0", optional = true }
|
||||
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
|
||||
tower = { version = "0.4.13", features = ["tokio", "util"] }
|
||||
|
||||
# Personally audited
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = "1.0.47"
|
||||
serde = { version = "1.0.186", features = ["derive"] }
|
||||
tempfile = { version = "3.10.0", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = { workspace = true }
|
||||
keyfork-slip10-test-data = { workspace = true }
|
||||
hex-literal = "0.4.1"
|
||||
keyfork-slip10-test-data = { path = "../../util/keyfork-slip10-test-data", registry = "distrust" }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Launch the Keyfork Server from using a mnemonic passed through standard input.
|
||||
//!
|
||||
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct BincodeLayer<'a, Request> {
|
|||
phantom_request: PhantomData<&'a Request>,
|
||||
}
|
||||
|
||||
impl<Request> BincodeLayer<'_, Request> {
|
||||
impl<'a, Request> BincodeLayer<'a, Request> {
|
||||
/// Create a new [`BincodeLayer`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
|
@ -21,7 +21,7 @@ impl<Request> BincodeLayer<'_, Request> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Request> Default for BincodeLayer<'_, Request> {
|
||||
impl<'a, Request> Default for BincodeLayer<'a, Request> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ mod tests {
|
|||
async fn properly_derives_secp256k1() {
|
||||
let tests = test_data()
|
||||
.unwrap()
|
||||
.remove("secp256k1")
|
||||
.remove(&"secp256k1".to_string())
|
||||
.unwrap();
|
||||
|
||||
for per_seed in tests {
|
||||
|
@ -146,7 +146,7 @@ mod tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn properly_derives_ed25519() {
|
||||
let tests = test_data().unwrap().remove("ed25519").unwrap();
|
||||
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
||||
|
||||
for per_seed in tests {
|
||||
let seed = &per_seed.seed;
|
||||
|
|
|
@ -26,7 +26,7 @@ pub enum UninstantiableError {}
|
|||
/// };
|
||||
/// assert!(closure().is_ok());
|
||||
/// ```
|
||||
pub type Panicable = std::result::Result<(), UninstantiableError>;
|
||||
pub type Panicable<T> = std::result::Result<T, UninstantiableError>;
|
||||
|
||||
/// Run a test making use of a Keyforkd server. The test may use a seed (the first argument) from a
|
||||
/// test suite, or (as shown in the example below) a simple seed may be used solely to ensure
|
||||
|
@ -62,9 +62,9 @@ pub type Panicable = std::result::Result<(), UninstantiableError>;
|
|||
/// }).unwrap();
|
||||
/// ```
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn run_test<F, E>(seed: &[u8], closure: F) -> std::result::Result<(), E>
|
||||
pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E>
|
||||
where
|
||||
F: FnOnce(&std::path::Path) -> std::result::Result<(), E> + Send + 'static,
|
||||
F: FnOnce(&std::path::Path) -> Result<(), E> + Send + 'static,
|
||||
E: Send + 'static,
|
||||
{
|
||||
let rt = Builder::new_multi_thread()
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
[package]
|
||||
name = "keyfork-derive-age"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
keyfork-derive-util = { workspace = true, default-features = false, features = ["ed25519"] }
|
||||
keyforkd-client = { workspace = true }
|
||||
smex = { workspace = true }
|
||||
thiserror = "1.0.48"
|
||||
bech32 = "0.11.0"
|
||||
keyfork-derive-path-data = { workspace = true }
|
||||
ed25519-dalek = "2.1.1"
|
|
@ -1,69 +0,0 @@
|
|||
use std::{env, process::ExitCode, str::FromStr};
|
||||
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyfork_derive_util::{DerivationPath, ExtendedPrivateKey, PathError};
|
||||
use keyforkd_client::Client;
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
type XPrv = ExtendedPrivateKey<SigningKey>;
|
||||
|
||||
/// Any error that can occur while deriving a key.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The given path could not be parsed.
|
||||
#[error("Could not parse the given path: {0}")]
|
||||
PathFormat(#[from] PathError),
|
||||
|
||||
/// The request to derive data failed.
|
||||
#[error("Unable to perform key derivation request: {0}")]
|
||||
KeyforkdClient(#[from] keyforkd_client::Error),
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
fn validate(path: &str) -> Result<DerivationPath> {
|
||||
let index = paths::AGE.inner().first().unwrap();
|
||||
|
||||
let path = DerivationPath::from_str(path)?;
|
||||
assert!(
|
||||
path.len() >= 2,
|
||||
"Expected path of at least m/{index}/account_id'"
|
||||
);
|
||||
|
||||
let given_index = path.iter().next().expect("checked .len() above");
|
||||
assert_eq!(
|
||||
index, given_index,
|
||||
"Expected derivation path starting with m/{index}, got: {given_index}",
|
||||
);
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut args = env::args();
|
||||
let program_name = args.next().expect("program name");
|
||||
let args = args.collect::<Vec<_>>();
|
||||
let path = match args.as_slice() {
|
||||
[path] => validate(path)?,
|
||||
_ => panic!("Usage: {program_name} path"),
|
||||
};
|
||||
|
||||
let mut client = Client::discover_socket()?;
|
||||
// TODO: should this key be clamped to Curve25519 specs?
|
||||
let xprv: XPrv = client.request_xprv(&path)?;
|
||||
let hrp = bech32::Hrp::parse("AGE-SECRET-KEY-")?;
|
||||
let age_key = bech32::encode::<bech32::Bech32>(hrp, &xprv.private_key().to_bytes())?;
|
||||
println!("{}", age_key.to_uppercase());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
if let Err(e) = run() {
|
||||
eprintln!("Error: {e}");
|
||||
ExitCode::FAILURE
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
[package]
|
||||
name = "keyfork-derive-key"
|
||||
version = "0.1.2"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
keyfork-derive-util = { workspace = true }
|
||||
keyforkd-client = { workspace = true }
|
||||
smex = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", registry = "distrust" }
|
||||
keyforkd-client = { version = "0.2.0", path = "../../daemon/keyforkd-client", registry = "distrust" }
|
||||
smex = { version = "0.1.0", path = "../../util/smex", registry = "distrust" }
|
||||
thiserror = "1.0.48"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Query the Keyfork Server to generate a hex-encoded key for a given algorithm.
|
||||
//!
|
||||
|
||||
use std::{env, process::ExitCode, str::FromStr};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-derive-openpgp"
|
||||
version = "0.1.4"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -10,10 +10,10 @@ default = ["bin"]
|
|||
bin = ["sequoia-openpgp/crypto-nettle"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-derive-util = { workspace = true, default-features = false, features = ["ed25519"] }
|
||||
keyforkd-client = { workspace = true, default-features = false, features = ["ed25519"] }
|
||||
ed25519-dalek = { workspace = true }
|
||||
sequoia-openpgp = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-derive-path-data = { workspace = true }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, features = ["ed25519"], registry = "distrust" }
|
||||
keyforkd-client = { version = "0.2.0", path = "../../daemon/keyforkd-client", default-features = false, features = ["ed25519"], registry = "distrust" }
|
||||
ed25519-dalek = "2.0.0"
|
||||
sequoia-openpgp = { version = "1.17.0", default-features = false }
|
||||
anyhow = "1.0.75"
|
||||
thiserror = "1.0.49"
|
||||
keyfork-derive-path-data = { version = "0.1.1", path = "../keyfork-derive-path-data" }
|
||||
|
|
|
@ -19,14 +19,8 @@ use sequoia_openpgp::{
|
|||
Cert, Packet,
|
||||
};
|
||||
|
||||
// TODO: this key type is actually _not_ the extended private key, so it should be renamed
|
||||
// something like Prv or PrvKey.
|
||||
|
||||
/// The private key type used with OpenPGP.
|
||||
pub type XPrvKey = SigningKey;
|
||||
|
||||
/// The extended private key type used with OpenPGP.
|
||||
pub type XPrv = ExtendedPrivateKey<XPrvKey>;
|
||||
pub type XPrv = ExtendedPrivateKey<SigningKey>;
|
||||
|
||||
/// An error occurred while creating an OpenPGP key.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! Query the Keyfork Servre to derive an OpenPGP Secret Key.
|
||||
//!
|
||||
|
||||
use std::{env, process::ExitCode, str::FromStr};
|
||||
|
||||
use keyfork_derive_util::DerivationPath;
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyforkd_client::Client;
|
||||
|
||||
|
@ -121,7 +121,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||
|
||||
for packet in cert.as_tsk().into_packets() {
|
||||
for packet in cert.into_packets2() {
|
||||
packet.serialize(&mut w)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
[package]
|
||||
name = "keyfork-derive-path-data"
|
||||
version = "0.1.3"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
keyfork-derive-util = { workspace = true, default-features = false }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" }
|
||||
once_cell = "1.19.0"
|
||||
|
|
|
@ -6,7 +6,6 @@ use once_cell::sync::Lazy;
|
|||
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
|
||||
/// All common paths for key derivation.
|
||||
pub mod paths {
|
||||
use super::*;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-derive-util"
|
||||
version = "0.2.2"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -12,25 +12,25 @@ secp256k1 = ["k256"]
|
|||
ed25519 = ["ed25519-dalek"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-mnemonic = { workspace = true }
|
||||
keyfork-bug = { workspace = true }
|
||||
keyfork-mnemonic = { version = "0.3.0", path = "../../util/keyfork-mnemonic", registry = "distrust" }
|
||||
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" }
|
||||
|
||||
# Included in Rust
|
||||
digest = "0.10.7"
|
||||
sha2 = { workspace = true }
|
||||
sha2 = "0.10.7"
|
||||
|
||||
# Rust-Crypto ecosystem, not personally audited
|
||||
ripemd = "0.1.3"
|
||||
hmac = { workspace = true, features = ["std"] }
|
||||
hmac = { version = "0.12.1", features = ["std"] }
|
||||
|
||||
# Personally audited
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
serde = { version = "1.0.186", features = ["derive"] }
|
||||
thiserror = "1.0.47"
|
||||
|
||||
# Optional, not personally audited
|
||||
k256 = { workspace = true, default-features = false, features = ["std", "arithmetic"], optional = true }
|
||||
ed25519-dalek = { workspace = true, optional = true }
|
||||
k256 = { version = "0.13.1", default-features = false, features = ["std", "arithmetic"], optional = true }
|
||||
ed25519-dalek = { version = "2.0.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
hex-literal = { workspace = true }
|
||||
keyfork-slip10-test-data = { workspace = true }
|
||||
hex-literal = "0.4.1"
|
||||
keyfork-slip10-test-data = { version = "0.1.0", path = "../../util/keyfork-slip10-test-data", registry = "distrust" }
|
||||
|
|
|
@ -41,9 +41,9 @@
|
|||
//! }
|
||||
//! ```
|
||||
|
||||
#[allow(missing_docs)]
|
||||
///
|
||||
pub mod private_key;
|
||||
#[allow(missing_docs)]
|
||||
///
|
||||
pub mod public_key;
|
||||
|
||||
pub use {private_key::ExtendedPrivateKey, public_key::ExtendedPublicKey};
|
||||
|
|
|
@ -15,7 +15,7 @@ fn secp256k1() {
|
|||
|
||||
let tests = test_data()
|
||||
.unwrap()
|
||||
.remove("secp256k1")
|
||||
.remove(&"secp256k1".to_string())
|
||||
.unwrap();
|
||||
|
||||
for per_seed in tests {
|
||||
|
@ -62,7 +62,7 @@ fn secp256k1() {
|
|||
fn ed25519() {
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
let tests = test_data().unwrap().remove("ed25519").unwrap();
|
||||
let tests = test_data().unwrap().remove(&"ed25519".to_string()).unwrap();
|
||||
|
||||
for per_seed in tests {
|
||||
let seed = &per_seed.seed;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-shard"
|
||||
version = "0.3.0"
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -14,26 +14,27 @@ openpgp-card = ["openpgp-card-sequoia", "card-backend-pcsc", "card-backend", "de
|
|||
qrcode = ["keyfork-qrcode"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-bug = { workspace = true }
|
||||
keyfork-prompt = { workspace = true, default-features = false, features = ["mnemonic"] }
|
||||
keyfork-qrcode = { workspace = true, optional = true, default-features = false }
|
||||
smex = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-bug = { version = "0.1.0", path = "../util/keyfork-bug", registry = "distrust" }
|
||||
keyfork-prompt = { version = "0.1.1", path = "../util/keyfork-prompt", default-features = false, features = ["mnemonic"], registry = "distrust" }
|
||||
keyfork-qrcode = { version = "0.1.1", path = "../qrcode/keyfork-qrcode", optional = true, default-features = false, registry = "distrust" }
|
||||
smex = { version = "0.1.0", path = "../util/smex", registry = "distrust" }
|
||||
|
||||
sharks = "0.5.0"
|
||||
thiserror = "1.0.50"
|
||||
|
||||
# Remote operator mode
|
||||
keyfork-mnemonic = { workspace = true }
|
||||
keyfork-mnemonic = { version = "0.3.0", path = "../util/keyfork-mnemonic", registry = "distrust" }
|
||||
x25519-dalek = { version = "2.0.0", features = ["getrandom"] }
|
||||
aes-gcm = { version = "0.10.3", features = ["std"] }
|
||||
hkdf = { version = "0.12.4", features = ["std"] }
|
||||
sha2 = { workspace = true }
|
||||
sha2 = "0.10.8"
|
||||
|
||||
# OpenPGP
|
||||
keyfork-derive-openpgp = { workspace = true, default-features = false }
|
||||
anyhow = { workspace = true, optional = true }
|
||||
keyfork-derive-openpgp = { version = "0.1.0", path = "../derive/keyfork-derive-openpgp", default-features = false, registry = "distrust" }
|
||||
anyhow = { version = "1.0.79", optional = true }
|
||||
card-backend = { version = "0.2.0", optional = true }
|
||||
card-backend-pcsc = { workspace = true, optional = true }
|
||||
openpgp-card-sequoia = { workspace = true, optional = true }
|
||||
openpgp-card = { workspace = true, optional = true }
|
||||
sequoia-openpgp = { workspace = true, optional = true }
|
||||
base64 = { workspace = true }
|
||||
blahaj = "0.6.0"
|
||||
card-backend-pcsc = { version = "0.5.0", optional = true }
|
||||
openpgp-card-sequoia = { version = "0.2.0", optional = true, default-features = false }
|
||||
openpgp-card = { version = "0.4.0", optional = true }
|
||||
sequoia-openpgp = { version = "1.17.0", optional = true, default-features = false }
|
||||
base64 = "0.22.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Combine OpenPGP shards and output the hex-encoded secret.
|
||||
//!
|
||||
|
||||
use std::{
|
||||
env,
|
||||
|
@ -7,7 +7,7 @@ use std::{
|
|||
process::ExitCode,
|
||||
};
|
||||
|
||||
use keyfork_prompt::default_handler;
|
||||
use keyfork_prompt::{DefaultTerminal, default_terminal};
|
||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
@ -32,8 +32,8 @@ fn run() -> Result<()> {
|
|||
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
|
||||
};
|
||||
|
||||
let openpgp = OpenPGP;
|
||||
let prompt_handler = default_handler()?;
|
||||
let openpgp = OpenPGP::<DefaultTerminal>::new();
|
||||
let prompt_handler = default_terminal()?;
|
||||
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
||||
print!("{}", smex::encode(bytes));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Decrypt a single OpenPGP shard and encapsulate it for remote transport.
|
||||
//!
|
||||
|
||||
use std::{
|
||||
env,
|
||||
|
@ -7,7 +7,7 @@ use std::{
|
|||
process::ExitCode,
|
||||
};
|
||||
|
||||
use keyfork_prompt::default_handler;
|
||||
use keyfork_prompt::{DefaultTerminal, default_terminal};
|
||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
@ -32,8 +32,8 @@ fn run() -> Result<()> {
|
|||
_ => panic!("Usage: {program_name} <shard> [key_discovery]"),
|
||||
};
|
||||
|
||||
let openpgp = OpenPGP;
|
||||
let prompt_handler = default_handler()?;
|
||||
let openpgp = OpenPGP::<DefaultTerminal>::new();
|
||||
let prompt_handler = default_terminal()?;
|
||||
|
||||
openpgp.decrypt_one_shard_for_transport(key_discovery.as_deref(), messages_file, prompt_handler)?;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Combine OpenPGP shards using remote transport and output the hex-encoded secret.
|
||||
//!
|
||||
|
||||
use std::{
|
||||
env,
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
//! Split a hex-encoded secret into OpenPGP shards
|
||||
//!
|
||||
|
||||
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
|
||||
|
||||
use keyfork_prompt::terminal::DefaultTerminal;
|
||||
use keyfork_shard::{Format, openpgp::OpenPGP};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
@ -50,7 +51,7 @@ fn run() -> Result<()> {
|
|||
smex::decode(line?)?
|
||||
};
|
||||
|
||||
let openpgp = OpenPGP;
|
||||
let openpgp = OpenPGP::<DefaultTerminal>::new();
|
||||
|
||||
openpgp.shard_and_encrypt(threshold, max, &input, key_discovery.as_path(), std::io::stdout())?;
|
||||
Ok(())
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
|
||||
use std::{
|
||||
io::{stdin, stdout, Read, Write},
|
||||
sync::Mutex,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use aes_gcm::{
|
||||
|
@ -12,12 +11,10 @@ use aes_gcm::{
|
|||
Aes256Gcm, KeyInit, Nonce,
|
||||
};
|
||||
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||
use blahaj::{Share, Sharks};
|
||||
use hkdf::Hkdf;
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_prompt::{
|
||||
prompt_validated_wordlist,
|
||||
validators::{
|
||||
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
||||
Validator,
|
||||
|
@ -25,6 +22,7 @@ use keyfork_prompt::{
|
|||
Message as PromptMessage, PromptHandler, Terminal,
|
||||
};
|
||||
use sha2::Sha256;
|
||||
use sharks::{Share, Sharks};
|
||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||
|
||||
const PLAINTEXT_LENGTH: u8 = 32 // shard
|
||||
|
@ -137,7 +135,7 @@ pub trait Format {
|
|||
&self,
|
||||
private_keys: Option<Self::PrivateKeyData>,
|
||||
encrypted_messages: &[Self::EncryptedData],
|
||||
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
||||
) -> Result<(Vec<Share>, u8), Self::Error>;
|
||||
|
||||
/// Decrypt a single share and associated metadata from a reaable input. For the current
|
||||
|
@ -151,7 +149,7 @@ pub trait Format {
|
|||
&self,
|
||||
private_keys: Option<Self::PrivateKeyData>,
|
||||
encrypted_data: &[Self::EncryptedData],
|
||||
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
||||
) -> Result<(Share, u8), Self::Error>;
|
||||
|
||||
/// Decrypt multiple shares and combine them to recreate a secret.
|
||||
|
@ -163,7 +161,7 @@ pub trait Format {
|
|||
&self,
|
||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||
reader: impl Read + Send + Sync,
|
||||
prompt: Box<dyn PromptHandler>,
|
||||
prompt: impl PromptHandler,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||
let private_keys = private_key_discovery
|
||||
.map(|p| p.discover_private_keys())
|
||||
|
@ -172,7 +170,7 @@ pub trait Format {
|
|||
let (shares, threshold) = self.decrypt_all_shards(
|
||||
private_keys,
|
||||
&encrypted_messages,
|
||||
Rc::new(Mutex::new(prompt)),
|
||||
Arc::new(Mutex::new(prompt)),
|
||||
)?;
|
||||
|
||||
let secret = Sharks(threshold)
|
||||
|
@ -193,9 +191,9 @@ pub trait Format {
|
|||
&self,
|
||||
private_key_discovery: Option<impl KeyDiscovery<Self>>,
|
||||
reader: impl Read + Send + Sync,
|
||||
prompt: Box<dyn PromptHandler>,
|
||||
prompt: impl PromptHandler,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let prompt = Rc::new(Mutex::new(prompt));
|
||||
let prompt = Arc::new(Mutex::new(prompt));
|
||||
|
||||
// parse input
|
||||
let private_keys = private_key_discovery
|
||||
|
@ -235,17 +233,6 @@ pub trait Format {
|
|||
let validator = MnemonicValidator {
|
||||
word_length: Some(WordLength::Count(24)),
|
||||
};
|
||||
let mut prompt = prompt.lock().expect(bug!(POISONED_MUTEX));
|
||||
prompt_validated_wordlist::<English, _>(
|
||||
&mut **prompt,
|
||||
QRCODE_COULDNT_READ,
|
||||
3,
|
||||
&*validator.to_fn(),
|
||||
)?
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| InvalidData)?
|
||||
/*
|
||||
prompt
|
||||
.lock()
|
||||
.expect(bug!(POISONED_MUTEX))
|
||||
|
@ -257,7 +244,6 @@ pub trait Format {
|
|||
.as_bytes()
|
||||
.try_into()
|
||||
.map_err(|_| InvalidData)?
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -515,12 +501,12 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
word_lengths: [24, 39],
|
||||
};
|
||||
|
||||
let [pubkey_mnemonic, payload_mnemonic] = prompt_validated_wordlist::<English, _>(
|
||||
&mut pm,
|
||||
QRCODE_COULDNT_READ,
|
||||
3,
|
||||
&*validator.to_fn(),
|
||||
)?;
|
||||
let [pubkey_mnemonic, payload_mnemonic] = pm
|
||||
.prompt_validated_wordlist::<English, _>(
|
||||
QRCODE_COULDNT_READ,
|
||||
3,
|
||||
validator.to_fn(),
|
||||
)?;
|
||||
let pubkey = pubkey_mnemonic
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write},
|
||||
marker::PhantomData,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
sync::Mutex,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use keyfork_bug::bug;
|
||||
|
@ -25,7 +25,7 @@ use openpgp::{
|
|||
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
|
||||
Parse,
|
||||
},
|
||||
policy::{NullPolicy, StandardPolicy, Policy},
|
||||
policy::{NullPolicy, Policy},
|
||||
serialize::{
|
||||
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
|
||||
Marshal,
|
||||
|
@ -34,7 +34,7 @@ use openpgp::{
|
|||
KeyID, PacketPile,
|
||||
};
|
||||
pub use sequoia_openpgp as openpgp;
|
||||
use blahaj::Share;
|
||||
use sharks::Share;
|
||||
|
||||
mod keyring;
|
||||
use keyring::Keyring;
|
||||
|
@ -77,10 +77,6 @@ pub enum Error {
|
|||
/// An IO error occurred.
|
||||
#[error("IO error: {0}")]
|
||||
Io(#[source] std::io::Error),
|
||||
|
||||
/// No valid keys were found for the given recipient.
|
||||
#[error("No valid keys were found for the recipient {0}")]
|
||||
NoValidKeys(KeyID),
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
|
@ -185,10 +181,19 @@ impl EncryptedMessage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Encoding and decoding shards using OpenPGP.
|
||||
pub struct OpenPGP;
|
||||
///
|
||||
pub struct OpenPGP<P: PromptHandler> {
|
||||
p: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl OpenPGP {
|
||||
impl<P: PromptHandler> OpenPGP<P> {
|
||||
#[allow(clippy::new_without_default, missing_docs)]
|
||||
pub fn new() -> Self {
|
||||
Self { p: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: PromptHandler> OpenPGP<P> {
|
||||
/// Read all OpenPGP certificates in a path and return a [`Vec`] of them.
|
||||
///
|
||||
/// Certificates are read from a file, or from files one level deep in a directory.
|
||||
|
@ -234,20 +239,13 @@ impl OpenPGP {
|
|||
certs.insert(certfp, cert);
|
||||
}
|
||||
}
|
||||
for cert in certs.values() {
|
||||
let policy = StandardPolicy::new();
|
||||
let valid_cert = cert.with_policy(&policy, None).map_err(Error::Sequoia)?;
|
||||
if get_encryption_keys(&valid_cert).next().is_none() {
|
||||
return Err(Error::NoValidKeys(valid_cert.keyid()))
|
||||
}
|
||||
}
|
||||
Ok(certs.into_values().collect())
|
||||
}
|
||||
}
|
||||
|
||||
const METADATA_MESSAGE_MISSING: &str = "Metadata message was not found in parsed packets";
|
||||
|
||||
impl Format for OpenPGP {
|
||||
impl<P: PromptHandler> Format for OpenPGP<P> {
|
||||
type Error = Error;
|
||||
type PublicKey = Cert;
|
||||
type PrivateKeyData = Vec<Cert>;
|
||||
|
@ -278,7 +276,7 @@ impl Format for OpenPGP {
|
|||
key_data: &[Self::PublicKey],
|
||||
threshold: u8,
|
||||
) -> Result<Self::EncryptedData, Self::Error> {
|
||||
let policy = StandardPolicy::new();
|
||||
let policy = NullPolicy::new();
|
||||
let mut pp = vec![SHARD_METADATA_VERSION, threshold];
|
||||
// Note: Sequoia does not export private keys on a Cert, only on a TSK
|
||||
signing_key
|
||||
|
@ -364,7 +362,7 @@ impl Format for OpenPGP {
|
|||
public_key: &Cert,
|
||||
signing_key: &mut Self::SigningKey,
|
||||
) -> Result<EncryptedMessage> {
|
||||
let policy = StandardPolicy::new();
|
||||
let policy = NullPolicy::new();
|
||||
let valid_cert = public_key
|
||||
.with_policy(&policy, None)
|
||||
.map_err(Error::Sequoia)?;
|
||||
|
@ -444,7 +442,7 @@ impl Format for OpenPGP {
|
|||
&self,
|
||||
private_keys: Option<Self::PrivateKeyData>,
|
||||
encrypted_data: &[Self::EncryptedData],
|
||||
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
||||
) -> std::result::Result<(Vec<Share>, u8), Self::Error> {
|
||||
// Be as liberal as possible when decrypting.
|
||||
// We don't want to invalidate someone's keys just because the old sig expired.
|
||||
|
@ -505,7 +503,7 @@ impl Format for OpenPGP {
|
|||
&self,
|
||||
private_keys: Option<Self::PrivateKeyData>,
|
||||
encrypted_data: &[Self::EncryptedData],
|
||||
prompt: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
prompt: Arc<Mutex<impl PromptHandler>>,
|
||||
) -> std::result::Result<(Share, u8), Self::Error> {
|
||||
let policy = NullPolicy::new();
|
||||
|
||||
|
@ -551,22 +549,22 @@ impl Format for OpenPGP {
|
|||
}
|
||||
}
|
||||
|
||||
impl KeyDiscovery<OpenPGP> for &Path {
|
||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
|
||||
OpenPGP::discover_certs(self)
|
||||
impl<P: PromptHandler> KeyDiscovery<OpenPGP<P>> for &Path {
|
||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP<P> as Format>::PublicKey>> {
|
||||
OpenPGP::<P>::discover_certs(self)
|
||||
}
|
||||
|
||||
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
|
||||
OpenPGP::discover_certs(self)
|
||||
fn discover_private_keys(&self) -> Result<<OpenPGP<P> as Format>::PrivateKeyData> {
|
||||
OpenPGP::<P>::discover_certs(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDiscovery<OpenPGP> for &[Cert] {
|
||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP as Format>::PublicKey>> {
|
||||
impl<P: PromptHandler> KeyDiscovery<OpenPGP<P>> for &[Cert] {
|
||||
fn discover_public_keys(&self) -> Result<Vec<<OpenPGP<P> as Format>::PublicKey>> {
|
||||
Ok(self.to_vec())
|
||||
}
|
||||
|
||||
fn discover_private_keys(&self) -> Result<<OpenPGP as Format>::PrivateKeyData> {
|
||||
fn discover_private_keys(&self) -> Result<<OpenPGP<P> as Format>::PrivateKeyData> {
|
||||
Ok(self.to_vec())
|
||||
}
|
||||
}
|
||||
|
@ -628,12 +626,12 @@ fn decode_metadata_v1(buf: &[u8]) -> Result<(u8, Cert, Vec<Cert>)> {
|
|||
|
||||
// NOTE: When using single-decryptor mechanism, use this method with `threshold = 1` to return a
|
||||
// single message.
|
||||
fn decrypt_with_manager(
|
||||
fn decrypt_with_manager<P: PromptHandler>(
|
||||
threshold: u8,
|
||||
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
||||
certs: &[Cert],
|
||||
policy: &dyn Policy,
|
||||
manager: &mut SmartcardManager,
|
||||
manager: &mut SmartcardManager<P>,
|
||||
) -> Result<HashMap<KeyID, Vec<u8>>> {
|
||||
let mut decrypted_messages = HashMap::new();
|
||||
|
||||
|
@ -678,11 +676,11 @@ fn decrypt_with_manager(
|
|||
|
||||
// NOTE: When using single-decryptor mechanism, only a single key should be provided in Keyring to
|
||||
// decrypt messages with.
|
||||
fn decrypt_with_keyring(
|
||||
fn decrypt_with_keyring<P: PromptHandler>(
|
||||
messages: &mut HashMap<KeyID, EncryptedMessage>,
|
||||
certs: &[Cert],
|
||||
policy: &NullPolicy,
|
||||
keyring: &mut Keyring,
|
||||
keyring: &mut Keyring<P>,
|
||||
) -> Result<HashMap<KeyID, Vec<u8>>, Error> {
|
||||
let mut decrypted_messages = HashMap::new();
|
||||
|
||||
|
@ -712,11 +710,11 @@ fn decrypt_with_keyring(
|
|||
Ok(decrypted_messages)
|
||||
}
|
||||
|
||||
fn decrypt_metadata(
|
||||
fn decrypt_metadata<P: PromptHandler>(
|
||||
message: &EncryptedMessage,
|
||||
policy: &NullPolicy,
|
||||
keyring: &mut Keyring,
|
||||
manager: &mut SmartcardManager,
|
||||
keyring: &mut Keyring<P>,
|
||||
manager: &mut SmartcardManager<P>,
|
||||
) -> Result<Vec<u8>> {
|
||||
Ok(if keyring.is_empty() {
|
||||
manager.load_any_card()?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::expect_fun_call)]
|
||||
|
||||
use std::{rc::Rc, sync::Mutex};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
use keyfork_prompt::{Error as PromptError, PromptHandler};
|
||||
|
@ -27,14 +27,14 @@ pub enum Error {
|
|||
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
||||
pub struct Keyring {
|
||||
pub struct Keyring<P: PromptHandler> {
|
||||
full_certs: Vec<Cert>,
|
||||
root: Option<Cert>,
|
||||
pm: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
pm: Arc<Mutex<P>>,
|
||||
}
|
||||
|
||||
impl Keyring {
|
||||
pub fn new(certs: impl AsRef<[Cert]>, p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
|
||||
impl<P: PromptHandler> Keyring<P> {
|
||||
pub fn new(certs: impl AsRef<[Cert]>, p: Arc<Mutex<P>>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
full_certs: certs.as_ref().to_vec(),
|
||||
root: Default::default(),
|
||||
|
@ -62,7 +62,7 @@ impl Keyring {
|
|||
}
|
||||
}
|
||||
|
||||
impl VerificationHelper for &mut Keyring {
|
||||
impl<P: PromptHandler> VerificationHelper for &mut Keyring<P> {
|
||||
fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
||||
Ok(ids
|
||||
.iter()
|
||||
|
@ -108,7 +108,7 @@ impl VerificationHelper for &mut Keyring {
|
|||
}
|
||||
}
|
||||
|
||||
impl DecryptionHelper for &mut Keyring {
|
||||
impl<P: PromptHandler> DecryptionHelper for &mut Keyring<P> {
|
||||
fn decrypt<D>(
|
||||
&mut self,
|
||||
pkesks: &[PKESK],
|
||||
|
|
|
@ -2,13 +2,11 @@
|
|||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
rc::Rc,
|
||||
sync::Mutex,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
use keyfork_prompt::{
|
||||
prompt_validated_passphrase,
|
||||
validators::{PinValidator, Validator},
|
||||
Error as PromptError, Message, PromptHandler,
|
||||
};
|
||||
|
@ -73,15 +71,15 @@ fn format_name(input: impl AsRef<str>) -> String {
|
|||
}
|
||||
|
||||
#[allow(clippy::module_name_repetitions)]
|
||||
pub struct SmartcardManager {
|
||||
pub struct SmartcardManager<P: PromptHandler> {
|
||||
current_card: Option<Card<Open>>,
|
||||
root: Option<Cert>,
|
||||
pm: Rc<Mutex<Box<dyn PromptHandler>>>,
|
||||
pm: Arc<Mutex<P>>,
|
||||
pin_cache: HashMap<Fingerprint, String>,
|
||||
}
|
||||
|
||||
impl SmartcardManager {
|
||||
pub fn new(p: Rc<Mutex<Box<dyn PromptHandler>>>) -> Result<Self> {
|
||||
impl<P: PromptHandler> SmartcardManager<P> {
|
||||
pub fn new(p: Arc<Mutex<P>>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
current_card: None,
|
||||
root: None,
|
||||
|
@ -175,7 +173,7 @@ impl SmartcardManager {
|
|||
}
|
||||
}
|
||||
|
||||
impl VerificationHelper for &mut SmartcardManager {
|
||||
impl<P: PromptHandler> VerificationHelper for &mut SmartcardManager<P> {
|
||||
fn get_certs(&mut self, ids: &[openpgp::KeyHandle]) -> openpgp::Result<Vec<Cert>> {
|
||||
#[allow(clippy::flat_map_option)]
|
||||
Ok(ids
|
||||
|
@ -219,7 +217,7 @@ impl VerificationHelper for &mut SmartcardManager {
|
|||
}
|
||||
}
|
||||
|
||||
impl DecryptionHelper for &mut SmartcardManager {
|
||||
impl<P: PromptHandler> DecryptionHelper for &mut SmartcardManager<P> {
|
||||
fn decrypt<D>(
|
||||
&mut self,
|
||||
pkesks: &[PKESK],
|
||||
|
@ -277,8 +275,11 @@ impl DecryptionHelper for &mut SmartcardManager {
|
|||
} else {
|
||||
format!("Unlock card {card_id} ({cardholder_name})\n{rpea}: {attempts}\n\nPIN: ")
|
||||
};
|
||||
let mut prompt = self.pm.lock().expect(bug!(POISONED_MUTEX));
|
||||
let temp_pin = prompt_validated_passphrase(&mut **prompt, &message, 3, &pin_validator)?;
|
||||
let temp_pin = self
|
||||
.pm
|
||||
.lock()
|
||||
.expect(bug!(POISONED_MUTEX))
|
||||
.prompt_validated_passphrase(&message, 3, &pin_validator)?;
|
||||
let verification_status = transaction.verify_user_pin(temp_pin.as_str().trim());
|
||||
match verification_status {
|
||||
#[allow(clippy::ignored_unit_patterns)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork"
|
||||
version = "0.2.5"
|
||||
version = "0.2.3"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -23,27 +23,25 @@ sequoia-crypto-backend-openssl = ["sequoia-openpgp/crypto-openssl"]
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
keyfork-bin = { workspace = true }
|
||||
keyforkd = { workspace = true, features = ["tracing"] }
|
||||
keyforkd-client = { workspace = true, default-features = false, features = ["ed25519"] }
|
||||
keyfork-derive-util = { workspace = true, default-features = true }
|
||||
keyfork-derive-openpgp = { workspace = true }
|
||||
keyfork-derive-path-data = { workspace = true }
|
||||
keyfork-entropy = { workspace = true }
|
||||
keyfork-mnemonic = { workspace = true }
|
||||
keyfork-prompt = { workspace = true }
|
||||
keyfork-qrcode = { workspace = true, default-features = false }
|
||||
keyfork-shard = { workspace = true, default-features = false, features = ["openpgp", "openpgp-card", "qrcode"] }
|
||||
smex = { workspace = true }
|
||||
keyfork-bin = { version = "0.1.0", path = "../util/keyfork-bin", registry = "distrust" }
|
||||
keyforkd = { version = "0.1.0", path = "../daemon/keyforkd", features = ["tracing"], registry = "distrust" }
|
||||
keyforkd-client = { version = "0.2.0", path = "../daemon/keyforkd-client", default-features = false, features = ["ed25519"], registry = "distrust" }
|
||||
keyfork-derive-openpgp = { version = "0.1.1", path = "../derive/keyfork-derive-openpgp", registry = "distrust" }
|
||||
keyfork-derive-util = { version = "0.2.0", path = "../derive/keyfork-derive-util", default-features = false, features = ["ed25519"], registry = "distrust" }
|
||||
keyfork-entropy = { version = "0.1.0", path = "../util/keyfork-entropy", registry = "distrust" }
|
||||
keyfork-mnemonic = { version = "0.3.0", path = "../util/keyfork-mnemonic", registry = "distrust" }
|
||||
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", registry = "distrust" }
|
||||
keyfork-qrcode = { version = "0.1.0", path = "../qrcode/keyfork-qrcode", default-features = false, registry = "distrust" }
|
||||
keyfork-shard = { version = "0.2.0", path = "../keyfork-shard", default-features = false, features = ["openpgp", "openpgp-card", "qrcode"], registry = "distrust" }
|
||||
smex = { version = "0.1.0", path = "../util/smex", registry = "distrust" }
|
||||
|
||||
clap = { version = "4.4.2", features = ["derive", "env", "wrap_help"] }
|
||||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||
card-backend-pcsc = { workspace = true }
|
||||
openpgp-card-sequoia = { workspace = true }
|
||||
openpgp-card = { workspace = true }
|
||||
thiserror = "1.0.48"
|
||||
serde = { version = "1.0.192", features = ["derive"] }
|
||||
tokio = { version = "1.35.1", default-features = false, features = ["rt-multi-thread"] }
|
||||
card-backend-pcsc = "0.5.0"
|
||||
openpgp-card-sequoia = { version = "0.2.0", default-features = false }
|
||||
openpgp-card = "0.4.1"
|
||||
clap_complete = { version = "4.4.6", optional = true }
|
||||
sequoia-openpgp = { workspace = true }
|
||||
keyforkd-models.workspace = true
|
||||
base64.workspace = true
|
||||
sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] }
|
||||
keyfork-derive-path-data = { version = "0.1.1", path = "../derive/keyfork-derive-path-data" }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::Keyfork;
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
|
||||
use keyfork_derive_openpgp::{
|
||||
openpgp::{
|
||||
|
@ -10,13 +10,9 @@ use keyfork_derive_openpgp::{
|
|||
},
|
||||
XPrvKey,
|
||||
};
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyfork_derive_util::{
|
||||
request::{DerivationAlgorithm, DerivationRequest, DerivationResponse},
|
||||
DerivationIndex, DerivationPath, IndexError,
|
||||
};
|
||||
use keyforkd_client::Client;
|
||||
use keyforkd_models::Request;
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
||||
|
@ -32,10 +28,7 @@ pub enum DeriveSubcommands {
|
|||
/// It is recommended to use the default expiration of one day and to change the expiration
|
||||
/// using an external utility, to ensure the Certify key is usable.
|
||||
#[command(name = "openpgp")]
|
||||
OpenPGP(OpenPGP),
|
||||
|
||||
/// Derive a bare key for a specific algorithm, in a given format.
|
||||
Key(Key),
|
||||
OpenPGP(OpenPGP)
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
|
@ -44,64 +37,10 @@ pub struct OpenPGP {
|
|||
user_id: String,
|
||||
}
|
||||
|
||||
/// A format for exporting a key.
|
||||
#[derive(ValueEnum, Clone, Debug)]
|
||||
pub enum KeyFormat {
|
||||
Hex,
|
||||
Base64,
|
||||
}
|
||||
|
||||
/// An invalid slug was provided.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum InvalidSlug {
|
||||
/// The value provided was longer than four bytes.
|
||||
#[error("The value provided was longer than four bytes: {0}")]
|
||||
InvalidSize(usize),
|
||||
|
||||
/// The value provided was higher than the maximum derivation index.
|
||||
#[error("The value provided was higher than the maximum derivation index: {0}")]
|
||||
InvalidValue(#[from] IndexError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Slug(DerivationIndex);
|
||||
|
||||
impl std::str::FromStr for Slug {
|
||||
type Err = InvalidSlug;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let bytes = s.as_bytes();
|
||||
let mut parseable_bytes = [0u8; 4];
|
||||
if bytes.len() <= 4 && !bytes.is_empty() {
|
||||
parseable_bytes[(4 - bytes.len())..4].copy_from_slice(bytes);
|
||||
} else {
|
||||
return Err(InvalidSlug::InvalidSize(bytes.len()));
|
||||
}
|
||||
let slug = u32::from_be_bytes(parseable_bytes);
|
||||
let index = DerivationIndex::new(slug, true)?;
|
||||
Ok(Slug(index))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct Key {
|
||||
/// The derivation algorithm to derive a key for.
|
||||
derivation_algorithm: DerivationAlgorithm,
|
||||
|
||||
/// The output format.
|
||||
#[arg(value_enum)]
|
||||
format: KeyFormat,
|
||||
|
||||
/// A maximum of four bytes, used for creating the derivation path.
|
||||
#[arg(value_parser = clap::value_parser!(Slug))]
|
||||
slug: Slug,
|
||||
}
|
||||
|
||||
impl DeriveSubcommands {
|
||||
fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||
match self {
|
||||
DeriveSubcommands::OpenPGP(opgp) => opgp.handle(account),
|
||||
DeriveSubcommands::Key(key) => key.handle(account),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,7 +63,7 @@ impl OpenPGP {
|
|||
|
||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||
|
||||
for packet in cert.as_tsk().into_packets() {
|
||||
for packet in cert.into_packets2() {
|
||||
packet.serialize(&mut w)?;
|
||||
}
|
||||
|
||||
|
@ -133,29 +72,6 @@ impl OpenPGP {
|
|||
}
|
||||
}
|
||||
|
||||
impl Key {
|
||||
pub fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||
let mut client = keyforkd_client::Client::discover_socket()?;
|
||||
let path = DerivationPath::default()
|
||||
.chain_push(self.slug.0.clone())
|
||||
.chain_push(account);
|
||||
let request = DerivationRequest::new(self.derivation_algorithm.clone(), &path);
|
||||
let request = Request::Derivation(request);
|
||||
let derived_key: DerivationResponse = client.request(&request)?.try_into()?;
|
||||
|
||||
let formatted = match self.format {
|
||||
KeyFormat::Hex => smex::encode(derived_key.data),
|
||||
KeyFormat::Base64 => {
|
||||
use base64::prelude::*;
|
||||
BASE64_STANDARD.encode(derived_key.data)
|
||||
}
|
||||
};
|
||||
|
||||
eprintln!("{formatted}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
pub struct Derive {
|
||||
#[command(subcommand)]
|
||||
|
|
|
@ -3,13 +3,7 @@ use clap::{Parser, Subcommand};
|
|||
use std::path::PathBuf;
|
||||
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_prompt::{
|
||||
default_handler, prompt_validated_wordlist,
|
||||
validators::{
|
||||
mnemonic::{MnemonicChoiceValidator, WordLength},
|
||||
Validator,
|
||||
},
|
||||
};
|
||||
use keyfork_prompt::{default_terminal, DefaultTerminal};
|
||||
use keyfork_shard::{remote_decrypt, Format};
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
@ -41,8 +35,8 @@ impl RecoverSubcommands {
|
|||
} => {
|
||||
let content = std::fs::read_to_string(shard_file)?;
|
||||
if content.contains("BEGIN PGP MESSAGE") {
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
let prompt_handler = default_handler()?;
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
||||
let prompt_handler = default_terminal()?;
|
||||
// TODO: remove .clone() by making handle() consume self
|
||||
let seed = openpgp.decrypt_all_shards_to_secret(
|
||||
key_discovery.as_deref(),
|
||||
|
@ -60,15 +54,21 @@ impl RecoverSubcommands {
|
|||
Ok(seed)
|
||||
}
|
||||
RecoverSubcommands::Mnemonic {} => {
|
||||
let mut prompt_handler = default_handler()?;
|
||||
use keyfork_prompt::{
|
||||
validators::{
|
||||
mnemonic::{MnemonicChoiceValidator, WordLength},
|
||||
Validator,
|
||||
},
|
||||
PromptHandler,
|
||||
};
|
||||
let mut term = default_terminal()?;
|
||||
let validator = MnemonicChoiceValidator {
|
||||
word_lengths: [WordLength::Count(12), WordLength::Count(24)],
|
||||
};
|
||||
let mnemonic = prompt_validated_wordlist::<English, _>(
|
||||
&mut *prompt_handler,
|
||||
let mnemonic = term.prompt_validated_wordlist::<English, _>(
|
||||
"Mnemonic: ",
|
||||
3,
|
||||
&*validator.to_fn(),
|
||||
validator.to_fn(),
|
||||
)?;
|
||||
Ok(mnemonic.to_bytes())
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::Keyfork;
|
||||
use clap::{builder::PossibleValue, Parser, Subcommand, ValueEnum};
|
||||
use keyfork_prompt::default_handler;
|
||||
use keyfork_prompt::{default_terminal, DefaultTerminal};
|
||||
use keyfork_shard::Format as _;
|
||||
use std::{
|
||||
io::{stdin, stdout, Read, Write},
|
||||
|
@ -64,7 +64,7 @@ impl ShardExec for OpenPGP {
|
|||
secret: &[u8],
|
||||
output: &mut (impl Write + Send + Sync),
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let opgp = keyfork_shard::openpgp::OpenPGP;
|
||||
let opgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
||||
opgp.shard_and_encrypt(threshold, max, secret, key_discovery, output)
|
||||
}
|
||||
|
||||
|
@ -74,8 +74,8 @@ impl ShardExec for OpenPGP {
|
|||
input: impl Read + Send + Sync,
|
||||
output: &mut impl Write,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
let prompt = default_handler()?;
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
||||
let prompt = default_terminal()?;
|
||||
let bytes = openpgp.decrypt_all_shards_to_secret(key_discovery, input, prompt)?;
|
||||
write!(output, "{}", smex::encode(bytes))?;
|
||||
|
||||
|
@ -87,8 +87,8 @@ impl ShardExec for OpenPGP {
|
|||
key_discovery: Option<&Path>,
|
||||
input: impl Read + Send + Sync,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP;
|
||||
let prompt = default_handler()?;
|
||||
let openpgp = keyfork_shard::openpgp::OpenPGP::<DefaultTerminal>::new();
|
||||
let prompt = default_terminal()?;
|
||||
openpgp.decrypt_one_shard_for_transport(key_discovery, input, prompt)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -17,13 +17,12 @@ use keyfork_derive_openpgp::{
|
|||
XPrv,
|
||||
};
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyfork_derive_util::DerivationIndex;
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
use keyfork_prompt::{
|
||||
default_handler,
|
||||
prompt_validated_passphrase,
|
||||
default_terminal,
|
||||
validators::{SecurePinValidator, Validator},
|
||||
Message,
|
||||
DefaultTerminal, Message, PromptHandler,
|
||||
};
|
||||
|
||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||
|
@ -175,7 +174,7 @@ impl WizardSubcommands {
|
|||
impl GenerateShardSecret {
|
||||
fn handle(&self) -> Result<()> {
|
||||
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||
let mut pm = default_handler()?;
|
||||
let mut pm = default_terminal()?;
|
||||
let mut certs = vec![];
|
||||
let mut seen_cards: HashSet<String> = HashSet::new();
|
||||
let stdout = std::io::stdout();
|
||||
|
@ -214,14 +213,12 @@ impl GenerateShardSecret {
|
|||
.to_string(),
|
||||
))?;
|
||||
};
|
||||
let user_pin = prompt_validated_passphrase(
|
||||
&mut *pm,
|
||||
let user_pin = pm.prompt_validated_passphrase(
|
||||
"Please enter the new smartcard User PIN: ",
|
||||
3,
|
||||
&user_pin_validator,
|
||||
)?;
|
||||
let admin_pin = prompt_validated_passphrase(
|
||||
&mut *pm,
|
||||
let admin_pin = pm.prompt_validated_passphrase(
|
||||
"Please enter the new smartcard Admin PIN: ",
|
||||
3,
|
||||
&admin_pin_validator,
|
||||
|
@ -237,7 +234,7 @@ impl GenerateShardSecret {
|
|||
certs.push(cert);
|
||||
}
|
||||
|
||||
let opgp = OpenPGP;
|
||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
||||
|
||||
if let Some(output_file) = self.output.as_ref() {
|
||||
let output = File::create(output_file)?;
|
||||
|
@ -285,8 +282,8 @@ impl BottomsUp {
|
|||
cert.serialize(&mut w)?;
|
||||
w.finalize()?;
|
||||
|
||||
let opgp = OpenPGP;
|
||||
let certs = OpenPGP::discover_certs(&self.key_discovery)?;
|
||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
||||
let certs = OpenPGP::<DefaultTerminal>::discover_certs(&self.key_discovery)?;
|
||||
|
||||
let shardfile = File::create(&self.output_shardfile)?;
|
||||
opgp.shard_and_encrypt(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-qrcode"
|
||||
version = "0.1.2"
|
||||
version = "0.1.1"
|
||||
repository = "https://git.distrust.co/public/keyfork"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
@ -14,9 +14,9 @@ decode-backend-rqrr = ["dep:rqrr"]
|
|||
decode-backend-zbar = ["dep:keyfork-zbar"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-bug = { workspace = true }
|
||||
keyfork-zbar = { workspace = true, optional = true }
|
||||
image = { workspace = true, default-features = false, features = ["jpeg"] }
|
||||
rqrr = { version = "0.7.0", optional = true }
|
||||
thiserror = { workspace = true }
|
||||
v4l = { workspace = true }
|
||||
keyfork-bug = { version = "0.1.0", path = "../../util/keyfork-bug", registry = "distrust" }
|
||||
keyfork-zbar = { version = "0.1.0", path = "../keyfork-zbar", optional = true, registry = "distrust" }
|
||||
image = { version = "0.24.7", default-features = false, features = ["jpeg"] }
|
||||
rqrr = { version = "0.6.0", optional = true }
|
||||
thiserror = "1.0.56"
|
||||
v4l = "0.14.0"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(missing_docs)]
|
||||
//!
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use keyfork_bug as bug;
|
||||
|
||||
use image::ImageReader;
|
||||
use image::io::Reader as ImageReader;
|
||||
use std::{
|
||||
io::{Cursor, Write},
|
||||
time::{Duration, Instant},
|
||||
|
@ -103,11 +103,6 @@ pub fn qrencode(
|
|||
const VIDEO_FORMAT_READ_ERROR: &str = "Failed to read video device format";
|
||||
|
||||
/// Continuously scan the `index`-th camera for a QR code.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function may return an error if the hardware is unable to scan video or if an image could
|
||||
/// not be decoded.
|
||||
#[cfg(feature = "decode-backend-rqrr")]
|
||||
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
|
||||
let device = Device::new(index)?;
|
||||
|
@ -138,11 +133,6 @@ pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QR
|
|||
}
|
||||
|
||||
/// Continuously scan the `index`-th camera for a QR code.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function may return an error if the hardware is unable to scan video or if an image could
|
||||
/// not be decoded.
|
||||
#[cfg(feature = "decode-backend-zbar")]
|
||||
pub fn scan_camera(timeout: Duration, index: usize) -> Result<Option<String>, QRCodeScanError> {
|
||||
let device = Device::new(index)?;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-zbar"
|
||||
version = "0.1.1"
|
||||
version = "0.1.0"
|
||||
repository = "https://git.distrust.co/public/keyfork"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
@ -13,9 +13,9 @@ bin = ["image"]
|
|||
image = ["dep:image"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-zbar-sys = { workspace = true }
|
||||
image = { workspace = true, default-features = false, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-zbar-sys = { version = "0.1.0", path = "../keyfork-zbar-sys", registry = "distrust" }
|
||||
image = { version = "0.24.7", default-features = false, optional = true }
|
||||
thiserror = "1.0.56"
|
||||
|
||||
[dev-dependencies]
|
||||
v4l = { workspace = true }
|
||||
v4l = "0.14.0"
|
||||
|
|
|
@ -33,7 +33,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
.decode()?,
|
||||
);
|
||||
|
||||
if let Some(symbol) = scanner.scan_image(&image).first() {
|
||||
if let Some(symbol) = scanner.scan_image(&image).get(0) {
|
||||
println!("{}", String::from_utf8_lossy(symbol.data()));
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! A Symbol represents some form of encoded data.
|
||||
//!
|
||||
|
||||
use super::sys;
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "keyfork-tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
assert_cmd = "2.0.16"
|
||||
keyforkd = { workspace = true, features = ["default"] }
|
||||
sequoia-openpgp = { workspace = true, features = ["crypto-nettle"] }
|
|
@ -1 +0,0 @@
|
|||
mod openpgp;
|
|
@ -1,58 +0,0 @@
|
|||
use sequoia_openpgp as openpgp;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use openpgp::{
|
||||
parse::{PacketParser, Parse},
|
||||
policy::StandardPolicy,
|
||||
types::KeyFlags,
|
||||
Cert,
|
||||
};
|
||||
|
||||
const KEYFORK_BIN: &str = "keyfork";
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let policy = StandardPolicy::new();
|
||||
|
||||
let command_output = Command::cargo_bin(KEYFORK_BIN)
|
||||
.unwrap()
|
||||
.args([
|
||||
"derive",
|
||||
"openpgp",
|
||||
"Ryan Heywood (RyanSquared) <ryan@distrust.co>",
|
||||
])
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let packets = PacketParser::from_bytes(&command_output.get_output().stdout).unwrap();
|
||||
let cert = Cert::try_from(packets).unwrap();
|
||||
|
||||
// assert the cert contains _any_ secret key data
|
||||
assert!(
|
||||
cert.is_tsk(),
|
||||
"exported key should contain secret key data, indicated by the key being a TSK"
|
||||
);
|
||||
|
||||
// assert the correct keys were added in the correct order
|
||||
let mut key_formats = std::collections::HashSet::from([
|
||||
KeyFlags::empty().set_certification(),
|
||||
KeyFlags::empty().set_signing(),
|
||||
KeyFlags::empty()
|
||||
.set_transport_encryption()
|
||||
.set_storage_encryption(),
|
||||
KeyFlags::empty().set_authentication(),
|
||||
]);
|
||||
let valid_cert = cert.with_policy(&policy, None).unwrap();
|
||||
for key in valid_cert.keys() {
|
||||
let flags = key.key_flags().unwrap();
|
||||
assert!(
|
||||
key_formats.remove(&flags),
|
||||
"could not find key flag set: {flags:?}"
|
||||
);
|
||||
key.alive().expect("is live after being generated");
|
||||
key.parts_into_secret().expect("has secret keys");
|
||||
}
|
||||
if !key_formats.is_empty() {
|
||||
panic!("remaining key formats: {key_formats:?}");
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
mod derive;
|
|
@ -1,2 +0,0 @@
|
|||
#[cfg(test)]
|
||||
mod keyfork;
|
|
@ -9,4 +9,4 @@ license = "MIT"
|
|||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
anyhow = "1.0.79"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-crossterm"
|
||||
version = "0.27.2"
|
||||
version = "0.27.1"
|
||||
# authors = ["T. Post"]
|
||||
authors = ["Ryan Heywood <ryan@distrust.co>"]
|
||||
description = "A crossplatform terminal library for manipulating terminals."
|
||||
|
@ -55,16 +55,16 @@ crossterm_winapi = { version = "0.9.1", optional = true }
|
|||
libc = "0.2"
|
||||
signal-hook = { version = "0.3.17", optional = true }
|
||||
filedescriptor = { version = "0.8", optional = true }
|
||||
mio = { version = "1.0", features = ["os-poll"], optional = true }
|
||||
signal-hook-mio = { version = "0.2.3", features = ["support-v1_0"], optional = true }
|
||||
mio = { version = "0.8", features = ["os-poll"], optional = true }
|
||||
signal-hook-mio = { version = "0.2.3", features = ["support-v0_8"], optional = true }
|
||||
|
||||
# Dev dependencies (examples, ...)
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio = { version = "1.25", features = ["full"] }
|
||||
futures = "0.3"
|
||||
futures-timer = "3.0"
|
||||
async-std = "1.12"
|
||||
serde_json = { workspace = true }
|
||||
serde_json = "1.0"
|
||||
serial_test = "2.0.0"
|
||||
|
||||
# Examples
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(missing_docs)]
|
||||
//!
|
||||
|
||||
use keyfork_crossterm::{
|
||||
execute,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::VecDeque, io, time::Duration};
|
||||
|
||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token};
|
||||
use signal_hook_mio::v1_0::Signals;
|
||||
use signal_hook_mio::v0_8::Signals;
|
||||
|
||||
#[cfg(feature = "event-stream")]
|
||||
use crate::event::sys::Waker;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-entropy"
|
||||
version = "0.1.2"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -11,5 +11,5 @@ default = ["bin"]
|
|||
bin = ["smex"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-bug = { workspace = true }
|
||||
smex = { workspace = true, optional = true }
|
||||
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug", registry = "distrust" }
|
||||
smex = { version = "0.1.0", path = "../smex", optional = true, registry = "distrust" }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Generate entropy of a given size, encoded as hex.
|
||||
//!
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bit_size: usize = std::env::args()
|
||||
|
|
|
@ -12,13 +12,13 @@ async = ["dep:tokio"]
|
|||
|
||||
[dependencies]
|
||||
# Included in Rust
|
||||
sha2 = { workspace = true }
|
||||
sha2 = "0.10.7"
|
||||
|
||||
# Personally audited
|
||||
thiserror = { workspace = true }
|
||||
thiserror = "1.0.47"
|
||||
|
||||
# Optional, not personally audited
|
||||
tokio = { workspace = true, optional = true, features = ["io-util"] }
|
||||
tokio = { version = "1.32.0", optional = true, features = ["io-util"] }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.31.0"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-mnemonic"
|
||||
version = "0.4.1"
|
||||
version = "0.3.0"
|
||||
description = "Utilities to generate and manage seeds based on BIP-0039 mnemonics."
|
||||
repository = "https://git.distrust.co/public/keyfork"
|
||||
edition = "2021"
|
||||
|
@ -11,14 +11,14 @@ default = ["bin"]
|
|||
bin = ["smex"]
|
||||
|
||||
[dependencies]
|
||||
smex = { workspace = true, optional = true }
|
||||
keyfork-bug = { workspace = true }
|
||||
smex = { version = "0.1.0", path = "../smex", optional = true, registry = "distrust" }
|
||||
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug", registry = "distrust" }
|
||||
|
||||
sha2 = { workspace = true }
|
||||
hmac = { workspace = true }
|
||||
sha2 = "0.10.7"
|
||||
hmac = "0.12.1"
|
||||
pbkdf2 = "0.12.2"
|
||||
|
||||
[dev-dependencies]
|
||||
bip39 = "2.0.0"
|
||||
hex = "0.4.3"
|
||||
serde_json = { workspace = true }
|
||||
serde_json = "1.0.105"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Generate a mnemonic from hex-encoded input.
|
||||
//!
|
||||
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-prompt"
|
||||
version = "0.2.0"
|
||||
version = "0.1.1"
|
||||
description = "Prompt management utilities for Keyfork"
|
||||
repository = "https://git.distrust.co/public/keyfork"
|
||||
edition = "2021"
|
||||
|
@ -13,7 +13,7 @@ default = ["mnemonic"]
|
|||
mnemonic = ["keyfork-mnemonic"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-bug = { workspace = true }
|
||||
keyfork-crossterm = { workspace = true, default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"] }
|
||||
keyfork-mnemonic = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-bug = { version = "0.1.0", path = "../keyfork-bug", registry = "distrust" }
|
||||
keyfork-crossterm = { version = "0.27.1", path = "../keyfork-crossterm", default-features = false, features = ["use-dev-tty", "events", "bracketed-paste"], registry = "distrust" }
|
||||
keyfork-mnemonic = { version = "0.3.0", path = "../keyfork-mnemonic", optional = true, registry = "distrust" }
|
||||
thiserror = "1.0.51"
|
||||
|
|
|
@ -1,26 +1,38 @@
|
|||
#![allow(missing_docs)]
|
||||
//!
|
||||
|
||||
use std::io::{stdin, stdout};
|
||||
|
||||
use keyfork_prompt::{
|
||||
prompt_validated_wordlist,
|
||||
validators::{mnemonic, Validator},
|
||||
default_handler,
|
||||
Terminal, PromptHandler,
|
||||
};
|
||||
|
||||
use keyfork_mnemonic::English;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut handler = default_handler().unwrap();
|
||||
let mut mgr = Terminal::new(stdin(), stdout())?;
|
||||
let transport_validator = mnemonic::MnemonicSetValidator {
|
||||
word_lengths: [24],
|
||||
word_lengths: [9, 24],
|
||||
};
|
||||
let combine_validator = mnemonic::MnemonicSetValidator {
|
||||
word_lengths: [24, 48],
|
||||
};
|
||||
|
||||
let mnemonics = prompt_validated_wordlist::<English, _>(
|
||||
&mut *handler,
|
||||
"Enter a 24-word mnemonic: ",
|
||||
let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
|
||||
"Enter a 9-word and 24-word mnemonic: ",
|
||||
3,
|
||||
&*transport_validator.to_fn(),
|
||||
transport_validator.to_fn(),
|
||||
)?;
|
||||
assert_eq!(mnemonics[0].as_bytes().len(), 12);
|
||||
assert_eq!(mnemonics[1].as_bytes().len(), 32);
|
||||
|
||||
let mnemonics = mgr.prompt_validated_wordlist::<English, _>(
|
||||
"Enter a 24 and 48-word mnemonic: ",
|
||||
3,
|
||||
combine_validator.to_fn(),
|
||||
)?;
|
||||
assert_eq!(mnemonics[0].as_bytes().len(), 32);
|
||||
assert_eq!(mnemonics[1].as_bytes().len(), 64);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
//! A headless prompt handler.
|
||||
//!
|
||||
//! This prompt handler uses the program's standard input and output to read inputs. It is not
|
||||
//! directly intended to be machine-readable, but can be used for scriptable automation in a
|
||||
//! fashion similar to a terminal handler.
|
||||
|
||||
use std::io::{IsTerminal, Write};
|
||||
|
||||
use crate::{BoxResult, Error, Message, PromptHandler, Result};
|
||||
|
||||
/// A headless prompt handler, usable in situations when a terminal might not be available, or for
|
||||
/// scripting purposes where manual input from a terminal is not desirable.
|
||||
pub struct Headless {
|
||||
stdin: std::io::Stdin,
|
||||
stderr: std::io::Stderr,
|
||||
}
|
||||
|
||||
impl Headless {
|
||||
/// Create a new [`Headless`] prompt handler.
|
||||
#[allow(clippy::missing_errors_doc, clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stdin: std::io::stdin(),
|
||||
stderr: std::io::stderr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PromptHandler for Headless {
|
||||
fn prompt_input(&mut self, prompt: &str) -> Result<String> {
|
||||
self.stderr.write_all(prompt.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
let mut line = String::new();
|
||||
self.stdin.read_line(&mut line)?;
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
fn prompt_wordlist(&mut self, prompt: &str, _wordlist: &[&str]) -> Result<String> {
|
||||
self.stderr.write_all(prompt.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
let mut line = String::new();
|
||||
self.stdin.read_line(&mut line)?;
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String> {
|
||||
// Temporarily perform an IOCTL to disable printed output.
|
||||
if self.stdin.is_terminal() {
|
||||
eprintln!("WARNING: Headless terminal mode may leak passwords!");
|
||||
}
|
||||
self.stderr.write_all(prompt.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
let mut line = String::new();
|
||||
self.stdin.read_line(&mut line)?;
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
|
||||
match prompt {
|
||||
Message::Text(s) => {
|
||||
self.stderr.write_all(s.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
}
|
||||
Message::Data(s) => {
|
||||
self.stderr.write_all(s.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn prompt_validated_wordlist(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
_wordlist: &[&str],
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<()> {
|
||||
let mut line = String::new();
|
||||
let mut last_error = String::new();
|
||||
for _ in 0..retries {
|
||||
self.stderr.write_all(prompt.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
self.stderr.flush()?;
|
||||
self.stdin.read_line(&mut line)?;
|
||||
if let Err(e) = validator_fn(std::mem::take(&mut line)) {
|
||||
last_error = e.to_string();
|
||||
self.stderr.write_all(e.to_string().as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::Validation(retries, last_error))
|
||||
}
|
||||
|
||||
fn prompt_validated_passphrase(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<()> {
|
||||
let mut line = String::new();
|
||||
let mut last_error = String::new();
|
||||
for _ in 0..retries {
|
||||
self.stderr.write_all(prompt.as_bytes())?;
|
||||
self.stderr.flush()?;
|
||||
self.stdin.read_line(&mut line)?;
|
||||
if let Err(e) = validator_fn(std::mem::take(&mut line)) {
|
||||
last_error = e.to_string();
|
||||
self.stderr.write_all(e.to_string().as_bytes())?;
|
||||
self.stderr.write_all(b"\n")?;
|
||||
self.stderr.flush()?;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err(Error::Validation(retries, last_error))
|
||||
}
|
||||
}
|
|
@ -1,36 +1,14 @@
|
|||
//! Prompt display and interaction management.
|
||||
|
||||
use std::io::IsTerminal;
|
||||
use std::borrow::Borrow;
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
use keyfork_mnemonic::Wordlist;
|
||||
|
||||
pub mod headless;
|
||||
///
|
||||
pub mod terminal;
|
||||
pub mod validators;
|
||||
pub use headless::Headless;
|
||||
pub use terminal::{default_terminal, DefaultTerminal, Terminal};
|
||||
|
||||
/// An error occurred in the process of loading a default handler.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum DefaultHandlerError {
|
||||
/// An invalid handler was loaded.
|
||||
#[error("An invalid handler was loaded: {handler} ({error})")]
|
||||
InvalidHandler {
|
||||
/// The handle that caused an error.
|
||||
handler: String,
|
||||
|
||||
/// The error that occurred.
|
||||
error: String,
|
||||
},
|
||||
|
||||
/// An unknown handler was requested.
|
||||
#[error("An unknown handler was requested: {handler}")]
|
||||
UnknownHandler {
|
||||
/// The requested, but unknown, handler.
|
||||
handler: String,
|
||||
},
|
||||
}
|
||||
pub use terminal::{Terminal, DefaultTerminal, default_terminal};
|
||||
|
||||
/// An error occurred while displaying a prompt.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
@ -64,9 +42,6 @@ pub enum Message {
|
|||
Data(String),
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub type BoxResult = std::result::Result<(), Box<dyn std::error::Error>>;
|
||||
|
||||
/// A trait to allow displaying prompts and accepting input.
|
||||
pub trait PromptHandler {
|
||||
/// Prompt the user for input.
|
||||
|
@ -82,7 +57,25 @@ pub trait PromptHandler {
|
|||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed or if the input
|
||||
/// could not be read.
|
||||
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &[&str]) -> Result<String>;
|
||||
#[cfg(feature = "mnemonic")]
|
||||
fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String> where X: Wordlist;
|
||||
|
||||
/// Prompt the user for input based on a wordlist, while validating the wordlist using a
|
||||
/// provided parser function, returning the type from the parser. A language must be specified
|
||||
/// as the generic parameter `X` (any type implementing [`Wordlist`]) when parsing a wordlist.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed, if the input
|
||||
/// could not be read, or if the parser returned an error.
|
||||
#[cfg(feature = "mnemonic")]
|
||||
fn prompt_validated_wordlist<X, V>(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error>
|
||||
where
|
||||
X: Wordlist;
|
||||
|
||||
/// Prompt the user for a passphrase, which is hidden while typing.
|
||||
///
|
||||
|
@ -91,140 +84,23 @@ pub trait PromptHandler {
|
|||
/// could not be read.
|
||||
fn prompt_passphrase(&mut self, prompt: &str) -> Result<String>;
|
||||
|
||||
/// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase
|
||||
/// using a provided parser function, returning the type from the parser.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed, if the input
|
||||
/// could not be read, or if the parser returned an error.
|
||||
fn prompt_validated_passphrase<V>(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error>;
|
||||
|
||||
/// Prompt the user with a [`Message`].
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed or if an error
|
||||
/// occurred while waiting for the user to dismiss the message.
|
||||
fn prompt_message(&mut self, prompt: Message) -> Result<()>;
|
||||
|
||||
/// Prompt the user for input based on a wordlist, while validating the wordlist using a
|
||||
/// provided parser function, returning the type from the parser. A language must be specified
|
||||
/// as the generic parameter `X` (any type implementing [`Wordlist`]) when parsing a wordlist.
|
||||
///
|
||||
/// This method MUST NOT be used directly. Instead, use
|
||||
/// [`prompt_validated_wordlist`].
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed, if the input
|
||||
/// could not be read, or if the parser returned an error.
|
||||
fn prompt_validated_wordlist(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
wordlist: &[&str],
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase
|
||||
/// using a provided parser function, returning the type from the parser.
|
||||
///
|
||||
/// This method MUST NOT be used directly. Instead, use
|
||||
/// [`prompt_validated_wordlist`].
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed, if the input
|
||||
/// could not be read, or if the parser returned an error.
|
||||
fn prompt_validated_passphrase(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Prompt the user for input based on a wordlist, while validating the wordlist using a
|
||||
/// provided parser function, returning the type from the parser. A language must be specified
|
||||
/// as the generic parameter `X` (any type implementing [`Wordlist`]) when parsing a wordlist.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed, if the input
|
||||
/// could not be read, or if the parser returned an error.
|
||||
#[cfg(feature = "mnemonic")]
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn prompt_validated_wordlist<X, V>(
|
||||
handler: &mut dyn PromptHandler,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: &dyn Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error>
|
||||
where
|
||||
X: Wordlist,
|
||||
{
|
||||
let wordlist = X::get_singleton();
|
||||
let words = wordlist.to_str_array();
|
||||
let mut opt: Option<V> = None;
|
||||
handler.prompt_validated_wordlist(prompt, retries, &words, &mut |string| {
|
||||
opt = Some(validator_fn(string)?);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(opt.unwrap())
|
||||
}
|
||||
|
||||
/// Prompt the user for a passphrase, which is hidden while typing, and validate the passphrase
|
||||
/// using a provided parser function, returning the type from the parser.
|
||||
///
|
||||
/// # Errors
|
||||
/// The method may return an error if the message was not able to be displayed, if the input
|
||||
/// could not be read, or if the parser returned an error.
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn prompt_validated_passphrase<V>(
|
||||
handler: &mut dyn PromptHandler,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error> {
|
||||
let mut opt: Option<V> = None;
|
||||
handler.prompt_validated_passphrase(prompt, retries, &mut |string| {
|
||||
opt = Some(validator_fn(string)?);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(opt.unwrap())
|
||||
}
|
||||
|
||||
/// Get a Prompt Handler that is most suitable for the given environment.
|
||||
///
|
||||
/// The following handlers will be used based on the `KEYFORK_PROMPT_TYPE` variable:
|
||||
/// * `KEYFORK_PROMPT_TYPE=terminal`: [`DefaultTerminal`]
|
||||
/// * `KEYFORK_PROMPT_TYPE=headless`: [`Headless`]
|
||||
///
|
||||
/// Otherwise, the following heuristics are followed:
|
||||
/// * [`std::io::IsTerminal::is_terminal`]: [`DefaultTerminal`]
|
||||
/// * default: [`Headless`]
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// The function will return an error if a specific handler was requested but could not be
|
||||
/// constructed.
|
||||
pub fn default_handler() -> Result<Box<dyn PromptHandler>, DefaultHandlerError> {
|
||||
if let Some((_, value)) = std::env::vars().find(|(k, _)| k == "KEYFORK_PROMPT_TYPE") {
|
||||
match value.as_str() {
|
||||
"terminal" => match default_terminal() {
|
||||
Ok(terminal) => return Ok(Box::new(terminal)),
|
||||
Err(e) => {
|
||||
return Err(DefaultHandlerError::InvalidHandler {
|
||||
handler: value,
|
||||
error: e.to_string(),
|
||||
})
|
||||
}
|
||||
},
|
||||
"headless" => {
|
||||
return Ok(Box::new(Headless::new()));
|
||||
}
|
||||
_ => {
|
||||
return Err(DefaultHandlerError::UnknownHandler { handler: value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stdout can be not-a-terminal and we'll just override it anyways, stdin is the
|
||||
// important one.
|
||||
if std::io::stdin().is_terminal() {
|
||||
// because this is a "guessed" handler, let's take the nice route and not error, just skip.
|
||||
if let Ok(terminal) = default_terminal() {
|
||||
return Ok(Box::new(terminal));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Box::new(Headless::new()))
|
||||
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()>;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,3 @@
|
|||
//! A terminal prompt handler.
|
||||
//!
|
||||
//! This prompt handler uses a raw terminal device to read inputs and uses ANSI escape codes to
|
||||
//! provide formatting for prompts. Because of these reasons, it is not intended to be
|
||||
//! machine-readable.
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
io::{stderr, stdin, BufRead, BufReader, Read, Stderr, Stdin, Write},
|
||||
|
@ -21,7 +15,7 @@ use keyfork_crossterm::{
|
|||
|
||||
use keyfork_bug::bug;
|
||||
|
||||
use crate::{BoxResult, Error, Message, PromptHandler};
|
||||
use crate::{Error, Message, PromptHandler, Wordlist};
|
||||
|
||||
#[allow(missing_docs)]
|
||||
pub type Result<T, E = Error> = std::result::Result<T, E>;
|
||||
|
@ -198,20 +192,23 @@ where
|
|||
Ok(line)
|
||||
}
|
||||
|
||||
fn prompt_validated_wordlist(
|
||||
#[cfg(feature = "mnemonic")]
|
||||
fn prompt_validated_wordlist<X, V>(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
wordlist: &[&str],
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<(), Error> {
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error>
|
||||
where
|
||||
X: Wordlist,
|
||||
{
|
||||
let mut last_error = None;
|
||||
for _ in 0..retries {
|
||||
let s = self.prompt_wordlist(prompt, wordlist)?;
|
||||
let s = self.prompt_wordlist::<X>(prompt)?;
|
||||
match validator_fn(s) {
|
||||
Ok(v) => return Ok(v),
|
||||
Err(e) => {
|
||||
self.prompt_message(Message::Text(format!("Error validating wordlist: {e}")))?;
|
||||
self.prompt_message(&Message::Text(format!("Error validating wordlist: {e}")))?;
|
||||
let _ = last_error.insert(e);
|
||||
}
|
||||
}
|
||||
|
@ -224,8 +221,15 @@ where
|
|||
))
|
||||
}
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn prompt_wordlist(&mut self, prompt: &str, wordlist: &[&str]) -> Result<String> {
|
||||
fn prompt_wordlist<X>(&mut self, prompt: &str) -> Result<String>
|
||||
where
|
||||
X: Wordlist,
|
||||
{
|
||||
let wordlist = X::get_singleton();
|
||||
let words = wordlist.to_str_array();
|
||||
|
||||
let mut terminal = self
|
||||
.lock()
|
||||
.alternate_screen()?
|
||||
|
@ -295,7 +299,7 @@ where
|
|||
let word = input.split_whitespace().next_back().map(ToOwned::to_owned);
|
||||
if let Some(steel_word) = word {
|
||||
if steel_word.len() >= 4 {
|
||||
for word in wordlist.iter().filter(|word| word.len() >= 4) {
|
||||
for word in words.iter().filter(|word| word.len() >= 4) {
|
||||
if word[..4] == steel_word {
|
||||
input.push_str(&word[4..]);
|
||||
input.push(' ');
|
||||
|
@ -341,7 +345,7 @@ where
|
|||
let mut iter = printable_input.split_whitespace().peekable();
|
||||
|
||||
while let Some(word) = iter.next() {
|
||||
if wordlist.contains(&word) {
|
||||
if words.contains(&word) {
|
||||
terminal.queue(PrintStyledContent(word.green()))?;
|
||||
} else {
|
||||
terminal.queue(PrintStyledContent(word.red()))?;
|
||||
|
@ -362,19 +366,19 @@ where
|
|||
Ok(input)
|
||||
}
|
||||
|
||||
fn prompt_validated_passphrase(
|
||||
fn prompt_validated_passphrase<V>(
|
||||
&mut self,
|
||||
prompt: &str,
|
||||
retries: u8,
|
||||
validator_fn: &mut dyn FnMut(String) -> BoxResult,
|
||||
) -> Result<(), Error> {
|
||||
validator_fn: impl Fn(String) -> Result<V, Box<dyn std::error::Error>>,
|
||||
) -> Result<V, Error> {
|
||||
let mut last_error = None;
|
||||
for _ in 0..retries {
|
||||
let s = self.prompt_passphrase(prompt)?;
|
||||
match validator_fn(s) {
|
||||
Ok(v) => return Ok(v),
|
||||
Err(e) => {
|
||||
self.prompt_message(Message::Text(format!(
|
||||
self.prompt_message(&Message::Text(format!(
|
||||
"Error validating passphrase: {e}"
|
||||
)))?;
|
||||
let _ = last_error.insert(e);
|
||||
|
@ -451,7 +455,7 @@ where
|
|||
Ok(passphrase)
|
||||
}
|
||||
|
||||
fn prompt_message(&mut self, prompt: Message) -> Result<()> {
|
||||
fn prompt_message(&mut self, prompt: impl Borrow<Message>) -> Result<()> {
|
||||
let mut terminal = self.lock().alternate_screen()?.raw_mode()?;
|
||||
|
||||
loop {
|
||||
|
|
|
@ -5,4 +5,4 @@ edition = "2021"
|
|||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
smex = { workspace = true }
|
||||
smex = { version = "0.1.0", path = "../smex", registry = "distrust" }
|
||||
|
|
104
deny.toml
104
deny.toml
|
@ -11,9 +11,6 @@
|
|||
|
||||
# Root options
|
||||
|
||||
# The graph table configures how the dependency graph is constructed and thus
|
||||
# which crates the checks are performed against
|
||||
[graph]
|
||||
# If 1 or more target triples (and optionally, target_features) are specified,
|
||||
# only the specified targets will be checked when running `cargo deny check`.
|
||||
# This means, if a particular package is only ever used as a target specific
|
||||
|
@ -25,7 +22,7 @@
|
|||
targets = [
|
||||
# The triple can be any string, but only the target triples built in to
|
||||
# rustc (as of 1.40) can be checked against actual config expressions
|
||||
#"x86_64-unknown-linux-musl",
|
||||
#{ triple = "x86_64-unknown-linux-musl" },
|
||||
# You can also specify which target_features you promise are enabled for a
|
||||
# particular target. target_features are currently not validated against
|
||||
# the actual valid features supported by the target architecture.
|
||||
|
@ -49,9 +46,6 @@ no-default-features = false
|
|||
# If set, these feature will be enabled when collecting metadata. If `--features`
|
||||
# is specified on the cmd line they will take precedence over this option.
|
||||
#features = []
|
||||
|
||||
# The output table provides options for how/if diagnostics are outputted
|
||||
[output]
|
||||
# When outputting inclusion graphs in diagnostics that include features, this
|
||||
# option can be used to specify the depth at which feature edges will be added.
|
||||
# This option is included since the graphs can be quite large and the addition
|
||||
|
@ -63,20 +57,38 @@ feature-depth = 1
|
|||
# More documentation for the advisories section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||
[advisories]
|
||||
# The path where the advisory databases are cloned/fetched into
|
||||
#db-path = "$CARGO_HOME/advisory-dbs"
|
||||
# The path where the advisory database is cloned/fetched into
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
# The url(s) of the advisory databases to use
|
||||
#db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
# The lint level for security vulnerabilities
|
||||
vulnerability = "deny"
|
||||
# The lint level for unmaintained crates
|
||||
unmaintained = "warn"
|
||||
# The lint level for crates that have been yanked from their source registry
|
||||
yanked = "warn"
|
||||
# The lint level for crates with security notices. Note that as of
|
||||
# 2019-12-17 there are no security notice advisories in
|
||||
# https://github.com/rustsec/advisory-db
|
||||
notice = "warn"
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
#"RUSTSEC-0000-0000",
|
||||
#{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" },
|
||||
#"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish
|
||||
#{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" },
|
||||
|
||||
{ id = "RUSTSEC-2023-0071", reason = "Not applicable, vulnerable path is not used" },
|
||||
# Not applicable, RSA is not used for crypto operations in the dep it's
|
||||
# used for, openpgp-card
|
||||
"RUSTSEC-2023-0071",
|
||||
]
|
||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||
# lower than the range specified will be ignored. Note that ignored advisories
|
||||
# will still output a note when they are encountered.
|
||||
# * None - CVSS Score 0.0
|
||||
# * Low - CVSS Score 0.1 - 3.9
|
||||
# * Medium - CVSS Score 4.0 - 6.9
|
||||
# * High - CVSS Score 7.0 - 8.9
|
||||
# * Critical - CVSS Score 9.0 - 10.0
|
||||
#severity-threshold =
|
||||
|
||||
# If this is true, then cargo deny will use the git executable to fetch advisory database.
|
||||
# If this is false, then it uses a built-in git library.
|
||||
# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support.
|
||||
|
@ -87,6 +99,8 @@ ignore = [
|
|||
# More documentation for the licenses section can be found here:
|
||||
# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
|
||||
[licenses]
|
||||
# The lint level for crates which do not have a detectable license
|
||||
unlicensed = "deny"
|
||||
# List of explicitly allowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
|
@ -100,8 +114,30 @@ allow = [
|
|||
"LGPL-2.0",
|
||||
"LGPL-3.0",
|
||||
"Unicode-3.0",
|
||||
#"Apache-2.0 WITH LLVM-exception",
|
||||
]
|
||||
|
||||
# List of explicitly disallowed licenses
|
||||
# See https://spdx.org/licenses/ for list of possible licenses
|
||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||
deny = [
|
||||
#"Nokia",
|
||||
]
|
||||
# Lint level for licenses considered copyleft
|
||||
copyleft = "warn"
|
||||
# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses
|
||||
# * both - The license will be approved if it is both OSI-approved *AND* FSF
|
||||
# * either - The license will be approved if it is either OSI-approved *OR* FSF
|
||||
# * osi - The license will be approved if it is OSI approved
|
||||
# * fsf - The license will be approved if it is FSF Free
|
||||
# * osi-only - The license will be approved if it is OSI-approved *AND NOT* FSF
|
||||
# * fsf-only - The license will be approved if it is FSF *AND NOT* OSI-approved
|
||||
# * neither - This predicate is ignored and the default lint level is used
|
||||
allow-osi-fsf-free = "neither"
|
||||
# Lint level used when no other predicates are matched
|
||||
# 1. License isn't in the allow or deny lists
|
||||
# 2. License isn't copyleft
|
||||
# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither"
|
||||
default = "deny"
|
||||
# The confidence threshold for detecting a license from license text.
|
||||
# The higher the value, the more closely the license text must be to the
|
||||
# canonical license text of a valid SPDX license file.
|
||||
|
@ -112,17 +148,18 @@ confidence-threshold = 0.8
|
|||
exceptions = [
|
||||
# Each entry is the crate and version constraint, and its specific allow
|
||||
# list
|
||||
#{ allow = ["Zlib"], crate = "adler32" },
|
||||
#{ allow = ["Zlib"], name = "adler32", version = "*" },
|
||||
{ allow = ["BSL-1.0"], name = "xxhash-rust", version = "*" },
|
||||
{ allow = ["Zlib"], name = "foldhash", version = "*" },
|
||||
]
|
||||
|
||||
# Some crates don't have (easily) machine readable licensing information,
|
||||
# adding a clarification entry for it allows you to manually specify the
|
||||
# licensing information
|
||||
#[[licenses.clarify]]
|
||||
# The package spec the clarification applies to
|
||||
#crate = "ring"
|
||||
# The name of the crate the clarification applies to
|
||||
#name = "ring"
|
||||
# The optional version constraint for the crate
|
||||
#version = "*"
|
||||
# The SPDX expression for the license requirements of the crate
|
||||
#expression = "MIT AND ISC AND OpenSSL"
|
||||
# One or more files in the crate's source used as the "source of truth" for
|
||||
|
@ -131,8 +168,8 @@ exceptions = [
|
|||
# and the crate will be checked normally, which may produce warnings or errors
|
||||
# depending on the rest of your configuration
|
||||
#license-files = [
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
# Each entry is a crate relative path, and the (opaque) hash of its contents
|
||||
#{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||
#]
|
||||
|
||||
[licenses.private]
|
||||
|
@ -172,24 +209,25 @@ workspace-default-features = "allow"
|
|||
external-default-features = "allow"
|
||||
# List of crates that are allowed. Use with care!
|
||||
allow = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" },
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
]
|
||||
# List of crates to deny
|
||||
deny = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" },
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
#
|
||||
# Wrapper crates can optionally be specified to allow the crate when it
|
||||
# is a direct dependency of the otherwise banned crate
|
||||
#{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] },
|
||||
{ name = "serde", version = ">1.0.171, <1.0.184", reason = "ships with prebuilt binaries" }
|
||||
#{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
|
||||
{ name = "serde", version = ">1.0.171, <1.0.184" }
|
||||
]
|
||||
|
||||
# List of features to allow/deny
|
||||
# Each entry the name of a crate and a version range. If version is
|
||||
# not specified, all versions will be matched.
|
||||
#[[bans.features]]
|
||||
#crate = "reqwest"
|
||||
#name = "reqwest"
|
||||
# Features to not allow
|
||||
#deny = ["json"]
|
||||
# Features to allow
|
||||
|
@ -210,18 +248,14 @@ deny = [
|
|||
|
||||
# Certain crates/versions that will be skipped when doing duplicate detection.
|
||||
skip = [
|
||||
#"ansi_term@0.11.0",
|
||||
#{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" },
|
||||
#{ name = "ansi_term", version = "=0.11.0" },
|
||||
]
|
||||
# Similarly to `skip` allows you to skip certain crates during duplicate
|
||||
# detection. Unlike skip, it also includes the entire tree of transitive
|
||||
# dependencies starting at the specified crate, up to a certain depth, which is
|
||||
# by default infinite.
|
||||
skip-tree = [
|
||||
{ name = "windows-sys" },
|
||||
{ name = "windows-targets" },
|
||||
#"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies
|
||||
#{ crate = "ansi_term@0.11.0", depth = 20 },
|
||||
#{ name = "ansi_term", version = "=0.11.0", depth = 20 },
|
||||
]
|
||||
|
||||
# This section is considered when running `cargo deny check sources`.
|
||||
|
|
|
@ -3,13 +3,12 @@ set -o pipefail
|
|||
|
||||
LAST_REF="$1"
|
||||
CURRENT_REF="${2:-HEAD}"
|
||||
IGNORE="${3:-ABCDEFG}"
|
||||
|
||||
cargo metadata --format-version=1 | \
|
||||
jq -r '.packages[] | select(.source == null) | .name + " " + .manifest_path' | \
|
||||
while read crate manifest_path; do
|
||||
crate_path="$(dirname $manifest_path)"
|
||||
git_log="$(git log --format='%h %s' "$LAST_REF".."$CURRENT_REF" "$crate_path" | { grep -v "$IGNORE" || true; })"
|
||||
git_log="$(git log --format='%h %s' "$LAST_REF"..HEAD "$crate_path")"
|
||||
if test ! -z "$git_log"; then
|
||||
echo "### Changes in $crate:"
|
||||
echo ""
|
||||
|
|
|
@ -10,7 +10,7 @@ cargo metadata --format-version=1 | jq -r '.packages[] | select(.source == null)
|
|||
|
||||
while read crate manifest_path version <&3; do
|
||||
crate_path="$(dirname $manifest_path)"
|
||||
git_log="$(git log --format='%h %s' "$LAST_REF".."$CURRENT_REF" "$crate_path")"
|
||||
git_log="$(git log --format='%h %s' "$LAST_REF"..HEAD "$crate_path")"
|
||||
git_tag="$(git tag --list "$crate-v${version}")"
|
||||
if test ! -z "$git_log" -a -z "$git_tag"; then
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue