Compare commits
No commits in common. "main" and "anton/require-min-entropy" have entirely different histories.
main
...
anton/requ
277
CHANGELOG.md
277
CHANGELOG.md
|
@ -1,277 +0,0 @@
|
|||
# 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
|
||||
would not be able to properly verify the length of remote shard QR codes.
|
||||
|
||||
# Keyfork v0.2.0
|
||||
|
||||
Some of the changes in this release are based on feedback from audits
|
||||
(publications coming soon!). The previous version of Keyfork, in almost every
|
||||
configuration, is safe to use. The most significant change in this version
|
||||
affects Keyfork Shard, which has an incompatible difference between this
|
||||
version and the previous version. Information about shards, such as the length
|
||||
of the shard, could be leaked and discovered by an attacker when using the
|
||||
Remote Shard recovery mechanism.
|
||||
|
||||
An additional change is the requirement of hardened indices on the first two
|
||||
levels of key derivation. This is due to Keyfork potentially leaking private
|
||||
keys when hardened derivation is not used. To be completely honest, I don't
|
||||
entirely understand the math behind it.
|
||||
|
||||
There is no reason to upgrade if Keyfork has been used as-is, as all supported
|
||||
provisioners at this point in time require hardened derivation at all steps.
|
||||
|
||||
### Changes in keyfork:
|
||||
|
||||
```
|
||||
d04989e keyfork-derive-util: make key parsing fallible again, since secp256k1 isn't guaranteed correct
|
||||
5d2309e keyfork-prompt: add SecurePinValidator for making new, secure, PINs
|
||||
cdf4015 keyfork wizard: use correct derivation path for re-deriving shard decryption keys
|
||||
f0e5ae9 keyfork-derive-openpgp: document KEYFORK_OPENPGP_EXPIRE
|
||||
289cec3 keyfork wizard: upcast i and index to avoid wrapping add
|
||||
9394500 keyfork-shard: generate nonce using hkdf
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-openpgp:
|
||||
|
||||
```
|
||||
f0e5ae9 keyfork-derive-openpgp: document KEYFORK_OPENPGP_EXPIRE
|
||||
9f089e7 keyfork-derive-openpgp: use .first() in place of .get(0)
|
||||
```
|
||||
|
||||
### Changes in keyfork-derive-util:
|
||||
|
||||
```
|
||||
de4e98a keyfork-derive-util: black-box checking all zeroes
|
||||
48ccd7c keyfork-derive-util: add note about potential side-channel when verifying keys
|
||||
d04989e keyfork-derive-util: make key parsing fallible again, since secp256k1 isn't guaranteed correct
|
||||
1de466c keyfork-derive-util: allow zeroable input for non-master-key derivation
|
||||
61871a7 keyfork-derive-util: make private and public test keys more visible
|
||||
2bca0a1 keyfork-derive-util: make Test{Public,Private}Key public, rename Internal algorithm
|
||||
```
|
||||
|
||||
### Changes in keyfork-entropy:
|
||||
|
||||
```
|
||||
5438f4e keyfork-entropy: downgrade entropy size limit to warning
|
||||
```
|
||||
|
||||
### Changes in keyfork-mnemonic-util:
|
||||
|
||||
```
|
||||
001fc0b remove trailing hitespace :(
|
||||
6a265ad keyfork-mnemonic-util: add MnemonicBase::from_nonstandard_bytes
|
||||
```
|
||||
|
||||
### Changes in keyfork-prompt:
|
||||
|
||||
```
|
||||
5d2309e keyfork-prompt: add SecurePinValidator for making new, secure, PINs
|
||||
```
|
||||
|
||||
### Changes in keyfork-qrcode:
|
||||
|
||||
```
|
||||
fa125e7 keyfork-qrcode: prefer Instant over SystemTime for infallible time comparison
|
||||
```
|
||||
|
||||
### Changes in keyfork-shard:
|
||||
|
||||
```
|
||||
d04989e keyfork-derive-util: make key parsing fallible again, since secp256k1 isn't guaranteed correct
|
||||
1a036a0 keyfork-shard: clean up documentation for encrypted shard padding
|
||||
e068743 keyfork-shard: display error message on duplicate key fingerprints found
|
||||
23db509 keyfork-shard: improve wording for counting shardholders
|
||||
9461772 keyfork-shard: ignore duplicate certificate entries
|
||||
6a265ad keyfork-mnemonic-util: add MnemonicBase::from_nonstandard_bytes
|
||||
c0b19e2 keyfork-shard: assert shared secrets are contributory
|
||||
0fe5301 keyfork-shard: add in bug messages
|
||||
08a66e2 keyfork-shard: base64 encode content instead of base16
|
||||
6fa434e keyfork-shard: shorten length and pad inside encrypted block
|
||||
9394500 keyfork-shard: generate nonce using hkdf
|
||||
194d475 keyfork-shard: validate signatures using shard-specific validation requirements
|
||||
```
|
||||
|
||||
### Changes in keyfork-zbar:
|
||||
|
||||
```
|
||||
0c76869 .cargo/config.toml: add registry configuration :)
|
||||
```
|
||||
|
||||
### Changes in keyforkd:
|
||||
|
||||
```
|
||||
bcfcc87 keyforkd: add warning when loading seed with less than 128 bits
|
||||
40551a5 keyforkd: require hardened derivation on two highest indexes
|
||||
```
|
||||
|
||||
### Changes in keyforkd-client:
|
||||
|
||||
```
|
||||
d04989e keyfork-derive-util: make key parsing fallible again, since secp256k1 isn't guaranteed correct
|
||||
1de466c keyfork-derive-util: allow zeroable input for non-master-key derivation
|
||||
40551a5 keyforkd: require hardened derivation on two highest indexes
|
||||
```
|
||||
|
||||
### Changes in keyforkd-models:
|
||||
|
||||
```
|
||||
40551a5 keyforkd: require hardened derivation on two highest indexes
|
||||
```
|
||||
|
||||
# Keyfork v0.1.0
|
||||
|
||||
### Tagged releases:
|
||||
|
||||
* `keyfork-bin 0.1.0`
|
||||
* `keyfork-bug 0.1.0`
|
||||
* `keyfork-crossterm 0.27.1`
|
||||
* `keyfork-derive-key 0.1.0`
|
||||
* `keyfork-derive-openpgp 0.1.0`
|
||||
* `keyfork-derive-path-data 0.1.0`
|
||||
* `keyfork-derive-util 0.1.0`
|
||||
* `keyfork-entropy 0.1.0`
|
||||
* `keyfork-frame 0.1.0`
|
||||
* `keyfork-mnemonic-util 0.2.0`
|
||||
* `keyfork-prompt 0.1.0`
|
||||
* `keyfork-qrcode 0.1.0`
|
||||
* `keyfork-shard 0.1.0`
|
||||
* `keyfork-slip10-test-data 0.1.0`
|
||||
* `keyfork 0.1.0`
|
||||
* `keyfork-zbar-sys 0.1.0`
|
||||
* `keyfork-zbar 0.1.0`
|
||||
* `keyforkd-client 0.1.0`
|
||||
* `keyforkd-models 0.1.0`
|
||||
* `keyforkd 0.1.0`
|
||||
* `smex 0.1.0`
|
File diff suppressed because it is too large
Load Diff
53
Cargo.toml
53
Cargo.toml
|
@ -19,63 +19,12 @@ members = [
|
|||
"crates/util/keyfork-crossterm",
|
||||
"crates/util/keyfork-entropy",
|
||||
"crates/util/keyfork-frame",
|
||||
"crates/util/keyfork-mnemonic",
|
||||
"crates/util/keyfork-mnemonic-util",
|
||||
"crates/util/keyfork-prompt",
|
||||
"crates/util/keyfork-slip10-test-data",
|
||||
"crates/util/smex",
|
||||
]
|
||||
|
||||
[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-key = { version = "0.1.1", path = "crates/derive/keyfork-derive-key", 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.2.2", 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.1.1", 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"
|
||||
|
||||
[profile.dev.package.keyfork-qrcode]
|
||||
opt-level = 3
|
||||
debug = true
|
||||
|
|
|
@ -178,8 +178,7 @@ keyfork recover mnemonic
|
|||
|
||||
This guide assumes you are sharding to an `N`-of-`M` system with `I` smart
|
||||
cards per shardholder. The variables will be used in the following commands as
|
||||
`$N`, `$M`, and `$I`. The smart card OpenPGP slots will be factory reset during
|
||||
the process.
|
||||
`$N`, `$M`, and `$I`. The smart cards will be factory reset during the process.
|
||||
|
||||
On an airgapped system, run the following command to generate a file containing
|
||||
encrypted shards of a generated seed:
|
||||
|
|
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.1"
|
||||
version = "0.1.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.1.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.1.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" }
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
//! # The Keyforkd Client
|
||||
//!
|
||||
//! Keyfork allows securing the master key and highest-level derivation keys by having derivation
|
||||
//! requests performed against a server, "Keyforkd" or the "Keyfork Server". This allows
|
||||
//! enforcement of policies, such as requiring at least two leves of a derivation path (for
|
||||
//! instance, `m/0'` would not be allowed, but `m/0'/0'` would). The server is operated on a UNIX
|
||||
//! socket with messages sent using the Keyfork Frame format.
|
||||
//! requests performed against a server, "Keyforkd" or the "Keyfork Server". The server is operated
|
||||
//! on a UNIX socket with messages sent using the Keyfork Frame format.
|
||||
//!
|
||||
//! Programs using the Keyfork Client should ensure they are built against a compatible version of
|
||||
//! the Keyfork Server. For versions prior to `1.0.0`, all versions within a "minor" version (i.e.,
|
||||
|
@ -12,58 +10,9 @@
|
|||
//! after `1.0.0`, all versions within a "major" version (i.e., `1.0.0`) will be compatible, but
|
||||
//! `1.x.y` will not be compatible with `2.0.0`.
|
||||
//!
|
||||
//! The Keyfork Client documentation makes extensive use of the `keyforkd::test_util` module.
|
||||
//! This provides testing infrastructure to set up a temporary Keyfork Daemon. In
|
||||
//! your code, you should assume the daemon has already been initialized, whether by another
|
||||
//! process, on another terminal, or some other instance. At no point should a program deriving an
|
||||
//! "endpoint" key have control over a mnemonic or a seed.
|
||||
//! Presently, the Keyfork server only supports the following requests:
|
||||
//!
|
||||
//! ## Server Requests
|
||||
//!
|
||||
//! Keyfork is designed as a client-request/server-response model. The client sends a request, such
|
||||
//! as a derivation request, and the server sends its response. Presently, the Keyfork server
|
||||
//! supports the following requests:
|
||||
//!
|
||||
//! ### Request: Derive Key
|
||||
//!
|
||||
//! The client creates a derivation path of at least two indices and requests a derived XPrv
|
||||
//! (Extended Private Key) from the server.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
//! use keyforkd_client::Client;
|
||||
//! use keyfork_derive_util::DerivationPath;
|
||||
//! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
||||
//! // use k256::SecretKey as PrivateKey;
|
||||
//! // use ed25519_dalek::SigningKey as PrivateKey;
|
||||
//!
|
||||
//! #[derive(Debug, thiserror::Error)]
|
||||
//! enum Error {
|
||||
//! #[error(transparent)]
|
||||
//! Path(#[from] keyfork_derive_util::PathError),
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! Keyforkd(#[from] keyforkd_client::Error),
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<(), Error> {
|
||||
//! # let seed = b"funky accordion noises";
|
||||
//! # keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
//! let derivation_path = DerivationPath::from_str("m/44'/0'")?;
|
||||
//! let mut client = Client::discover_socket()?;
|
||||
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path)?;
|
||||
//! # Ok::<_, Error>(())
|
||||
//! # })?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ---
|
||||
//!
|
||||
//! Request objects are typically handled by the Keyfork Client library (such as with
|
||||
//! [`Client::request_xprv`]). While unadvised, clients can also attempt to handle their own
|
||||
//! requests, using [`Client::request`].
|
||||
//! * Derive Key
|
||||
//!
|
||||
//! ## Extended Private Keys
|
||||
//!
|
||||
|
@ -72,87 +21,27 @@
|
|||
//! The tests for this library ensure that all levels of Keyfork derivation beyond the required two
|
||||
//! will be derived similarly between the server and the client.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
//! use keyforkd_client::Client;
|
||||
//! use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
//! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
||||
//! // use k256::SecretKey as PrivateKey;
|
||||
//! // use ed25519_dalek::SigningKey as PrivateKey;
|
||||
//! # fn check_wallet<T>(_: T) {}
|
||||
//!
|
||||
//! #[derive(Debug, thiserror::Error)]
|
||||
//! enum Error {
|
||||
//! #[error(transparent)]
|
||||
//! Index(#[from] keyfork_derive_util::IndexError),
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! Path(#[from] keyfork_derive_util::PathError),
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! PrivateKey(#[from] keyfork_derive_util::PrivateKeyError),
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! Keyforkd(#[from] keyforkd_client::Error),
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<(), Error> {
|
||||
//! # let seed = b"funky accordion noises";
|
||||
//! # keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
//! let derivation_path = DerivationPath::from_str("m/44'/0'/0'/0")?;
|
||||
//! let mut client = Client::discover_socket()?;
|
||||
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path)?;
|
||||
//! // scan first 20 wallets
|
||||
//! for index in 0..20 {
|
||||
//! // use non-hardened derivation
|
||||
//! let new_xprv = xprv.derive_child(&DerivationIndex::new(index, false)?);
|
||||
//! check_wallet(new_xprv)
|
||||
//! }
|
||||
//! # Ok::<_, Error>(())
|
||||
//! # })?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Testing Infrastructure
|
||||
//!
|
||||
//! In tests, the `keyforkd::test_util` module and TestPrivateKeys can be used. These provide
|
||||
//! useful utilities for writing tests that interact with the Keyfork Server without needing to
|
||||
//! manually create the server for the purpose of the test. The `run_test` method can be used to
|
||||
//! run a test, which can handle both returning errors and correctly translating panics (though,
|
||||
//! the panics definitely won't look tidy).
|
||||
//!
|
||||
//! # Examples
|
||||
//! ```rust
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
//! use keyforkd_client::Client;
|
||||
//! use keyfork_derive_util::DerivationPath;
|
||||
//! use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
||||
//! # use keyfork_derive_util::private_key::TestPrivateKey as PrivateKey;
|
||||
//! // use k256::SecretKey as PrivateKey;
|
||||
//! // use ed25519_dalek::SigningKey as PrivateKey;
|
||||
//!
|
||||
//! #[derive(Debug, thiserror::Error)]
|
||||
//! enum Error {
|
||||
//! #[error(transparent)]
|
||||
//! Path(#[from] keyfork_derive_util::PathError),
|
||||
//!
|
||||
//! #[error(transparent)]
|
||||
//! Keyforkd(#[from] keyforkd_client::Error),
|
||||
//! }
|
||||
//!
|
||||
//! fn main() -> Result<(), Error> {
|
||||
//! let seed = b"funky accordion noises";
|
||||
//! keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
//! let derivation_path = DerivationPath::from_str("m/44'/0'")?;
|
||||
//! let mut client = Client::discover_socket()?;
|
||||
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path)?;
|
||||
//! Ok::<_, Error>(())
|
||||
//! })?;
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! # let seed = b"funky accordion noises";
|
||||
//! # keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
//! # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
||||
//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
|
||||
//! let mut client = Client::discover_socket().unwrap();
|
||||
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
|
||||
//! # keyforkd::test_util::Infallible::Ok(())
|
||||
//! # }).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! If you would rather write tests to panic rather than error, or would rather not deal with error
|
||||
//! types, the Panicable type should be used, which will handle the Error type for the closure.
|
||||
//! In tests, the Keyforkd test_util module and TestPrivateKeys can be used.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::str::FromStr;
|
||||
|
@ -163,10 +52,11 @@
|
|||
//!
|
||||
//! let seed = b"funky accordion noises";
|
||||
//! keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
//! std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
||||
//! let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
|
||||
//! let mut client = Client::discover_socket().unwrap();
|
||||
//! let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
|
||||
//! keyforkd::test_util::Panicable::Ok(())
|
||||
//! keyforkd::test_util::Infallible::Ok(())
|
||||
//! }).unwrap();
|
||||
//! ```
|
||||
|
||||
|
@ -275,9 +165,10 @@ impl Client {
|
|||
///
|
||||
/// # let seed = b"funky accordion noises";
|
||||
/// # keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
/// let mut socket = get_socket()?;
|
||||
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
||||
/// let mut socket = get_socket().unwrap();
|
||||
/// let mut client = Client::new(socket);
|
||||
/// # Ok::<_, keyforkd_client::Error>(())
|
||||
/// # keyforkd::test_util::Infallible::Ok(())
|
||||
/// # }).unwrap();
|
||||
/// ```
|
||||
pub fn new(socket: UnixStream) -> Self {
|
||||
|
@ -296,8 +187,9 @@ impl Client {
|
|||
///
|
||||
/// # let seed = b"funky accordion noises";
|
||||
/// # keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
/// let mut client = Client::discover_socket()?;
|
||||
/// # Ok::<_, keyforkd_client::Error>(())
|
||||
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
||||
/// let mut client = Client::discover_socket().unwrap();
|
||||
/// # keyforkd::test_util::Infallible::Ok(())
|
||||
/// # }).unwrap();
|
||||
/// ```
|
||||
pub fn discover_socket() -> Result<Self> {
|
||||
|
@ -325,10 +217,11 @@ impl Client {
|
|||
///
|
||||
/// # let seed = b"funky accordion noises";
|
||||
/// # keyforkd::test_util::run_test(seed, |socket_path| {
|
||||
/// # std::env::set_var("KEYFORKD_SOCKET_PATH", socket_path);
|
||||
/// let derivation_path = DerivationPath::from_str("m/44'/0'").unwrap();
|
||||
/// let mut client = Client::discover_socket().unwrap();
|
||||
/// let xprv = client.request_xprv::<PrivateKey>(&derivation_path).unwrap();
|
||||
/// # keyforkd::test_util::Panicable::Ok(())
|
||||
/// # keyforkd::test_util::Infallible::Ok(())
|
||||
/// # }).unwrap();
|
||||
/// ```
|
||||
pub fn request_xprv<K>(&mut self, path: &DerivationPath) -> Result<ExtendedPrivateKey<K>>
|
||||
|
@ -351,20 +244,15 @@ impl Client {
|
|||
_ => Err(Error::InvalidResponse),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Serialize and send a [`Request`] to the server, awaiting a [`Result<Response>`].
|
||||
///
|
||||
/// This function does not properly assert the association between a request type and a
|
||||
/// response type, and does not perform any serialization of native objects into Request or
|
||||
/// Response types, and should only be used when absolutely necessary.
|
||||
///
|
||||
/// # Errors
|
||||
/// An error may be returned if:
|
||||
/// * Reading or writing from or to the socket encountered an error.
|
||||
/// * Bincode could not serialize the request or deserialize the response.
|
||||
/// * An error occurred in Keyforkd.
|
||||
#[doc(hidden)]
|
||||
pub fn request(&mut self, req: &Request) -> Result<Response> {
|
||||
try_encode_to(&bincode::serialize(&req)?, &mut self.socket)?;
|
||||
let resp = try_decode_from(&mut self.socket)?;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::Client;
|
||||
use keyfork_derive_util::{request::*, DerivationPath};
|
||||
use keyfork_slip10_test_data::test_data;
|
||||
use keyforkd::test_util::{run_test, Panicable};
|
||||
use keyforkd::test_util::{run_test, Infallible};
|
||||
use std::{os::unix::net::UnixStream, str::FromStr};
|
||||
|
||||
#[test]
|
||||
|
@ -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;
|
||||
|
@ -109,7 +109,7 @@ fn ed25519_test_suite() {
|
|||
DerivationResponse::try_from(client.request(&req.into()).unwrap()).unwrap();
|
||||
assert_eq!(&response.data, test.private_key.as_slice());
|
||||
}
|
||||
Panicable::Ok(())
|
||||
Infallible::Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
[package]
|
||||
name = "keyforkd-models"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
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 }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-derive-util = { version = "0.1.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.2"
|
||||
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.1.0", path = "../../derive/keyfork-derive-util", registry = "distrust" }
|
||||
keyfork-frame = { version = "0.1.0", path = "../../util/keyfork-frame", features = ["async"], registry = "distrust" }
|
||||
keyfork-mnemonic-util = { version = "0.2.0", path = "../../util/keyfork-mnemonic-util", registry = "distrust" }
|
||||
keyfork-derive-path-data = { version = "0.1.0", path = "../../derive/keyfork-derive-path-data", registry = "distrust" }
|
||||
keyforkd-models = { version = "0.1.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" }
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub use keyfork_mnemonic::Mnemonic;
|
||||
pub use keyfork_mnemonic_util::Mnemonic;
|
||||
pub use tower::ServiceBuilder;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
|
@ -57,7 +57,7 @@ pub async fn start_and_run_server_on(
|
|||
let service = ServiceBuilder::new()
|
||||
.layer(middleware::BincodeLayer::new())
|
||||
// TODO: passphrase support and/or store passphrase with mnemonic
|
||||
.service(Keyforkd::new(mnemonic.generate_seed(None).to_vec()));
|
||||
.service(Keyforkd::new(mnemonic.generate_seed(None)));
|
||||
|
||||
let mut server = match UnixServer::bind(socket_path) {
|
||||
Ok(s) => s,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Launch the Keyfork Server from using a mnemonic passed through standard input.
|
||||
//!
|
||||
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
use keyfork_mnemonic_util::Mnemonic;
|
||||
|
||||
use tokio::io::{self, AsyncBufReadExt, BufReader};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -12,21 +12,20 @@ use keyfork_bug::bug;
|
|||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("This error can never be instantiated")]
|
||||
#[doc(hidden)]
|
||||
pub enum UninstantiableError {}
|
||||
pub struct InfallibleError {
|
||||
protected: (),
|
||||
}
|
||||
|
||||
/// A panicable result. This type can be used when a closure chooses to panic instead of
|
||||
/// returning an error. This doesn't necessarily mean a closure _has_ to panic, and its absence
|
||||
/// doesn't imply a closure _can't_ panic, but this is a useful utility function for writing tests,
|
||||
/// to avoid the necessity of making custom error types.
|
||||
/// An infallible result. This type can be used to represent a function that should never error.
|
||||
///
|
||||
/// ```rust
|
||||
/// use keyforkd::test_util::Panicable;
|
||||
/// use keyforkd::test_util::Infallible;
|
||||
/// let closure = || {
|
||||
/// Panicable::Ok(())
|
||||
/// Infallible::Ok(())
|
||||
/// };
|
||||
/// assert!(closure().is_ok());
|
||||
/// ```
|
||||
pub type Panicable<T> = std::result::Result<T, UninstantiableError>;
|
||||
pub type Infallible<T> = std::result::Result<T, InfallibleError>;
|
||||
|
||||
/// 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
|
||||
|
@ -40,8 +39,6 @@ pub type Panicable<T> = std::result::Result<T, UninstantiableError>;
|
|||
/// runtime.
|
||||
///
|
||||
/// # Examples
|
||||
/// The test utility provides a socket that can be connected to for deriving keys.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::os::unix::net::UnixStream;
|
||||
/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
|
@ -49,18 +46,6 @@ pub type Panicable<T> = std::result::Result<T, UninstantiableError>;
|
|||
/// UnixStream::connect(&path).map(|_| ())
|
||||
/// }).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// The `keyforkd-client` crate uses the `KEYFORKD_SOCKET_PATH` variable to determine the default
|
||||
/// socket path. The test will export the environment variable so it may be used by default.
|
||||
///
|
||||
/// ```rust
|
||||
/// use std::os::unix::net::UnixStream;
|
||||
/// let seed = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
/// keyforkd::test_util::run_test(seed.as_slice(), |path| {
|
||||
/// assert_eq!(std::env::var_os("KEYFORKD_SOCKET_PATH").unwrap(), path.as_os_str());
|
||||
/// UnixStream::connect(&path).map(|_| ())
|
||||
/// }).unwrap();
|
||||
/// ```
|
||||
#[allow(clippy::missing_errors_doc)]
|
||||
pub fn run_test<F, E>(seed: &[u8], closure: F) -> Result<(), E>
|
||||
where
|
||||
|
@ -97,7 +82,6 @@ where
|
|||
rx.recv()
|
||||
.await
|
||||
.expect(bug!("can't receive server start signal from channel"));
|
||||
std::env::set_var("KEYFORKD_SOCKET_PATH", &socket_path);
|
||||
let test_handle = tokio::task::spawn_blocking(move || closure(&socket_path));
|
||||
|
||||
let result = test_handle.await;
|
||||
|
@ -105,8 +89,8 @@ where
|
|||
result
|
||||
});
|
||||
if let Err(e) = result {
|
||||
if let Ok(reason) = e.try_into_panic() {
|
||||
std::panic::resume_unwind(reason);
|
||||
if e.is_panic() {
|
||||
std::panic::resume_unwind(e.into_panic());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -119,6 +103,6 @@ mod tests {
|
|||
#[test]
|
||||
fn test_run_test() {
|
||||
let seed = b"beefbeef";
|
||||
run_test(seed, |_path| Panicable::Ok(())).expect("infallible");
|
||||
run_test(seed, |_path| Infallible::Ok(())).expect("infallible");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.1"
|
||||
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 }
|
||||
keyforkd-client = { workspace = true }
|
||||
smex = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", registry = "distrust" }
|
||||
keyforkd-client = { version = "0.1.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.3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -10,10 +10,9 @@ 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.1.0", path = "../keyfork-derive-util", default-features = false, features = ["ed25519"], registry = "distrust" }
|
||||
keyforkd-client = { version = "0.1.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"
|
||||
|
|
|
@ -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,9 +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_path_data::paths;
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyforkd_client::Client;
|
||||
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
@ -79,14 +78,16 @@ fn validate(
|
|||
subkey_format: &str,
|
||||
default_userid: &str,
|
||||
) -> Result<(DerivationPath, Vec<KeyType>, UserID), Box<dyn std::error::Error>> {
|
||||
let index = paths::OPENPGP.inner().first().unwrap();
|
||||
let mut pgp_u32 = [0u8; 4];
|
||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
||||
let index = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
|
||||
|
||||
let path = DerivationPath::from_str(path)?;
|
||||
assert!(path.len() >= 2, "Expected path of at least m/{index}/account_id'");
|
||||
assert_eq!(2, path.len(), "Expected path of m/{index}/account_id'");
|
||||
|
||||
let given_index = path.iter().next().expect("checked .len() above");
|
||||
assert_eq!(
|
||||
index, given_index,
|
||||
&index, given_index,
|
||||
"Expected derivation path starting with m/{index}, got: {given_index}",
|
||||
);
|
||||
|
||||
|
@ -121,7 +122,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||
|
||||
for packet in cert.into_packets2() {
|
||||
for packet in cert.into_packets() {
|
||||
packet.serialize(&mut w)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
[package]
|
||||
name = "keyfork-derive-path-data"
|
||||
version = "0.1.2"
|
||||
version = "0.1.0"
|
||||
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 }
|
||||
once_cell = "1.19.0"
|
||||
keyfork-derive-util = { version = "0.1.0", path = "../keyfork-derive-util", default-features = false, registry = "distrust" }
|
||||
|
|
|
@ -2,129 +2,32 @@
|
|||
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
|
||||
/// All common paths for key derivation.
|
||||
pub mod paths {
|
||||
use super::*;
|
||||
|
||||
/// The default derivation path for OpenPGP.
|
||||
pub static OPENPGP: Lazy<DerivationPath> = Lazy::new(|| {
|
||||
DerivationPath::default().chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00pgp"),
|
||||
true,
|
||||
))
|
||||
});
|
||||
|
||||
/// The derivation path for OpenPGP certificates used for sharding.
|
||||
pub static OPENPGP_SHARD: Lazy<DerivationPath> = Lazy::new(|| {
|
||||
DerivationPath::default()
|
||||
.chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00pgp"),
|
||||
true,
|
||||
))
|
||||
.chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"shrd"),
|
||||
true,
|
||||
))
|
||||
});
|
||||
|
||||
/// The derivation path for OpenPGP certificates used for disaster recovery.
|
||||
pub static OPENPGP_DISASTER_RECOVERY: Lazy<DerivationPath> = Lazy::new(|| {
|
||||
DerivationPath::default()
|
||||
.chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00pgp"),
|
||||
true,
|
||||
))
|
||||
.chain_push(DerivationIndex::new_unchecked(
|
||||
u32::from_be_bytes(*b"\x00\x00dr"),
|
||||
true,
|
||||
))
|
||||
});
|
||||
}
|
||||
|
||||
/// Determine if a prefix matches and whether the next index exists.
|
||||
fn prefix_matches(given: &DerivationPath, target: &DerivationPath) -> Option<DerivationIndex> {
|
||||
if given.len() <= target.len() {
|
||||
return None;
|
||||
}
|
||||
if target
|
||||
.iter()
|
||||
.zip(given.iter())
|
||||
.all(|(left, right)| left == right)
|
||||
{
|
||||
given.iter().nth(target.len()).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
/// The default derivation path for OpenPGP.
|
||||
pub static OPENPGP: DerivationIndex = DerivationIndex::new_unchecked(7366512, true);
|
||||
|
||||
/// A derivation target.
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Target {
|
||||
/// An OpenPGP key, whose account is the given index.
|
||||
OpenPGP(DerivationIndex),
|
||||
|
||||
/// An OpenPGP key used for sharding.
|
||||
OpenPGPShard(DerivationIndex),
|
||||
|
||||
/// An OpenPGP key used for disaster recovery.
|
||||
OpenPGPDisasterRecovery(DerivationIndex),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Target {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Target::OpenPGP(account) => {
|
||||
Self::OpenPGP(account) => {
|
||||
write!(f, "OpenPGP key (account {account})")
|
||||
}
|
||||
Target::OpenPGPShard(shard_index) => {
|
||||
write!(f, "OpenPGP Shard key (shard index {shard_index})")
|
||||
}
|
||||
Target::OpenPGPDisasterRecovery(account) => {
|
||||
write!(f, "OpenPGP Disaster Recovery key (account {account})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! test_match {
|
||||
($var:ident, $shard:path, $target:path) => {
|
||||
if let Some(index) = prefix_matches($var, &$shard) {
|
||||
return Some($target(index));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Determine the closest [`Target`] for the given path. This method is intended to be used by
|
||||
/// `keyforkd` to provide an optional textual prompt to what a client is attempting to derive.
|
||||
pub fn guess_target(path: &DerivationPath) -> Option<Target> {
|
||||
test_match!(path, paths::OPENPGP_SHARD, Target::OpenPGPShard);
|
||||
test_match!(
|
||||
path,
|
||||
paths::OPENPGP_DISASTER_RECOVERY,
|
||||
Target::OpenPGPDisasterRecovery
|
||||
);
|
||||
test_match!(path, paths::OPENPGP, Target::OpenPGP);
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let index = DerivationIndex::new(5312, false).unwrap();
|
||||
let dr_key = paths::OPENPGP_DISASTER_RECOVERY
|
||||
.clone()
|
||||
.chain_push(index.clone());
|
||||
match guess_target(&dr_key) {
|
||||
Some(Target::OpenPGPDisasterRecovery(idx)) if idx == index => (),
|
||||
bad => panic!("invalid value: {bad:?}"),
|
||||
}
|
||||
}
|
||||
Some(match path.iter().collect::<Vec<_>>()[..] {
|
||||
[t, index] if t == &OPENPGP => Target::OpenPGP(index.clone()),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-derive-util"
|
||||
version = "0.2.1"
|
||||
version = "0.1.1"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
|
@ -12,25 +12,25 @@ secp256k1 = ["k256"]
|
|||
ed25519 = ["ed25519-dalek"]
|
||||
|
||||
[dependencies]
|
||||
keyfork-mnemonic = { workspace = true }
|
||||
keyfork-bug = { workspace = true }
|
||||
keyfork-mnemonic-util = { version = "0.2.0", path = "../../util/keyfork-mnemonic-util", 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" }
|
||||
|
|
|
@ -23,7 +23,7 @@ performed directly on a master seed. This is how Keyforkd works internally.
|
|||
|
||||
```rust
|
||||
use std::str::FromStr;
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
use keyfork_mnemonic_util::Mnemonic;
|
||||
use keyfork_derive_util::{*, request::*};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
//! # Examples
|
||||
//! ```rust
|
||||
//! use std::str::FromStr;
|
||||
//! use keyfork_mnemonic::Mnemonic;
|
||||
//! use keyfork_mnemonic_util::Mnemonic;
|
||||
//! use keyfork_derive_util::{*, request::*};
|
||||
//! use k256::SecretKey;
|
||||
//!
|
||||
|
@ -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};
|
||||
|
|
|
@ -24,7 +24,7 @@ use crate::{
|
|||
DerivationPath, ExtendedPrivateKey,
|
||||
};
|
||||
|
||||
use keyfork_mnemonic::{Mnemonic, MnemonicGenerationError};
|
||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicGenerationError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// An error encountered while deriving a key.
|
||||
|
@ -194,8 +194,8 @@ impl DerivationRequest {
|
|||
/// # private_key::TestPrivateKey as PrivateKey,
|
||||
/// # };
|
||||
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let mnemonic: keyfork_mnemonic::Mnemonic = //
|
||||
/// # keyfork_mnemonic::Mnemonic::from_entropy(
|
||||
/// let mnemonic: keyfork_mnemonic_util::Mnemonic = //
|
||||
/// # keyfork_mnemonic_util::Mnemonic::from_entropy(
|
||||
/// # b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
|
||||
/// # )?;
|
||||
/// let algo: DerivationAlgorithm = //
|
||||
|
|
|
@ -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.2.3"
|
||||
version = "0.2.0"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -14,27 +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 }
|
||||
keyfork-bug = { version = "0.1.0", path = "../util/keyfork-bug", registry = "distrust" }
|
||||
keyfork-prompt = { version = "0.1.0", path = "../util/keyfork-prompt", default-features = false, features = ["mnemonic"], registry = "distrust" }
|
||||
keyfork-qrcode = { version = "0.1.0", 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 = { workspace = true }
|
||||
thiserror = "1.0.50"
|
||||
|
||||
# Remote operator mode
|
||||
keyfork-mnemonic = { workspace = true }
|
||||
keyfork-mnemonic-util = { version = "0.2.0", path = "../util/keyfork-mnemonic-util", 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 }
|
||||
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,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Decrypt a single OpenPGP shard and encapsulate it for remote transport.
|
||||
//!
|
||||
|
||||
use std::{
|
||||
env,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Combine OpenPGP shards using remote transport and output the hex-encoded secret.
|
||||
//!
|
||||
|
||||
use std::{
|
||||
env,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! Split a hex-encoded secret into OpenPGP shards
|
||||
//!
|
||||
|
||||
use std::{env, path::PathBuf, process::ExitCode, str::FromStr};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use aes_gcm::{
|
|||
use base64::prelude::{Engine, BASE64_STANDARD};
|
||||
use hkdf::Hkdf;
|
||||
use keyfork_bug::{bug, POISONED_MUTEX};
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_mnemonic_util::{English, Mnemonic};
|
||||
use keyfork_prompt::{
|
||||
validators::{
|
||||
mnemonic::{MnemonicSetValidator, MnemonicValidator, WordLength},
|
||||
|
@ -249,7 +249,7 @@ pub trait Format {
|
|||
|
||||
// create our shared key
|
||||
let our_key = EphemeralSecret::random();
|
||||
let our_pubkey_mnemonic = Mnemonic::try_from_slice(PublicKey::from(&our_key).as_bytes())?;
|
||||
let our_pubkey_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
|
||||
let shared_secret = our_key.diffie_hellman(&PublicKey::from(their_pubkey));
|
||||
assert!(
|
||||
shared_secret.was_contributory(),
|
||||
|
@ -302,7 +302,7 @@ pub trait Format {
|
|||
let mut mnemonic_bytes = [0u8; ENCRYPTED_LENGTH as usize];
|
||||
mnemonic_bytes.copy_from_slice(&encrypted_bytes);
|
||||
|
||||
let payload_mnemonic = Mnemonic::from_array(mnemonic_bytes);
|
||||
let payload_mnemonic = Mnemonic::from_nonstandard_bytes(mnemonic_bytes);
|
||||
|
||||
#[cfg(feature = "qrcode")]
|
||||
{
|
||||
|
@ -439,7 +439,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
while iter_count.is_none() || iter_count.is_some_and(|i| i > 0) {
|
||||
iter += 1;
|
||||
let our_key = EphemeralSecret::random();
|
||||
let key_mnemonic = Mnemonic::try_from_slice(PublicKey::from(&our_key).as_bytes())?;
|
||||
let key_mnemonic = Mnemonic::from_bytes(PublicKey::from(&our_key).as_bytes())?;
|
||||
|
||||
#[cfg(feature = "qrcode")]
|
||||
{
|
||||
|
@ -483,8 +483,7 @@ pub fn remote_decrypt(w: &mut impl Write) -> Result<(), Box<dyn std::error::Erro
|
|||
.expect(bug!("qrcode should contain base64 encoded data"));
|
||||
assert_eq!(
|
||||
decoded_data.len(),
|
||||
// Include length of public key
|
||||
ENCRYPTED_LENGTH as usize + 32,
|
||||
ENCRYPTED_LENGTH as usize,
|
||||
bug!("invalid payload data")
|
||||
);
|
||||
let _ = pubkey_data.insert(decoded_data[..32].try_into().map_err(|_| InvalidData)?);
|
||||
|
|
|
@ -25,7 +25,7 @@ use openpgp::{
|
|||
stream::{DecryptionHelper, DecryptorBuilder, VerificationHelper},
|
||||
Parse,
|
||||
},
|
||||
policy::{NullPolicy, StandardPolicy, Policy},
|
||||
policy::{NullPolicy, Policy, StandardPolicy},
|
||||
serialize::{
|
||||
stream::{ArbitraryWriter, Encryptor2, LiteralWriter, Message, Recipient, Signer},
|
||||
Marshal,
|
||||
|
@ -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,7 +181,7 @@ impl EncryptedMessage {
|
|||
}
|
||||
}
|
||||
|
||||
/// Encoding and decoding shards using OpenPGP.
|
||||
///
|
||||
pub struct OpenPGP<P: PromptHandler> {
|
||||
p: PhantomData<P>,
|
||||
}
|
||||
|
@ -243,13 +239,6 @@ impl<P: PromptHandler> OpenPGP<P> {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
@ -588,8 +577,7 @@ fn get_encryption_keys<'a>(
|
|||
openpgp::packet::key::UnspecifiedRole,
|
||||
> {
|
||||
cert.keys()
|
||||
// NOTE: this causes complications on Airgap systems
|
||||
// .alive()
|
||||
.alive()
|
||||
.revoked(false)
|
||||
.supported()
|
||||
.for_storage_encryption()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork"
|
||||
version = "0.2.4"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-only"
|
||||
|
||||
|
@ -23,25 +23,24 @@ 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.1.0", path = "../daemon/keyforkd-client", default-features = false, features = ["ed25519"], registry = "distrust" }
|
||||
keyfork-derive-openpgp = { version = "0.1.0", path = "../derive/keyfork-derive-openpgp", registry = "distrust" }
|
||||
keyfork-derive-util = { version = "0.1.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-util = { version = "0.2.0", path = "../util/keyfork-mnemonic-util", 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 }
|
||||
sequoia-openpgp = { version = "1.17.0", default-features = false, features = ["compression"] }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::Keyfork;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
use keyfork_derive_openpgp::{
|
||||
openpgp::{
|
||||
|
@ -10,8 +10,7 @@ use keyfork_derive_openpgp::{
|
|||
},
|
||||
XPrvKey,
|
||||
};
|
||||
use keyfork_derive_util::DerivationIndex;
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyforkd_client::Client;
|
||||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
@ -28,46 +27,45 @@ 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(Args, Clone, Debug)]
|
||||
pub struct OpenPGP {
|
||||
/// Default User ID for the certificate, using the OpenPGP User ID format.
|
||||
user_id: String,
|
||||
OpenPGP {
|
||||
/// Default User ID for the certificate, using the OpenPGP User ID format.
|
||||
user_id: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl DeriveSubcommands {
|
||||
fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||
match self {
|
||||
DeriveSubcommands::OpenPGP(opgp) => opgp.handle(account),
|
||||
}
|
||||
}
|
||||
}
|
||||
DeriveSubcommands::OpenPGP { user_id } => {
|
||||
let mut pgp_u32 = [0u8; 4];
|
||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
||||
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
|
||||
let path = DerivationPath::default()
|
||||
.chain_push(chain)
|
||||
.chain_push(account);
|
||||
// TODO: should this be customizable?
|
||||
let subkeys = vec![
|
||||
KeyFlags::empty().set_certification(),
|
||||
KeyFlags::empty().set_signing(),
|
||||
KeyFlags::empty()
|
||||
.set_transport_encryption()
|
||||
.set_storage_encryption(),
|
||||
KeyFlags::empty().set_authentication(),
|
||||
];
|
||||
let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
|
||||
let default_userid = UserID::from(user_id.as_str());
|
||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
|
||||
|
||||
impl OpenPGP {
|
||||
pub fn handle(&self, account: DerivationIndex) -> Result<()> {
|
||||
let path = paths::OPENPGP.clone().chain_push(account);
|
||||
// TODO: should this be customizable?
|
||||
let subkeys = vec![
|
||||
KeyFlags::empty().set_certification(),
|
||||
KeyFlags::empty().set_signing(),
|
||||
KeyFlags::empty()
|
||||
.set_transport_encryption()
|
||||
.set_storage_encryption(),
|
||||
KeyFlags::empty().set_authentication(),
|
||||
];
|
||||
let xprv = Client::discover_socket()?.request_xprv::<XPrvKey>(&path)?;
|
||||
let default_userid = UserID::from(self.user_id.as_str());
|
||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &default_userid)?;
|
||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||
|
||||
let mut w = Writer::new(std::io::stdout(), Kind::SecretKey)?;
|
||||
for packet in cert.into_packets() {
|
||||
packet.serialize(&mut w)?;
|
||||
}
|
||||
|
||||
for packet in cert.into_packets2() {
|
||||
packet.serialize(&mut w)?;
|
||||
w.finalize()?;
|
||||
}
|
||||
}
|
||||
|
||||
w.finalize()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ impl MnemonicSeedSource {
|
|||
MnemonicSeedSource::Tarot => todo!(),
|
||||
MnemonicSeedSource::Dice => todo!(),
|
||||
};
|
||||
let mnemonic = keyfork_mnemonic::Mnemonic::try_from_slice(&seed)?;
|
||||
let mnemonic = keyfork_mnemonic_util::Mnemonic::from_bytes(&seed)?;
|
||||
Ok(mnemonic.to_string())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use super::Keyfork;
|
|||
use clap::{Parser, Subcommand};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use keyfork_mnemonic::{English, Mnemonic};
|
||||
use keyfork_mnemonic_util::{English, Mnemonic};
|
||||
use keyfork_prompt::{default_terminal, DefaultTerminal};
|
||||
use keyfork_shard::{remote_decrypt, Format};
|
||||
|
||||
|
@ -85,7 +85,7 @@ pub struct Recover {
|
|||
impl Recover {
|
||||
pub fn handle(&self, _k: &Keyfork) -> Result<()> {
|
||||
let seed = self.command.handle()?;
|
||||
let mnemonic = Mnemonic::try_from_slice(&seed)?;
|
||||
let mnemonic = Mnemonic::from_bytes(&seed)?;
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
|
|
|
@ -1,24 +1,15 @@
|
|||
use super::Keyfork;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use clap::{Parser, Subcommand};
|
||||
use std::{collections::HashSet, fs::File, io::IsTerminal, path::PathBuf};
|
||||
|
||||
use card_backend_pcsc::PcscBackend;
|
||||
use openpgp_card_sequoia::{state::Open, types::KeyType, Card};
|
||||
|
||||
use keyfork_derive_openpgp::{
|
||||
openpgp::{
|
||||
self,
|
||||
armor::{Kind, Writer},
|
||||
packet::UserID,
|
||||
serialize::Marshal,
|
||||
types::KeyFlags,
|
||||
Cert,
|
||||
},
|
||||
openpgp::{self, packet::UserID, types::KeyFlags, Cert},
|
||||
XPrv,
|
||||
};
|
||||
use keyfork_derive_path_data::paths;
|
||||
use keyfork_derive_util::DerivationIndex;
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
use keyfork_derive_util::{DerivationIndex, DerivationPath};
|
||||
use keyfork_prompt::{
|
||||
default_terminal,
|
||||
validators::{SecurePinValidator, Validator},
|
||||
|
@ -33,8 +24,6 @@ pub struct PinLength(usize);
|
|||
|
||||
type Result<T, E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
|
||||
|
||||
// TODO: refactor to use mnemonic derived seed instead of 256 bit entropy to allow for possible
|
||||
// recovery in the future.
|
||||
fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
|
||||
let subkeys = vec![
|
||||
KeyFlags::empty().set_certification(),
|
||||
|
@ -45,8 +34,17 @@ fn derive_key(seed: [u8; 32], index: u8) -> Result<Cert> {
|
|||
KeyFlags::empty().set_authentication(),
|
||||
];
|
||||
|
||||
let mut pgp_u32 = [0u8; 4];
|
||||
pgp_u32[1..].copy_from_slice(&"pgp".bytes().collect::<Vec<u8>>());
|
||||
let chain = DerivationIndex::new(u32::from_be_bytes(pgp_u32), true)?;
|
||||
let mut shrd_u32 = [0u8; 4];
|
||||
shrd_u32[..].copy_from_slice(&"shrd".bytes().collect::<Vec<u8>>());
|
||||
let account = DerivationIndex::new(u32::from_be_bytes(shrd_u32), true)?;
|
||||
let subkey = DerivationIndex::new(u32::from(index), true)?;
|
||||
let path = paths::OPENPGP_SHARD.clone().chain_push(subkey);
|
||||
let path = DerivationPath::default()
|
||||
.chain_push(chain)
|
||||
.chain_push(account)
|
||||
.chain_push(subkey);
|
||||
let xprv = XPrv::new(seed)
|
||||
.expect("could not construct master key from seed")
|
||||
.derive_path(&path)?;
|
||||
|
@ -103,200 +101,129 @@ fn factory_reset_current_card(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_shard_secret(
|
||||
threshold: u8,
|
||||
max: u8,
|
||||
keys_per_shard: u8,
|
||||
output_file: &Option<PathBuf>,
|
||||
) -> Result<()> {
|
||||
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||
let mut pm = default_terminal()?;
|
||||
let mut certs = vec![];
|
||||
let mut seen_cards: HashSet<String> = HashSet::new();
|
||||
let stdout = std::io::stdout();
|
||||
if output_file.is_none() {
|
||||
assert!(
|
||||
!stdout.is_terminal(),
|
||||
"not printing shard to terminal, redirect output"
|
||||
);
|
||||
}
|
||||
|
||||
let user_pin_validator = SecurePinValidator {
|
||||
min_length: Some(6),
|
||||
..Default::default()
|
||||
}
|
||||
.to_fn();
|
||||
let admin_pin_validator = SecurePinValidator {
|
||||
min_length: Some(8),
|
||||
..Default::default()
|
||||
}
|
||||
.to_fn();
|
||||
|
||||
for index in 0..max {
|
||||
let cert = derive_key(seed, index)?;
|
||||
for i in 0..keys_per_shard {
|
||||
pm.prompt_message(Message::Text(format!(
|
||||
"Please remove all keys and insert key #{} for user #{}",
|
||||
(i as u16) + 1,
|
||||
(index as u16) + 1,
|
||||
)))?;
|
||||
let card_backend = loop {
|
||||
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
||||
break c;
|
||||
}
|
||||
pm.prompt_message(Message::Text(
|
||||
"No smart card was found. Please plug in a smart card and press enter"
|
||||
.to_string(),
|
||||
))?;
|
||||
};
|
||||
let user_pin = pm.prompt_validated_passphrase(
|
||||
"Please enter the new smartcard User PIN: ",
|
||||
3,
|
||||
&user_pin_validator,
|
||||
)?;
|
||||
let admin_pin = pm.prompt_validated_passphrase(
|
||||
"Please enter the new smartcard Admin PIN: ",
|
||||
3,
|
||||
&admin_pin_validator,
|
||||
)?;
|
||||
factory_reset_current_card(
|
||||
&mut seen_cards,
|
||||
user_pin.trim(),
|
||||
admin_pin.trim(),
|
||||
&cert,
|
||||
card_backend,
|
||||
)?;
|
||||
}
|
||||
certs.push(cert);
|
||||
}
|
||||
|
||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
||||
|
||||
if let Some(output_file) = output_file {
|
||||
let output = File::create(output_file)?;
|
||||
opgp.shard_and_encrypt(threshold, certs.len() as u8, &seed, &certs[..], output)?;
|
||||
} else {
|
||||
opgp.shard_and_encrypt(
|
||||
threshold,
|
||||
certs.len() as u8,
|
||||
&seed,
|
||||
&certs[..],
|
||||
std::io::stdout(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
pub enum WizardSubcommands {
|
||||
GenerateShardSecret(GenerateShardSecret),
|
||||
BottomsUp(BottomsUp),
|
||||
}
|
||||
/// Create a 256 bit secret and shard the secret to smart cards.
|
||||
///
|
||||
/// Smart cards will need to be plugged in periodically during the wizard, where they will be factory reset and
|
||||
/// provisioned to `m/pgp'/shrd'/<share index>`. The secret can then be recovered with `keyfork recover shard` or
|
||||
/// `keyfork recover remote-shard`. The share file will be printed to standard output.
|
||||
GenerateShardSecret {
|
||||
/// The minimum amount of keys required to decrypt the secret.
|
||||
#[arg(long)]
|
||||
threshold: u8,
|
||||
|
||||
/// Create a 256 bit secret and shard the secret to smart cards.
|
||||
///
|
||||
/// Smart cards will need to be plugged in periodically during the wizard, where they will be
|
||||
/// factory reset and provisioned to `m/pgp'/shrd'/<share index>`. The secret can then be recovered
|
||||
/// with `keyfork recover shard` or `keyfork recover remote-shard`. The share file will be printed
|
||||
/// to standard output.
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct GenerateShardSecret {
|
||||
/// The minimum amount of keys required to decrypt the secret.
|
||||
#[arg(long)]
|
||||
threshold: u8,
|
||||
/// The maximum amount of shards.
|
||||
#[arg(long)]
|
||||
max: u8,
|
||||
|
||||
/// The maximum amount of shards.
|
||||
#[arg(long)]
|
||||
max: u8,
|
||||
/// The amount of smart cards to provision per-shard.
|
||||
#[arg(long, default_value = "1")]
|
||||
keys_per_shard: u8,
|
||||
|
||||
/// The amount of smart cards to provision per-shard.
|
||||
#[arg(long, default_value = "1")]
|
||||
keys_per_shard: u8,
|
||||
|
||||
/// The file to write the generated shard file to.
|
||||
#[arg(long)]
|
||||
output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Create a 256 bit secret and shard the secret to previously known OpenPGP certificates,
|
||||
/// deriving the default OpenPGP certificate for the secret.
|
||||
///
|
||||
/// This command was purpose-built for DEFCON and is not intended to be used normally, as it
|
||||
/// implies keys used for sharding have been generated by a custom source.
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct BottomsUp {
|
||||
/// The location of OpenPGP certificates to use when sharding.
|
||||
key_discovery: PathBuf,
|
||||
|
||||
/// The minimum amount of keys required to decrypt the secret.
|
||||
#[arg(long)]
|
||||
threshold: u8,
|
||||
|
||||
/// The file to write the generated shard file to.
|
||||
#[arg(long)]
|
||||
output_shardfile: PathBuf,
|
||||
|
||||
/// The file to write the generated OpenPGP certificate to.
|
||||
#[arg(long)]
|
||||
output_cert: PathBuf,
|
||||
|
||||
/// The User ID for the generated OpenPGP certificate.
|
||||
#[arg(long, default_value = "Disaster Recovery")]
|
||||
user_id: String,
|
||||
/// The file to write the generated shard file to.
|
||||
#[arg(long)]
|
||||
output: Option<PathBuf>,
|
||||
},
|
||||
}
|
||||
|
||||
impl WizardSubcommands {
|
||||
// dispatch
|
||||
fn handle(&self) -> Result<()> {
|
||||
match self {
|
||||
WizardSubcommands::GenerateShardSecret(gss) => gss.handle(),
|
||||
WizardSubcommands::BottomsUp(bu) => bu.handle(),
|
||||
WizardSubcommands::GenerateShardSecret {
|
||||
threshold,
|
||||
max,
|
||||
keys_per_shard,
|
||||
output,
|
||||
} => generate_shard_secret(*threshold, *max, *keys_per_shard, output),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GenerateShardSecret {
|
||||
fn handle(&self) -> Result<()> {
|
||||
let seed = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||
let mut pm = default_terminal()?;
|
||||
let mut certs = vec![];
|
||||
let mut seen_cards: HashSet<String> = HashSet::new();
|
||||
let stdout = std::io::stdout();
|
||||
if self.output.is_none() {
|
||||
assert!(
|
||||
!stdout.is_terminal(),
|
||||
"not printing shard to terminal, redirect output"
|
||||
);
|
||||
}
|
||||
|
||||
let user_pin_validator = SecurePinValidator {
|
||||
min_length: Some(6),
|
||||
..Default::default()
|
||||
}
|
||||
.to_fn();
|
||||
let admin_pin_validator = SecurePinValidator {
|
||||
min_length: Some(8),
|
||||
..Default::default()
|
||||
}
|
||||
.to_fn();
|
||||
|
||||
for index in 0..self.max {
|
||||
let cert = derive_key(seed, index)?;
|
||||
for i in 0..self.keys_per_shard {
|
||||
pm.prompt_message(Message::Text(format!(
|
||||
"Please remove all keys and insert key #{} for user #{}",
|
||||
(i as u16) + 1,
|
||||
(index as u16) + 1,
|
||||
)))?;
|
||||
let card_backend = loop {
|
||||
if let Some(c) = PcscBackend::cards(None)?.next().transpose()? {
|
||||
break c;
|
||||
}
|
||||
pm.prompt_message(Message::Text(
|
||||
"No smart card was found. Please plug in a smart card and press enter"
|
||||
.to_string(),
|
||||
))?;
|
||||
};
|
||||
let user_pin = pm.prompt_validated_passphrase(
|
||||
"Please enter the new smartcard User PIN: ",
|
||||
3,
|
||||
&user_pin_validator,
|
||||
)?;
|
||||
let admin_pin = pm.prompt_validated_passphrase(
|
||||
"Please enter the new smartcard Admin PIN: ",
|
||||
3,
|
||||
&admin_pin_validator,
|
||||
)?;
|
||||
factory_reset_current_card(
|
||||
&mut seen_cards,
|
||||
user_pin.trim(),
|
||||
admin_pin.trim(),
|
||||
&cert,
|
||||
card_backend,
|
||||
)?;
|
||||
}
|
||||
certs.push(cert);
|
||||
}
|
||||
|
||||
let opgp = OpenPGP::<DefaultTerminal>::new();
|
||||
|
||||
if let Some(output_file) = self.output.as_ref() {
|
||||
let output = File::create(output_file)?;
|
||||
opgp.shard_and_encrypt(self.threshold, certs.len() as u8, &seed, &certs[..], output)?;
|
||||
} else {
|
||||
opgp.shard_and_encrypt(
|
||||
self.threshold,
|
||||
certs.len() as u8,
|
||||
&seed,
|
||||
&certs[..],
|
||||
std::io::stdout(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl BottomsUp {
|
||||
fn handle(&self) -> Result<()> {
|
||||
let entropy = keyfork_entropy::generate_entropy_of_const_size::<{ 256 / 8 }>()?;
|
||||
let mnemonic = Mnemonic::from_array(entropy);
|
||||
let seed = mnemonic.generate_seed(None);
|
||||
|
||||
// TODO: should this allow for customizing the account index from 0? Potential for key reuse
|
||||
// errors.
|
||||
let path = paths::OPENPGP_DISASTER_RECOVERY
|
||||
.clone()
|
||||
.chain_push(DerivationIndex::new(0, true)?);
|
||||
let subkeys = [
|
||||
KeyFlags::empty().set_certification(),
|
||||
KeyFlags::empty().set_signing(),
|
||||
KeyFlags::empty()
|
||||
.set_transport_encryption()
|
||||
.set_storage_encryption(),
|
||||
KeyFlags::empty().set_authentication(),
|
||||
];
|
||||
let xprv = XPrv::new(seed)
|
||||
.expect("could not construct master key from seed")
|
||||
.derive_path(&path)?;
|
||||
let userid = UserID::from(self.user_id.as_str());
|
||||
|
||||
let cert = keyfork_derive_openpgp::derive(xprv, &subkeys, &userid)?;
|
||||
let certfile = File::create(&self.output_cert)?;
|
||||
let mut w = Writer::new(certfile, Kind::PublicKey)?;
|
||||
cert.serialize(&mut w)?;
|
||||
w.finalize()?;
|
||||
|
||||
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(
|
||||
self.threshold,
|
||||
certs.len() as u8,
|
||||
&entropy,
|
||||
&certs[..],
|
||||
shardfile,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
pub struct Wizard {
|
||||
#[command(subcommand)]
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -9,4 +9,4 @@ license = "MIT"
|
|||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
anyhow = "1.0.79"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.0"
|
||||
name = "keyfork-mnemonic-util"
|
||||
version = "0.2.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,6 +1,6 @@
|
|||
//! Generate a mnemonic from hex-encoded input.
|
||||
//!
|
||||
|
||||
use keyfork_mnemonic::Mnemonic;
|
||||
use keyfork_mnemonic_util::Mnemonic;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let input = std::io::stdin();
|
|
@ -3,17 +3,17 @@
|
|||
//! Mnemonics can be used to safely encode data of 32, 48, and 64 bytes as a phrase:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use keyfork_mnemonic::Mnemonic;
|
||||
//! use keyfork_mnemonic_util::Mnemonic;
|
||||
//! let data = b"Hello, world! I am a mnemonic :)";
|
||||
//! assert_eq!(data.len(), 32);
|
||||
//! let mnemonic = Mnemonic::try_from_slice(data).unwrap();
|
||||
//! let mnemonic = Mnemonic::from_bytes(data).unwrap();
|
||||
//! println!("Our mnemonic is: {mnemonic}");
|
||||
//! ```
|
||||
//!
|
||||
//! A mnemonic can also be parsed from a string:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use keyfork_mnemonic::Mnemonic;
|
||||
//! use keyfork_mnemonic_util::Mnemonic;
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
//! let data = b"Hello, world! I am a mnemonic :)";
|
||||
|
@ -28,7 +28,7 @@
|
|||
//! verified to be safe:
|
||||
//!
|
||||
//! ```rust
|
||||
//! use keyfork_mnemonic::Mnemonic;
|
||||
//! use keyfork_mnemonic_util::Mnemonic;
|
||||
//! let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
//! let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) };
|
||||
//! let mnemonic_text = mnemonic.to_string();
|
||||
|
@ -37,7 +37,7 @@
|
|||
//! If given an invalid length, undefined behavior may follow, or code may panic.
|
||||
//!
|
||||
//! ```rust,should_panic
|
||||
//! use keyfork_mnemonic::Mnemonic;
|
||||
//! use keyfork_mnemonic_util::Mnemonic;
|
||||
//! use std::str::FromStr;
|
||||
//!
|
||||
//! // NOTE: Data is of invalid length, 31
|
||||
|
@ -268,11 +268,11 @@ where
|
|||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use keyfork_mnemonic::Mnemonic;
|
||||
/// use keyfork_mnemonic_util::Mnemonic;
|
||||
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
/// let mnemonic = Mnemonic::try_from_slice(data.as_slice()).unwrap();
|
||||
/// let mnemonic = Mnemonic::from_bytes(data.as_slice()).unwrap();
|
||||
/// ```
|
||||
pub fn try_from_slice(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
|
||||
let bit_count = bytes.len() * 8;
|
||||
|
||||
if bit_count % 32 != 0 {
|
||||
|
@ -290,23 +290,23 @@ where
|
|||
/// of a factor of 4, up to 1024 bytes.
|
||||
///
|
||||
/// ```rust
|
||||
/// use keyfork_mnemonic::Mnemonic;
|
||||
/// use keyfork_mnemonic_util::Mnemonic;
|
||||
/// let data = b"hello world!";
|
||||
/// let mnemonic = Mnemonic::from_array(*data);
|
||||
/// let mnemonic = Mnemonic::from_nonstandard_bytes(*data);
|
||||
/// ```
|
||||
///
|
||||
/// If an invalid size is requested, the code will fail to compile:
|
||||
///
|
||||
/// ```rust,compile_fail
|
||||
/// use keyfork_mnemonic::Mnemonic;
|
||||
/// let mnemonic = Mnemonic::from_array([0u8; 53]);
|
||||
/// use keyfork_mnemonic_util::Mnemonic;
|
||||
/// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 53]);
|
||||
/// ```
|
||||
///
|
||||
/// ```rust,compile_fail
|
||||
/// use keyfork_mnemonic::Mnemonic;
|
||||
/// let mnemonic = Mnemonic::from_array([0u8; 1024 + 4]);
|
||||
/// use keyfork_mnemonic_util::Mnemonic;
|
||||
/// let mnemonic = Mnemonic::from_nonstandard_bytes([0u8; 1024 + 4]);
|
||||
/// ```
|
||||
pub fn from_array<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
|
||||
pub fn from_nonstandard_bytes<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
|
||||
#[allow(clippy::let_unit_value)]
|
||||
{
|
||||
let () = AssertValidMnemonicSize::<N>::OK_CHUNKS;
|
||||
|
@ -315,6 +315,16 @@ where
|
|||
Self::from_raw_bytes(&bytes)
|
||||
}
|
||||
|
||||
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be
|
||||
/// of 128, 192, or 256 bits, as per BIP-0039.
|
||||
///
|
||||
/// # Errors
|
||||
/// An error may be returned if the data is not within the expected lengths.
|
||||
#[deprecated = "use Mnemonic::from_bytes"]
|
||||
pub fn from_entropy(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
|
||||
MnemonicBase::from_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Create a Mnemonic using an arbitrary length of given data. The length does not need to
|
||||
/// conform to BIP-0039 standards, but should be a multiple of 32 bits or 4 bytes.
|
||||
///
|
||||
|
@ -322,12 +332,12 @@ where
|
|||
/// This function can potentially produce mnemonics that are not BIP-0039 compliant or can't
|
||||
/// properly be encoded as a mnemonic. It is assumed the caller asserts the byte count is `% 4
|
||||
/// == 0`. If the assumption is incorrect, code may panic. The
|
||||
/// [`MnemonicBase::from_array`] function may be used to generate entropy if the length of the
|
||||
/// data is known at compile-time.
|
||||
/// [`MnemonicBase::from_nonstandard_bytes`] function may be used to generate entropy if the
|
||||
/// length of the data is known at compile-time.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```rust
|
||||
/// use keyfork_mnemonic::Mnemonic;
|
||||
/// use keyfork_mnemonic_util::Mnemonic;
|
||||
/// let data = b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
||||
/// let mnemonic = unsafe { Mnemonic::from_raw_bytes(data.as_slice()) };
|
||||
/// let mnemonic_text = mnemonic.to_string();
|
||||
|
@ -336,7 +346,7 @@ where
|
|||
/// If given an invalid length, undefined behavior may follow, or code may panic.
|
||||
///
|
||||
/// ```rust,should_panic
|
||||
/// use keyfork_mnemonic::Mnemonic;
|
||||
/// use keyfork_mnemonic_util::Mnemonic;
|
||||
/// use std::str::FromStr;
|
||||
///
|
||||
/// // NOTE: Data is of invalid length, 31
|
||||
|
@ -373,31 +383,16 @@ where
|
|||
&self.data
|
||||
}
|
||||
|
||||
/// A view to internal representation of the decoded data.
|
||||
pub fn as_slice(&self) -> &[u8] {
|
||||
&self.data
|
||||
}
|
||||
|
||||
/// A clone of the internal representation of the decoded data.
|
||||
pub fn to_bytes(&self) -> Vec<u8> {
|
||||
self.data.to_vec()
|
||||
}
|
||||
|
||||
/// A clone of the internal representation of the decoded data.
|
||||
pub fn to_vec(&self) -> Vec<u8> {
|
||||
self.data.to_vec()
|
||||
}
|
||||
|
||||
/// Conver the Mnemonic into the internal representation of the decoded data.
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Conver the Mnemonic into the internal representation of the decoded data.
|
||||
pub fn into_vec(self) -> Vec<u8> {
|
||||
self.data
|
||||
}
|
||||
|
||||
/// Clone the existing data.
|
||||
#[deprecated = "Use as_bytes(), to_bytes(), or into_bytes() instead"]
|
||||
pub fn entropy(&self) -> Vec<u8> {
|
||||
|
@ -413,7 +408,7 @@ where
|
|||
&self,
|
||||
passphrase: impl Into<Option<&'a str>>,
|
||||
) -> Result<Vec<u8>, MnemonicGenerationError> {
|
||||
Ok(self.generate_seed(passphrase).to_vec())
|
||||
Ok(self.generate_seed(passphrase))
|
||||
}
|
||||
|
||||
/// Create a BIP-0032 seed from the provided data and an optional passphrase.
|
||||
|
@ -421,7 +416,8 @@ where
|
|||
/// # Panics
|
||||
/// The function may panic if the HmacSha512 function returns an error. The only error the
|
||||
/// HmacSha512 function should return is an invalid length, which should not be possible.
|
||||
pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> [u8; 64] {
|
||||
///
|
||||
pub fn generate_seed<'a>(&self, passphrase: impl Into<Option<&'a str>>) -> Vec<u8> {
|
||||
let passphrase = passphrase.into();
|
||||
|
||||
let mut seed = [0u8; 64];
|
||||
|
@ -429,7 +425,7 @@ where
|
|||
let salt = ["mnemonic", passphrase.unwrap_or("")].join("");
|
||||
pbkdf2::<Hmac<Sha512>>(mnemonic.as_bytes(), salt.as_bytes(), 2048, &mut seed)
|
||||
.expect(bug!("HmacSha512 InvalidLength should be infallible"));
|
||||
seed
|
||||
seed.to_vec()
|
||||
}
|
||||
|
||||
/// Encode the mnemonic into a list of integers 11 bits in length, matching the length of a
|
||||
|
@ -466,39 +462,6 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<W> MnemonicBase<W>
|
||||
where
|
||||
W: Wordlist,
|
||||
{
|
||||
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be
|
||||
/// of 128, 192, or 256 bits, as per BIP-0039.
|
||||
///
|
||||
/// # Errors
|
||||
/// An error may be returned if the data is not within the expected lengths.
|
||||
#[deprecated = "use Mnemonic::try_from_slice"]
|
||||
pub fn from_bytes(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
|
||||
MnemonicBase::try_from_slice(bytes)
|
||||
}
|
||||
|
||||
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data is expected to be
|
||||
/// of 128, 192, or 256 bits, as per BIP-0039.
|
||||
///
|
||||
/// # Errors
|
||||
/// An error may be returned if the data is not within the expected lengths.
|
||||
#[deprecated = "use Mnemonic::try_from_slice"]
|
||||
pub fn from_entropy(bytes: &[u8]) -> Result<MnemonicBase<W>, MnemonicGenerationError> {
|
||||
MnemonicBase::try_from_slice(bytes)
|
||||
}
|
||||
|
||||
/// Generate a [`Mnemonic`] from the provided data and [`Wordlist`]. The data may be of a size
|
||||
/// of a factor of 4, up to 1024 bytes.
|
||||
///
|
||||
#[deprecated = "Use Mnemonic::from_array"]
|
||||
pub fn from_nonstandard_bytes<const N: usize>(bytes: [u8; N]) -> MnemonicBase<W> {
|
||||
MnemonicBase::from_array(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{collections::HashSet, fs::File, io::Read};
|
||||
|
@ -515,7 +478,7 @@ mod tests {
|
|||
let mut random_handle = File::open("/dev/random").unwrap();
|
||||
let entropy = &mut [0u8; 256 / 8];
|
||||
random_handle.read_exact(&mut entropy[..]).unwrap();
|
||||
let mnemonic = super::Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap();
|
||||
let mnemonic = super::Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap();
|
||||
let new_entropy = mnemonic.as_bytes();
|
||||
assert_eq!(new_entropy, entropy);
|
||||
}
|
||||
|
@ -531,7 +494,7 @@ mod tests {
|
|||
};
|
||||
let hex = hex::decode(hex_.as_str().unwrap()).unwrap();
|
||||
|
||||
let mnemonic = Mnemonic::try_from_slice(&hex).unwrap();
|
||||
let mnemonic = Mnemonic::from_bytes(&hex).unwrap();
|
||||
|
||||
assert_eq!(mnemonic.to_string(), seed.as_str().unwrap());
|
||||
}
|
||||
|
@ -542,7 +505,7 @@ mod tests {
|
|||
let mut random_handle = File::open("/dev/random").unwrap();
|
||||
let entropy = &mut [0u8; 256 / 8];
|
||||
random_handle.read_exact(&mut entropy[..]).unwrap();
|
||||
let my_mnemonic = Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap();
|
||||
let my_mnemonic = Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap();
|
||||
let their_mnemonic = bip39::Mnemonic::from_entropy(&entropy[..256 / 8]).unwrap();
|
||||
assert_eq!(my_mnemonic.to_string(), their_mnemonic.to_string());
|
||||
assert_eq!(my_mnemonic.generate_seed(None), their_mnemonic.to_seed(""));
|
||||
|
@ -566,7 +529,7 @@ mod tests {
|
|||
|
||||
for _ in 0..tests {
|
||||
random.read_exact(&mut entropy[..]).unwrap();
|
||||
let mnemonic = Mnemonic::try_from_slice(&entropy[..256 / 8]).unwrap();
|
||||
let mnemonic = Mnemonic::from_bytes(&entropy[..256 / 8]).unwrap();
|
||||
let words = mnemonic.words();
|
||||
hs.clear();
|
||||
hs.extend(words);
|
||||
|
@ -597,7 +560,7 @@ mod tests {
|
|||
let mut entropy = [0u8; 1024];
|
||||
let mut random = std::fs::File::open("/dev/urandom").unwrap();
|
||||
random.read_exact(&mut entropy[..]).unwrap();
|
||||
let mnemonic = Mnemonic::from_array(entropy);
|
||||
let mnemonic = Mnemonic::from_nonstandard_bytes(entropy);
|
||||
let words = mnemonic.words();
|
||||
assert_eq!(words.len(), 768);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "keyfork-prompt"
|
||||
version = "0.1.2"
|
||||
version = "0.1.0"
|
||||
description = "Prompt management utilities for Keyfork"
|
||||
repository = "https://git.distrust.co/public/keyfork"
|
||||
edition = "2021"
|
||||
|
@ -10,10 +10,10 @@ license = "MIT"
|
|||
|
||||
[features]
|
||||
default = ["mnemonic"]
|
||||
mnemonic = ["keyfork-mnemonic"]
|
||||
mnemonic = ["keyfork-mnemonic-util"]
|
||||
|
||||
[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-util = { version = "0.2.0", path = "../keyfork-mnemonic-util", optional = true, registry = "distrust" }
|
||||
thiserror = "1.0.51"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#![allow(missing_docs)]
|
||||
//!
|
||||
|
||||
use std::io::{stdin, stdout};
|
||||
|
||||
|
@ -7,7 +7,7 @@ use keyfork_prompt::{
|
|||
Terminal, PromptHandler,
|
||||
};
|
||||
|
||||
use keyfork_mnemonic::English;
|
||||
use keyfork_mnemonic_util::English;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut mgr = Terminal::new(stdin(), stdout())?;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::borrow::Borrow;
|
||||
|
||||
#[cfg(feature = "mnemonic")]
|
||||
use keyfork_mnemonic::Wordlist;
|
||||
use keyfork_mnemonic_util::Wordlist;
|
||||
|
||||
///
|
||||
pub mod terminal;
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -158,7 +158,7 @@ pub mod mnemonic {
|
|||
use super::Validator;
|
||||
|
||||
use keyfork_bug::bug;
|
||||
use keyfork_mnemonic::{Mnemonic, MnemonicFromStrError};
|
||||
use keyfork_mnemonic_util::{Mnemonic, MnemonicFromStrError};
|
||||
|
||||
/// A mnemonic could not be validated from the given input.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
|
|
@ -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)].
|
||||
|
@ -99,9 +113,30 @@ allow = [
|
|||
"Unicode-DFS-2016",
|
||||
"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,7 +147,7 @@ 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 = "*" },
|
||||
]
|
||||
|
||||
|
@ -120,8 +155,10 @@ exceptions = [
|
|||
# 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
|
||||
|
@ -130,8 +167,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]
|
||||
|
@ -171,24 +208,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
|
||||
|
@ -209,18 +247,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`.
|
||||
|
|
|
@ -45,7 +45,7 @@ A command line interface for generating, deriving from, and managing secrets.
|
|||
* [`keyfork-derive-openpgp`]
|
||||
* [`keyfork-derive-util`]
|
||||
* [`keyfork-entropy`]
|
||||
* [`keyfork-mnemonic`]
|
||||
* [`keyfork-mnemonic-util`]
|
||||
* [`keyfork-prompt`]
|
||||
* [`keyfork-qrcode`]
|
||||
* [`keyfork-shard`]
|
||||
|
@ -68,7 +68,7 @@ seed or close-to-root derivations.
|
|||
* [`keyfork-derive-path-data`]
|
||||
* [`keyfork-derive-util`]
|
||||
* [`keyfork-frame`]
|
||||
* [`keyfork-mnemonic`]
|
||||
* [`keyfork-mnemonic-util`]
|
||||
* [`keyforkd-models`]
|
||||
* [`serde`]
|
||||
* [`thiserror`]
|
||||
|
@ -129,7 +129,7 @@ BIP-0032 derivation.
|
|||
* [`ed25519-dalek`]: Ed25519 key parsing and arithmetic.
|
||||
* [`hmac`]: Derivation of keys using HMAC.
|
||||
* [`k256`]: secp256k1 (K-256) key parsing and arithmetic.
|
||||
* [`keyfork-mnemonic`]
|
||||
* [`keyfork-mnemonic-util`]
|
||||
* [`ripemd`]: Generating hash for fingerprinting of BIP-0032 derived data.
|
||||
* [`serde`]
|
||||
* [`sha2`]: Generating hashes for fingerprinting and derivation of data.
|
||||
|
@ -145,7 +145,7 @@ M-of-N recombination of secret data using Shamir's Secret Sharing.
|
|||
* [`card-backend-pcsc`]: PCSC support for OpenPGP-card.
|
||||
* [`hkdf`]: Key derivation for transport encryption keys.
|
||||
* [`keyfork-derive-openpgp`]
|
||||
* [`keyfork-mnemonic`]: Encoding encrypted shards using mnemonics.
|
||||
* [`keyfork-mnemonic-util`]: Encoding encrypted shards using mnemonics.
|
||||
* [`keyfork-prompt`]
|
||||
* [`keyfork-qrcode`]: Encoding and decoding of encrypted shards using QR codes.
|
||||
* [`openpgp-card`]: OpenPGP card support.
|
||||
|
@ -193,7 +193,7 @@ Frame data in a length-storing checksum-verified format.
|
|||
* [`thiserror`]
|
||||
* [`tokio`]: Read and write from AsyncRead and AsyncWrite sources.
|
||||
|
||||
## `keyfork-mnemonic`
|
||||
## `keyfork-mnemonic-util`
|
||||
|
||||
* [`hmac`]: Hash utilities.
|
||||
* [`sha2`]: Checksum of mnemonic data and hash for pbkdf2
|
||||
|
@ -202,7 +202,7 @@ Frame data in a length-storing checksum-verified format.
|
|||
## `keyfork-prompt`
|
||||
|
||||
* [`keyfork-crossterm`]: Interacting with the terminal.
|
||||
* [`keyfork-mnemonic`]
|
||||
* [`keyfork-mnemonic-util`]
|
||||
* [`thiserror`]
|
||||
|
||||
## `keyfork-plumbing`
|
||||
|
@ -210,7 +210,7 @@ Frame data in a length-storing checksum-verified format.
|
|||
Binaries for `keyfork-entropy` and `keyfork-mnemonic-from-seed`.
|
||||
|
||||
* [`keyfork-entropy`]
|
||||
* [`keyfork-mnemonic`]
|
||||
* [`keyfork-mnemonic-util`]
|
||||
* [`smex`]
|
||||
|
||||
## `keyfork-slip10-test-data`
|
||||
|
@ -229,7 +229,7 @@ Zero-dependency hex encoding and decoding.
|
|||
[`keyfork-derive-util`]: #keyfork-derive-util
|
||||
[`keyfork-entropy`]: #keyfork-entropy
|
||||
[`keyfork-frame`]: #keyfork-frame
|
||||
[`keyfork-mnemonic`]: #keyfork-mnemonic
|
||||
[`keyfork-mnemonic-util`]: #keyfork-mnemonic-util
|
||||
[`keyfork-prompt`]: #keyfork-prompt
|
||||
[`keyfork-qrcode`]: #keyfork-qrcode
|
||||
[`keyfork-shard`]: #keyfork-shard
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import json
|
||||
import sys
|
||||
|
||||
# pipe `cargo metadata --format-version=1` into this
|
||||
|
||||
priority_queue = []
|
||||
packages = json.load(sys.stdin)["packages"]
|
||||
|
||||
def sort_graph(unsorted):
|
||||
sorted = []
|
||||
while unsorted:
|
||||
deadlock = True
|
||||
for node, edges in list(unsorted.items()):
|
||||
for edge in edges:
|
||||
if edge in unsorted:
|
||||
break
|
||||
else:
|
||||
del unsorted[node]
|
||||
sorted.append((node, edges))
|
||||
deadlock = False
|
||||
if deadlock:
|
||||
raise Exception("deadlock")
|
||||
return sorted
|
||||
|
||||
packages_dict = {
|
||||
package["name"]: [
|
||||
dep["name"] for dep in package["dependencies"]
|
||||
if dep["kind"] is None
|
||||
]
|
||||
for package in packages if package["source"] is None
|
||||
}
|
||||
|
||||
# iter_packages("keyfork")
|
||||
priority_queue = sort_graph(packages_dict.copy())
|
||||
for key, _ in priority_queue:
|
||||
version = next(p["version"] for p in packages if p["name"] == key)
|
||||
print(" ".join([key, version]))
|
|
@ -1,21 +0,0 @@
|
|||
set -eu
|
||||
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; })"
|
||||
if test ! -z "$git_log"; then
|
||||
echo "### Changes in $crate:"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
echo "$git_log"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
fi
|
||||
done
|
|
@ -1,15 +0,0 @@
|
|||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
scripts_dir="$(dirname $0)"
|
||||
python_script="$scripts_dir/generate-dependency-queue.py"
|
||||
registry_url="https://git.distrust.co/api/packages/public/cargo"
|
||||
search_url="${registry_url}/api/v1/crates"
|
||||
|
||||
cargo metadata --format-version=1 | python3 "$python_script" | while read crate version; do
|
||||
# Verify the package does not exist
|
||||
if ! curl "${search_url}?q=${crate}" 2>/dev/null | jq -e "$(printf '.crates | .[] | select(.name == "%s" and .max_version == "%s")' "$crate" "$version")" >/dev/null; then
|
||||
cargo publish --registry distrust -p "$crate"
|
||||
fi
|
||||
done
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
LAST_REF="$1"
|
||||
CURRENT_REF="${2:-HEAD}"
|
||||
|
||||
temp_file="$(mktemp)"
|
||||
|
||||
cargo metadata --format-version=1 | jq -r '.packages[] | select(.source == null) | .name + " " + .manifest_path + " " + .version' > "$temp_file"
|
||||
|
||||
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_tag="$(git tag --list "$crate-v${version}")"
|
||||
if test ! -z "$git_log" -a -z "$git_tag"; then
|
||||
{
|
||||
echo "${crate} v${version}"
|
||||
echo ""
|
||||
echo "\`\`\`"
|
||||
echo "$git_log"
|
||||
echo "\`\`\`"
|
||||
echo ""
|
||||
echo "# Crate: ${crate} ${version}"
|
||||
} | git tag --sign "${crate}-v${version}" -F - -e
|
||||
echo "Making new tag: ${crate}-v${version}"
|
||||
fi
|
||||
done 3<"$temp_file"
|
||||
|
||||
rm "$temp_file"
|
Loading…
Reference in New Issue