Compare commits
100 Commits
4a48e932d4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a50024285 | |||
|
07e1671867
|
|||
|
4537b6ece9
|
|||
|
f96144f450
|
|||
|
1d49b7c1c1
|
|||
|
10c38cda8a
|
|||
|
9f544e3cb7
|
|||
|
3d29fe6a6d
|
|||
|
1d23dba248
|
|||
|
6f556cc2d6
|
|||
|
33a6661c3f
|
|||
|
421f2e2ffe
|
|||
|
3647515321
|
|||
|
6bd4d0ba57
|
|||
|
d272904357
|
|||
|
0bc671be7b
|
|||
|
a698a852fd
|
|||
|
febdf659cd
|
|||
|
62110ed7fb
|
|||
|
149650bf15
|
|||
|
5b3e0bc8cb
|
|||
|
f870c07387
|
|||
|
ecf034376d
|
|||
|
488db38387
|
|||
|
f6b9671872
|
|||
|
87e51cc7e4
|
|||
|
bb8d804505
|
|||
|
d104d4405e
|
|||
|
f5a15ca0ae
|
|||
|
28c2f096f7
|
|||
|
9926dbf09d
|
|||
|
b23f4a3a69
|
|||
|
d42bfd4bcc
|
|||
|
f74820903a
|
|||
|
21b5cc8221
|
|||
|
7fa6aa1146
|
|||
|
b4beaa3a75
|
|||
|
fb026c9f21
|
|||
|
4431bff9e6
|
|||
|
58f665823d
|
|||
|
fdf02bc976
|
|||
|
8e4cf5cec8
|
|||
|
0b9ec436ba
|
|||
|
a1ae0ff4dc
|
|||
|
06d2da4ddf
|
|||
|
0513dd2398
|
|||
|
d6ecdb5ed4
|
|||
|
81f7a6d77e
|
|||
|
63fabc6054
|
|||
|
57c3ec57df
|
|||
|
c0ea3b773d
|
|||
|
67568f8f15
|
|||
|
9435b287c8
|
|||
|
96927e0dab
|
|||
|
8894a2156a
|
|||
|
e52e42d48c
|
|||
|
a3541e7b68
|
|||
|
d7f52530df
|
|||
|
3dae02e090
|
|||
|
86489c5d29
|
|||
|
1773186dbf
|
|||
|
cec27e0f88
|
|||
|
9a749b63eb
|
|||
|
fcb10f5efa
|
|||
|
5329108380
|
|||
|
b8f0be2023
|
|||
|
0ac9300262
|
|||
|
c270c2e369
|
|||
|
3af863762f
|
|||
|
21676451fd
|
|||
|
4dca8e0146
|
|||
|
dc56f2df77
|
|||
|
4dac890200
|
|||
|
e7b20abd6d
|
|||
|
492c434f62
|
|||
|
fe30f538ba
|
|||
|
e6409174b6
|
|||
|
bb02c7c823
|
|||
|
e9388eb164
|
|||
|
a6bff6d31c
|
|||
|
6d3298549e
|
|||
|
417e3f6a49
|
|||
|
3a40d7f0ad
|
|||
|
1be5754ed1
|
|||
|
7ec3a705cf
|
|||
|
e2fa3bba9f
|
|||
|
070161c056
|
|||
|
3fb43403aa
|
|||
|
c2b3a779c8
|
|||
|
755d61fa86
|
|||
|
af20f4c4a0
|
|||
|
1582f76cae
|
|||
|
3848b65ff1
|
|||
|
d4fce3f4fc
|
|||
|
8aeb47c66f
|
|||
|
d04038ccd9
|
|||
|
aee2f8d5d3
|
|||
|
25e80661bb
|
|||
|
c5e35fd941
|
|||
|
468701b3ff
|
2306
Cargo.lock
generated
2306
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
58
Cargo.toml
58
Cargo.toml
@@ -1,9 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.11.0"
|
version = "1.13.21"
|
||||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
description = "FIDO(U2F, WebAuthn), YubiKey, OpenPGP command line tool"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
repository = "https://git.hatter.ink/hatter/card-cli"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["with-sequoia-openpgp"]
|
default = ["with-sequoia-openpgp"]
|
||||||
with-sequoia-openpgp = ["sequoia-openpgp", "openpgp-card-sequoia"]
|
with-sequoia-openpgp = ["sequoia-openpgp", "openpgp-card-sequoia"]
|
||||||
@@ -16,15 +20,14 @@ digest = "0.10"
|
|||||||
sha1 = "0.10"
|
sha1 = "0.10"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
base64 = "0.21"
|
base64 = "0.22"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
u2f = "0.2"
|
|
||||||
openpgp-card = "0.3"
|
openpgp-card = "0.3"
|
||||||
openpgp-card-pcsc = "0.3"
|
openpgp-card-pcsc = "0.3"
|
||||||
openpgp-card-sequoia = { version = "0.1", optional = true }
|
openpgp-card-sequoia = { version = "0.2", optional = true }
|
||||||
sequoia-openpgp = { version = "1.0", optional = true }
|
sequoia-openpgp = { version = "2.0", optional = true }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
simpledateformat = "0.1"
|
simpledateformat = "0.1"
|
||||||
ring = "0.17"
|
ring = "0.17"
|
||||||
@@ -33,28 +36,41 @@ pem = "3.0"
|
|||||||
yubikey = { version = "0.8", features = ["untested"] }
|
yubikey = { version = "0.8", features = ["untested"] }
|
||||||
yubico_manager = "0.9"
|
yubico_manager = "0.9"
|
||||||
x509 = "0.2"
|
x509 = "0.2"
|
||||||
x509-parser = { version = "0.15", features = ["verify"] }
|
x509-parser = { version = "0.17", features = ["verify"] }
|
||||||
ssh-agent = { version = "0.2", features = ["agent"] }
|
ssh-agent = { version = "0.2", features = ["agent"] }
|
||||||
p256 = { version = "0.13", features = ["pem", "ecdh", "ecdsa"] }
|
p256 = { version = "0.13", features = ["pem", "ecdh", "ecdsa", "jwk"] }
|
||||||
p384 = { version = "0.13", features = ["pem", "ecdh", "ecdsa"] }
|
p384 = { version = "0.13", features = ["pem", "ecdh", "ecdsa", "jwk"] }
|
||||||
|
p521 = { version = "0.13", features = ["pem", "ecdh", "ecdsa", "jwk"] }
|
||||||
spki = { version = "0.7", features = ["pem"] }
|
spki = { version = "0.7", features = ["pem"] }
|
||||||
tabled = "0.14"
|
tabled = "0.20"
|
||||||
env_logger = "0.10"
|
env_logger = "0.11"
|
||||||
bech32 = "0.9"
|
bech32 = "0.9"
|
||||||
ecdsa = { version = "0.16", features = ["verifying", "spki", "pem", "der"] }
|
ecdsa = { version = "0.16", features = ["verifying", "spki", "pem", "der"] }
|
||||||
jwt = "0.16"
|
jwt = "0.16"
|
||||||
reqwest = { version = "0.11", features = ["blocking"] }
|
reqwest = { version = "0.12", features = ["blocking"] }
|
||||||
pinentry = "0.5"
|
pinentry = "0.6"
|
||||||
rpassword = "7.3"
|
rpassword = "7.3"
|
||||||
secrecy = "0.8"
|
secrecy = "0.10"
|
||||||
der-parser = "9.0"
|
der-parser = "10.0"
|
||||||
sshcerts = "0.13"
|
sshcerts-hatter-fork = "0.14.1"
|
||||||
regex = "1.4.6"
|
regex = "1.11"
|
||||||
aes-gcm-stream = "0.2.4"
|
aes-gcm-stream = "0.2"
|
||||||
swift-secure-enclave-tool-rs = "0.1.0"
|
swift-secure-enclave-tool-rs = "1.0"
|
||||||
|
u2f-hatter-fork = "0.2"
|
||||||
|
security-framework = { version = "3.0", features = ["OSX_10_15"] }
|
||||||
|
rsa = "0.9"
|
||||||
|
which = "8.0"
|
||||||
|
percent-encoding = "2.3.1"
|
||||||
|
external-command-rs = "0.1.1"
|
||||||
|
ssh-agent-lib = { version = "0.5.1" }
|
||||||
|
ssh-key = { version = "0.6", features = ["ecdsa", "alloc"] }
|
||||||
|
tokio = "1.45.1"
|
||||||
|
ssh-encoding = { version = "0.2.0", features = ["alloc"] }
|
||||||
|
zeroize = "1.8"
|
||||||
|
ml-kem = { version = "0.2.1", features = ["zeroize"] }
|
||||||
|
zeroizing-alloc = "0.1.0"
|
||||||
#lazy_static = "1.4.0"
|
#lazy_static = "1.4.0"
|
||||||
#ssh-key = "0.4.0"
|
|
||||||
#ctap-hid-fido2 = "2.1.3"
|
#ctap-hid-fido2 = "2.1.3"
|
||||||
|
|
||||||
[patch.crates-io]
|
#[patch.crates-io]
|
||||||
u2f = { git = "https://github.com/jht5945/u2f-rs.git" }
|
#ml-kem = { path = "externals/ml-kem" }
|
||||||
|
|||||||
@@ -247,6 +247,10 @@ SSH to server:
|
|||||||
ssh -i id_user root@example.com
|
ssh -i id_user root@example.com
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
> `external_*` subcommands follow <<Cryptography external command specification>><br>
|
||||||
|
> Specification: https://openwebstandard.org/rfc1
|
||||||
|
|
||||||
<br><br>
|
<br><br>
|
||||||
|
|
||||||
|
|||||||
26
examples/rsa.rs
Normal file
26
examples/rsa.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use base64::Engine;
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
use rsa::pkcs1::LineEnding;
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
use rsa::pkcs8::EncodePrivateKey;
|
||||||
|
use rsa::traits::PublicKeyParts;
|
||||||
|
use rsa::RsaPrivateKey;
|
||||||
|
use spki::EncodePublicKey;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let key = RsaPrivateKey::new(&mut OsRng, 1024).unwrap();
|
||||||
|
let pem = key.to_pkcs8_pem(LineEnding::LF).unwrap();
|
||||||
|
println!("{}", pem.as_str());
|
||||||
|
|
||||||
|
let key2 = RsaPrivateKey::from_pkcs8_pem(pem.as_ref()).unwrap();
|
||||||
|
|
||||||
|
let pub_key = key2.to_public_key();
|
||||||
|
let public_key_pem = pub_key.to_public_key_pem(LineEnding::LF).unwrap();
|
||||||
|
println!("{}", public_key_pem);
|
||||||
|
|
||||||
|
let n = pub_key.n();
|
||||||
|
let e = pub_key.e();
|
||||||
|
let url_safe = base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||||
|
println!("n: {}", url_safe.encode(&n.to_bytes_be()));
|
||||||
|
println!("e: {}", url_safe.encode(&e.to_bytes_be()));
|
||||||
|
}
|
||||||
4
justfile
4
justfile
@@ -1,6 +1,10 @@
|
|||||||
_:
|
_:
|
||||||
@just --list
|
@just --list
|
||||||
|
|
||||||
|
# publish
|
||||||
|
publish:
|
||||||
|
cargo publish --registry crates-io
|
||||||
|
|
||||||
# install card-cli
|
# install card-cli
|
||||||
install:
|
install:
|
||||||
cargo install --path .
|
cargo install --path .
|
||||||
|
|||||||
@@ -5,14 +5,22 @@ use std::io::Read;
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
|
|
||||||
use crate::digest::{sha256, sha256_bytes};
|
use crate::digestutil::DigestAlgorithm;
|
||||||
|
|
||||||
|
|
||||||
pub fn get_sha256_digest_or_hash(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
|
pub fn get_sha256_digest_or_hash(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
|
||||||
get_sha256_digest_or_hash_with_file_opt(sub_arg_matches, &None)
|
get_sha256_digest_or_hash_with_file_opt(sub_arg_matches, &None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_digest_or_hash(sub_arg_matches: &ArgMatches, digest: DigestAlgorithm) -> XResult<Vec<u8>> {
|
||||||
|
get_digest_or_hash_with_file_opt(sub_arg_matches, &None, digest)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_sha256_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, file_opt: &Option<String>) -> XResult<Vec<u8>> {
|
pub fn get_sha256_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, file_opt: &Option<String>) -> XResult<Vec<u8>> {
|
||||||
|
get_digest_or_hash_with_file_opt(sub_arg_matches, file_opt, DigestAlgorithm::Sha256)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, file_opt: &Option<String>, digest: DigestAlgorithm) -> XResult<Vec<u8>> {
|
||||||
let file_opt = file_opt.as_ref().map(String::as_str);
|
let file_opt = file_opt.as_ref().map(String::as_str);
|
||||||
if let Some(file) = sub_arg_matches.value_of("file").or(file_opt) {
|
if let Some(file) = sub_arg_matches.value_of("file").or(file_opt) {
|
||||||
let metadata = opt_result!(fs::metadata(file), "Read file: {} metadata filed: {}", file);
|
let metadata = opt_result!(fs::metadata(file), "Read file: {} metadata filed: {}", file);
|
||||||
@@ -28,9 +36,9 @@ pub fn get_sha256_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, fil
|
|||||||
let mut f = opt_result!(File::open(file), "Open file: {} failed: {}", file);
|
let mut f = opt_result!(File::open(file), "Open file: {} failed: {}", file);
|
||||||
let mut content = vec![];
|
let mut content = vec![];
|
||||||
opt_result!(f.read_to_end(&mut content), "Read file: {} failed: {}", file);
|
opt_result!(f.read_to_end(&mut content), "Read file: {} failed: {}", file);
|
||||||
Ok(sha256_bytes(&content))
|
Ok(digest.digest(&content))
|
||||||
} else if let Some(input) = sub_arg_matches.value_of("input") {
|
} else if let Some(input) = sub_arg_matches.value_of("input") {
|
||||||
Ok(sha256(input))
|
Ok(digest.digest_str(input))
|
||||||
} else if let Some(hash_hex) = sub_arg_matches.value_of("hash-hex") {
|
} else if let Some(hash_hex) = sub_arg_matches.value_of("hash-hex") {
|
||||||
Ok(opt_result!(hex::decode(hash_hex), "Parse hash-hex failed: {}"))
|
Ok(opt_result!(hex::decode(hash_hex), "Parse hash-hex failed: {}"))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
|
|
||||||
use crate::hmacutil;
|
use crate::{cmdutil, hmacutil};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -17,12 +16,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("sha256").short("2").long("sha256").help("Output SHA256"))
|
.arg(Arg::with_name("sha256").short("2").long("sha256").help("Output SHA256"))
|
||||||
.arg(Arg::with_name("sha384").short("3").long("sha384").help("Output SHA384"))
|
.arg(Arg::with_name("sha384").short("3").long("sha384").help("Output SHA384"))
|
||||||
.arg(Arg::with_name("sha512").short("5").long("sha512").help("Output SHA512"))
|
.arg(Arg::with_name("sha512").short("5").long("sha512").help("Output SHA512"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let challenge_bytes = hmacutil::get_challenge_bytes(sub_arg_matches)?;
|
let challenge_bytes = hmacutil::get_challenge_bytes(sub_arg_matches)?;
|
||||||
let hmac_result = hmacutil::compute_yubikey_hmac(&challenge_bytes)?;
|
let hmac_result = hmacutil::compute_yubikey_hmac(&challenge_bytes)?;
|
||||||
|
|||||||
46
src/cmd_convert_jwk_to_pem.rs
Normal file
46
src/cmd_convert_jwk_to_pem.rs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
use crate::util::base64_encode;
|
||||||
|
use crate::{cmdutil, ecutil, util};
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"convert-jwk-to-pem"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("Convert PEM to JWK")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("jwk")
|
||||||
|
.long("jwk")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.help("JWK"),
|
||||||
|
)
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let jwk = sub_arg_matches.value_of("jwk").unwrap();
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let (public_key_pem, public_ker_der) = ecutil::convert_ec_jwk_to_public_key(jwk)?;
|
||||||
|
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
if json_output {
|
||||||
|
json.insert("public_key_pem", public_key_pem);
|
||||||
|
json.insert("public_key_base64", base64_encode(&public_ker_der));
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
information!("Public key PEM:\n{}", &public_key_pem);
|
||||||
|
information!("\nPublic key base64:\n{}", base64_encode(&public_ker_der));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/cmd_convert_pem_to_jwk.rs
Normal file
42
src/cmd_convert_pem_to_jwk.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::{ecutil, rsautil, util};
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"convert-pem-to-jwk"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("Convert PEM to JWK")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("public-key")
|
||||||
|
.long("public-key")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Public key (PEM, base64(DER) format)"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let public_key = sub_arg_matches.value_of("public-key").unwrap();
|
||||||
|
|
||||||
|
let jwk = match ecutil::convert_ec_public_key_to_jwk(public_key) {
|
||||||
|
Ok(jwk) => jwk,
|
||||||
|
Err(_) => match rsautil::convert_rsa_to_jwk(public_key) {
|
||||||
|
Ok(jwk) => jwk,
|
||||||
|
Err(_) => return simple_error!("Invalid public key."),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let jwk_value: Value = serde_json::from_str(&jwk).unwrap();
|
||||||
|
|
||||||
|
util::print_pretty_json(&jwk_value);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,9 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
|
|
||||||
use crate::ecdsautil::EcdsaAlgorithm;
|
use crate::ecdsautil::EcdsaAlgorithm;
|
||||||
use crate::{argsutil, ecdsautil};
|
use crate::{argsutil, cmdutil, ecdsautil, util};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -19,12 +18,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file"))
|
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file"))
|
||||||
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
|
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
|
||||||
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
|
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?;
|
let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?;
|
||||||
let public_key = if let Some(public_key_hex) = sub_arg_matches.value_of("public-key-hex") {
|
let public_key = if let Some(public_key_hex) = sub_arg_matches.value_of("public-key-hex") {
|
||||||
@@ -55,7 +53,7 @@ impl Command for CommandImpl {
|
|||||||
json.insert("signature_hex", hex::encode(&signature));
|
json.insert("signature_hex", hex::encode(&signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
match ecdsautil::ecdsaverify(ecdsa_algorithm, &public_key, &hash_bytes, &signature) {
|
match ecdsautil::ecdsa_verify(ecdsa_algorithm, &public_key, &hash_bytes, &signature) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
success!("Verify ECDSA succeed.");
|
success!("Verify ECDSA succeed.");
|
||||||
if json_output {
|
if json_output {
|
||||||
@@ -72,7 +70,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
142
src/cmd_external_ecdh.rs
Normal file
142
src/cmd_external_ecdh.rs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
use crate::keyutil::{parse_key_uri, KeyAlgorithmId, KeyUri, KeyUsage};
|
||||||
|
use crate::pivutil::ToStr;
|
||||||
|
use crate::{cmd_hmac_decrypt, cmd_se_ecdh, cmdutil, ecdhutil, mlkemutil, pivutil, seutil, util, yubikeyutil};
|
||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use yubikey::piv::{decrypt_data, AlgorithmId};
|
||||||
|
use crate::util::try_decode;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"external_ecdh"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("External ECDH subcommand")
|
||||||
|
.arg(cmdutil::build_parameter_arg())
|
||||||
|
.arg(cmdutil::build_epk_arg())
|
||||||
|
.arg(cmdutil::build_pin_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let parameter = sub_arg_matches.value_of("parameter").unwrap();
|
||||||
|
let epk = sub_arg_matches.value_of("epk").unwrap();
|
||||||
|
let ephemeral_public_key_der_bytes = cmd_se_ecdh::parse_epk(epk)?;
|
||||||
|
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
|
let key_uri = parse_key_uri(parameter)?;
|
||||||
|
|
||||||
|
match ecdh(&ephemeral_public_key_der_bytes, key_uri, sub_arg_matches) {
|
||||||
|
Ok(shared_secret_bytes) => {
|
||||||
|
json.insert("success", Value::Bool(true));
|
||||||
|
json.insert(
|
||||||
|
"shared_secret_hex",
|
||||||
|
hex::encode(&shared_secret_bytes).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
json.insert("success", Value::Bool(false));
|
||||||
|
json.insert("error", e.to_string().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ecdh(
|
||||||
|
ephemeral_public_key_bytes: &[u8],
|
||||||
|
key_uri: KeyUri,
|
||||||
|
sub_arg_matches: &ArgMatches,
|
||||||
|
) -> XResult<Vec<u8>> {
|
||||||
|
match key_uri {
|
||||||
|
KeyUri::SecureEnclave(key) => {
|
||||||
|
if key.usage != KeyUsage::Singing {
|
||||||
|
return simple_error!("Not singing key");
|
||||||
|
}
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.private_key)?;
|
||||||
|
seutil::secure_enclave_p256_dh(&private_key, ephemeral_public_key_bytes)
|
||||||
|
}
|
||||||
|
KeyUri::YubikeyPiv(key) => {
|
||||||
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
|
let pin_opt = pivutil::check_read_pin(&mut yk, key.slot, sub_arg_matches);
|
||||||
|
|
||||||
|
if let Some(pin) = pin_opt {
|
||||||
|
opt_result!(
|
||||||
|
yk.verify_pin(pin.as_bytes()),
|
||||||
|
"YubiKey verify pin failed: {}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let algorithm = opt_value_result!(
|
||||||
|
KeyAlgorithmId::to_algorithm_id(key.algorithm),
|
||||||
|
"Yubikey not supported algorithm: {}",
|
||||||
|
key.algorithm.to_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
let epk_bytes = match algorithm {
|
||||||
|
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => {
|
||||||
|
return simple_error!("Algorithm is not supported: {:?}", algorithm)
|
||||||
|
}
|
||||||
|
AlgorithmId::EccP256 => {
|
||||||
|
use p256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
let public_key = opt_result!(PublicKey::from_public_key_der(
|
||||||
|
ephemeral_public_key_bytes),"Parse P256 ephemeral public key failed: {}");
|
||||||
|
public_key.to_encoded_point(false).as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
AlgorithmId::EccP384 => {
|
||||||
|
use p384::{elliptic_curve::sec1::ToEncodedPoint, PublicKey};
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
let public_key = opt_result!(PublicKey::from_public_key_der(
|
||||||
|
ephemeral_public_key_bytes), "Parse P384 ephemeral public key failed: {}");
|
||||||
|
public_key.to_encoded_point(false).as_bytes().to_vec()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let decrypted_shared_secret = opt_result!(
|
||||||
|
decrypt_data(&mut yk, &epk_bytes, algorithm, key.slot,),
|
||||||
|
"Decrypt piv failed: {}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(decrypted_shared_secret.to_vec())
|
||||||
|
}
|
||||||
|
KeyUri::YubikeyHmacEncSoft(key) => {
|
||||||
|
if key.algorithm.is_ecc() {
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let private_key_bytes = try_decode(&private_key)?;
|
||||||
|
|
||||||
|
if let Ok(shared_secret) = ecdhutil::parse_p256_private_and_ecdh(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||||
|
return Ok(shared_secret.to_vec());
|
||||||
|
}
|
||||||
|
if let Ok(shared_secret) = ecdhutil::parse_p384_private_and_ecdh(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||||
|
return Ok(shared_secret.to_vec());
|
||||||
|
}
|
||||||
|
if let Ok(shared_secret) = ecdhutil::parse_p521_private_and_ecdh(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||||
|
return Ok(shared_secret.to_vec());
|
||||||
|
}
|
||||||
|
simple_error!("Invalid EC private key and/or ephemeral public key")
|
||||||
|
} else if key.algorithm.is_mlkem() {
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let private_key_bytes = try_decode(&private_key)?;
|
||||||
|
if let Ok((_, shared_secret)) = mlkemutil::try_parse_decapsulate_key_private_then_decapsulate(&private_key_bytes, ephemeral_public_key_bytes) {
|
||||||
|
return Ok(shared_secret);
|
||||||
|
}
|
||||||
|
simple_error!("Invalid ML-KEM private key and/or ephemeral public key")
|
||||||
|
} else {
|
||||||
|
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyUri::ExternalCommand(key) => {
|
||||||
|
let parameter = cmd_hmac_decrypt::try_decrypt(&mut None, &key.parameter)?;
|
||||||
|
external_command_rs::external_ecdh(&key.external_command, ¶meter, ephemeral_public_key_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
121
src/cmd_external_public_key.rs
Normal file
121
src/cmd_external_public_key.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
|
||||||
|
use crate::util::{base64_decode, base64_encode};
|
||||||
|
use crate::yubikeyutil::find_key_or_error;
|
||||||
|
use crate::{cmd_hmac_decrypt, cmdutil, ecdsautil, seutil, util, yubikeyutil};
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use ecdsa::elliptic_curve::pkcs8::der::Encode;
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use rsa::RsaPrivateKey;
|
||||||
|
use spki::EncodePublicKey;
|
||||||
|
use x509_parser::parse_x509_certificate;
|
||||||
|
use crate::mlkemutil::{try_parse_decapsulate_key_private_get_encapsulate, try_parse_encapsulation_key_public_then_encapsulate};
|
||||||
|
use crate::pivutil::ToStr;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"external_public_key"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("External public key subcommand")
|
||||||
|
.arg(cmdutil::build_parameter_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
|
.arg(Arg::with_name("x-generate-shared-secret").long("x-generate-shared-secret").help("Generate a shared secret"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let parameter = sub_arg_matches.value_of("parameter").unwrap();
|
||||||
|
let serial_opt = sub_arg_matches.value_of("serial");
|
||||||
|
let generate_shared_secret = sub_arg_matches.is_present("x-generate-shared-secret");
|
||||||
|
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
|
match fetch_public_key(parameter, &serial_opt, generate_shared_secret, &mut json) {
|
||||||
|
Ok(public_key_bytes) => {
|
||||||
|
json.insert("success", Value::Bool(true));
|
||||||
|
json.insert("public_key_base64", base64_encode(&public_key_bytes).into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
json.insert("success", Value::Bool(false));
|
||||||
|
json.insert("error", e.to_string().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>, generate_shared_secret: bool, json: &mut BTreeMap<&str, Value>) -> XResult<Vec<u8>> {
|
||||||
|
let key_uri = parse_key_uri(parameter)?;
|
||||||
|
match key_uri {
|
||||||
|
KeyUri::SecureEnclave(key) => {
|
||||||
|
if key.usage != KeyUsage::Singing {
|
||||||
|
simple_error!("Not singing key")
|
||||||
|
} else {
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.private_key)?;
|
||||||
|
let (_, public_key_der, _) =
|
||||||
|
seutil::recover_secure_enclave_p256_public_key(&private_key, true)?;
|
||||||
|
Ok(public_key_der)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyUri::YubikeyPiv(key) => {
|
||||||
|
let mut yk = yubikeyutil::open_yubikey_with_serial(serial_opt)?;
|
||||||
|
if let Some(key) = find_key_or_error(&mut yk, &key.slot)? {
|
||||||
|
let cert_der = key.certificate().cert.to_der()?;
|
||||||
|
let x509_certificate = parse_x509_certificate(cert_der.as_slice()).unwrap().1;
|
||||||
|
let public_key_bytes = x509_certificate.public_key().raw;
|
||||||
|
return Ok(public_key_bytes.to_vec());
|
||||||
|
}
|
||||||
|
simple_error!("Slot {} not found", key.slot)
|
||||||
|
}
|
||||||
|
KeyUri::YubikeyHmacEncSoft(key) => {
|
||||||
|
if key.algorithm.is_ecc() {
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let p256_public_key = ecdsautil::parse_p256_private_key_to_public_key(&private_key).ok();
|
||||||
|
let p384_public_key = ecdsautil::parse_p384_private_key_to_public_key(&private_key).ok();
|
||||||
|
let p521_public_key = ecdsautil::parse_p521_private_key_to_public_key(&private_key).ok();
|
||||||
|
|
||||||
|
if let Some(p256_public_key) = p256_public_key {
|
||||||
|
return Ok(p256_public_key);
|
||||||
|
}
|
||||||
|
if let Some(p384_public_key) = p384_public_key {
|
||||||
|
return Ok(p384_public_key);
|
||||||
|
}
|
||||||
|
if let Some(p521_public_key) = p521_public_key {
|
||||||
|
return Ok(p521_public_key);
|
||||||
|
}
|
||||||
|
simple_error!("Invalid hmac enc private key")
|
||||||
|
} else if key.algorithm.is_rsa() {
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let private_key_der = base64_decode(&private_key)?;
|
||||||
|
let rsa_private_key = RsaPrivateKey::from_pkcs8_der(&private_key_der)?;
|
||||||
|
Ok(rsa_private_key.to_public_key().to_public_key_der()?.to_vec())
|
||||||
|
} else if key.algorithm.is_mlkem() {
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let private_key_der = base64_decode(&private_key)?;
|
||||||
|
let (_, ek_public) = try_parse_decapsulate_key_private_get_encapsulate(&private_key_der)?;
|
||||||
|
if generate_shared_secret {
|
||||||
|
if let Ok((mlkem_len, ciphertext, shared_secret)) = try_parse_encapsulation_key_public_then_encapsulate(&ek_public) {
|
||||||
|
json.insert("algorithm", mlkem_len.to_str().into());
|
||||||
|
json.insert("ciphertext", base64_encode(ciphertext).into());
|
||||||
|
json.insert("shared_secret", hex::encode(&shared_secret).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(ek_public)
|
||||||
|
} else {
|
||||||
|
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyUri::ExternalCommand(key) => {
|
||||||
|
let parameter = cmd_hmac_decrypt::try_decrypt(&mut None, &key.parameter)?;
|
||||||
|
external_command_rs::external_public_key(&key.external_command, ¶meter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
169
src/cmd_external_sign.rs
Normal file
169
src/cmd_external_sign.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
use crate::cmd_sign_jwt_piv::digest_by_jwt_algorithm;
|
||||||
|
use crate::cmd_sign_jwt_soft::{convert_jwt_algorithm_to_ecdsa_algorithm, parse_ecdsa_private_key};
|
||||||
|
use crate::ecdsautil::EcdsaSignType;
|
||||||
|
use crate::keyutil::{parse_key_uri, KeyAlgorithmId, KeyUri, KeyUsage, YubikeyPivKey};
|
||||||
|
use crate::pivutil::ToStr;
|
||||||
|
use crate::rsautil::RsaSignAlgorithm;
|
||||||
|
use crate::util::{base64_decode, base64_encode};
|
||||||
|
use crate::{cmd_hmac_decrypt, cmdutil, ecdsautil, pivutil, rsautil, seutil, util, yubikeyutil};
|
||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
|
use jwt::AlgorithmType;
|
||||||
|
use rsa::RsaPrivateKey;
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use swift_secure_enclave_tool_rs::DigestType;
|
||||||
|
use yubikey::piv::sign_data;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"external_sign"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("External sign subcommand")
|
||||||
|
.arg(cmdutil::build_alg_arg())
|
||||||
|
.arg(cmdutil::build_parameter_arg())
|
||||||
|
.arg(cmdutil::build_message_arg())
|
||||||
|
.arg(cmdutil::build_message_type_arg())
|
||||||
|
.arg(cmdutil::build_pin_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let alg = sub_arg_matches.value_of("alg").unwrap();
|
||||||
|
let parameter = sub_arg_matches.value_of("parameter").unwrap();
|
||||||
|
let message_base64 = sub_arg_matches.value_of("message-base64").unwrap();
|
||||||
|
let message_type = sub_arg_matches.value_of("message-type");
|
||||||
|
let message_bytes = base64_decode(message_base64)?;
|
||||||
|
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
|
let key_uri = parse_key_uri(parameter)?;
|
||||||
|
match sign(alg, &message_bytes, message_type, key_uri, sub_arg_matches) {
|
||||||
|
Ok(signature_bytes) => {
|
||||||
|
json.insert("success", Value::Bool(true));
|
||||||
|
json.insert("signature_base64", base64_encode(&signature_bytes).into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
json.insert("success", Value::Bool(false));
|
||||||
|
json.insert("error", e.to_string().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(alg: &str, message: &[u8], message_type: Option<&str>, key_uri: KeyUri, sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
|
||||||
|
let digest_type = DigestType::parse(message_type)?;
|
||||||
|
if let Some(bytes_len) = digest_type.bytes() {
|
||||||
|
if message.len() != bytes_len as usize {
|
||||||
|
return simple_error!("Invalid message length, requires: {}, actual: {}", bytes_len, message.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let is_raw = DigestType::Raw == digest_type;
|
||||||
|
match key_uri {
|
||||||
|
KeyUri::SecureEnclave(key) => {
|
||||||
|
if "ES256" != alg {
|
||||||
|
return simple_error!("Invalid alg: {}", alg);
|
||||||
|
}
|
||||||
|
if key.usage != KeyUsage::Singing {
|
||||||
|
return simple_error!("Not singing key");
|
||||||
|
}
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.private_key)?;
|
||||||
|
seutil::secure_enclave_p256_sign(&private_key, message, digest_type)
|
||||||
|
}
|
||||||
|
KeyUri::YubikeyPiv(key) => {
|
||||||
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
|
let pin_opt = pivutil::check_read_pin(&mut yk, key.slot, sub_arg_matches);
|
||||||
|
|
||||||
|
// FIXME Check YubiKey slot algorithm
|
||||||
|
let jwt_algorithm = get_jwt_algorithm(&key, alg)?;
|
||||||
|
|
||||||
|
if let Some(pin) = pin_opt {
|
||||||
|
opt_result!(
|
||||||
|
yk.verify_pin(pin.as_bytes()),
|
||||||
|
"YubiKey verify pin failed: {}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let algorithm = opt_value_result!(
|
||||||
|
KeyAlgorithmId::to_algorithm_id(key.algorithm),
|
||||||
|
"Yubikey not supported algorithm: {}",
|
||||||
|
key.algorithm.to_str()
|
||||||
|
);
|
||||||
|
let raw_in = iff!(is_raw, digest_by_jwt_algorithm(jwt_algorithm, message)?, message.to_vec());
|
||||||
|
let signed_data = opt_result!(
|
||||||
|
sign_data(&mut yk, &raw_in, algorithm, key.slot),
|
||||||
|
"Sign YubiKey failed: {}"
|
||||||
|
);
|
||||||
|
Ok(signed_data.to_vec())
|
||||||
|
}
|
||||||
|
KeyUri::YubikeyHmacEncSoft(key) => {
|
||||||
|
if key.algorithm.is_ecc() {
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let (jwt_algorithm, private_key_d) = parse_ecdsa_private_key(&private_key)?;
|
||||||
|
|
||||||
|
let raw_in = iff!(is_raw, digest_by_jwt_algorithm(jwt_algorithm, message)?, message.to_vec());
|
||||||
|
let ecdsa_algorithm = convert_jwt_algorithm_to_ecdsa_algorithm(jwt_algorithm)?;
|
||||||
|
let signed_data = ecdsautil::ecdsa_sign(
|
||||||
|
ecdsa_algorithm,
|
||||||
|
&private_key_d,
|
||||||
|
&raw_in,
|
||||||
|
EcdsaSignType::Der,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(signed_data)
|
||||||
|
} else if key.algorithm.is_rsa() {
|
||||||
|
use rsa::pkcs8::DecodePrivateKey;
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &key.hmac_enc_private_key)?;
|
||||||
|
let private_key_der = base64_decode(&private_key)?;
|
||||||
|
let rsa_private_key = RsaPrivateKey::from_pkcs8_der(&private_key_der)?;
|
||||||
|
|
||||||
|
let rsa_sign_algorithm =
|
||||||
|
opt_value_result!(RsaSignAlgorithm::from_str(alg), "Invalid --alg: {}", alg);
|
||||||
|
rsautil::sign(&rsa_private_key, rsa_sign_algorithm, message, is_raw)
|
||||||
|
} else {
|
||||||
|
simple_error!("Invalid algorithm: {}", key.algorithm.to_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyUri::ExternalCommand(key) => {
|
||||||
|
let parameter = cmd_hmac_decrypt::try_decrypt(&mut None, &key.parameter)?;
|
||||||
|
let alg = key.algorithm.as_jwa_name();
|
||||||
|
let signature = external_command_rs::external_sign_digested(
|
||||||
|
&key.external_command, ¶meter, alg, message, digest_type.to_str())?;
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_jwt_algorithm(key: &YubikeyPivKey, alg: &str) -> XResult<AlgorithmType> {
|
||||||
|
let jwt_algorithm = match alg {
|
||||||
|
"ES256" => AlgorithmType::Es256,
|
||||||
|
"ES384" => AlgorithmType::Es384,
|
||||||
|
"ES512" => AlgorithmType::Es512,
|
||||||
|
"RS256" => AlgorithmType::Rs256,
|
||||||
|
_ => return simple_error!("Invalid alg: {}", alg),
|
||||||
|
};
|
||||||
|
if key.algorithm == KeyAlgorithmId::Rsa1024 {
|
||||||
|
return simple_error!("Invalid algorithm: RSA1024");
|
||||||
|
}
|
||||||
|
let is_p256_mismatch =
|
||||||
|
key.algorithm == KeyAlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256;
|
||||||
|
let is_p384_mismatch =
|
||||||
|
key.algorithm == KeyAlgorithmId::EccP384 && jwt_algorithm != AlgorithmType::Es384;
|
||||||
|
let is_p521_mismatch =
|
||||||
|
key.algorithm == KeyAlgorithmId::EccP521 && jwt_algorithm != AlgorithmType::Es512;
|
||||||
|
let is_rsa_mismatch =
|
||||||
|
key.algorithm == KeyAlgorithmId::Rsa2048 && jwt_algorithm != AlgorithmType::Rs256;
|
||||||
|
|
||||||
|
if is_p256_mismatch || is_p384_mismatch || is_p521_mismatch || is_rsa_mismatch {
|
||||||
|
return simple_error!("Invalid algorithm: {} vs {}", key.algorithm.to_str(), alg);
|
||||||
|
}
|
||||||
|
Ok(jwt_algorithm)
|
||||||
|
}
|
||||||
40
src/cmd_external_spec.rs
Normal file
40
src/cmd_external_spec.rs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
use crate::util;
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
// https://openwebstandard.org/rfc1
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"external_spec"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name()).about("External spec subcommand")
|
||||||
|
.arg(Arg::with_name("external-command").long("external-command").takes_value(true).help("External command"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let external_command_opt = sub_arg_matches.value_of("external-command");
|
||||||
|
|
||||||
|
if let Some(external_command) = external_command_opt {
|
||||||
|
let spec = external_command_rs::external_spec(external_command)?;
|
||||||
|
util::print_pretty_json(&spec);
|
||||||
|
} else {
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
|
json.insert("success", Value::Bool(true));
|
||||||
|
json.insert(
|
||||||
|
"agent",
|
||||||
|
format!("card-external-provider/{}", env!("CARGO_PKG_VERSION")).into(),
|
||||||
|
);
|
||||||
|
json.insert("specification", "External/1.0.0-alpha".into());
|
||||||
|
json.insert("commands", vec!["external_public_key", "external_sign", "external_ecdh"].into());
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,8 @@ use x509_parser::nom::AsBytes;
|
|||||||
use yubikey::{Key, YubiKey};
|
use yubikey::{Key, YubiKey};
|
||||||
use yubikey::piv::{sign_data, SlotId};
|
use yubikey::piv::{sign_data, SlotId};
|
||||||
|
|
||||||
use crate::{argsutil, pinutil, pivutil};
|
use crate::{argsutil, cmdutil, pinutil, pivutil};
|
||||||
use crate::digest::sha256_bytes;
|
use crate::digestutil::sha256_bytes;
|
||||||
use crate::signfile::{CERTIFICATES_SEARCH_URL, HASH_ALGORITHM_SHA256, SIGNATURE_ALGORITHM_SHA256_WITH_ECDSA, SignFileRequest, SIMPLE_SIG_SCHEMA, SimpleSignFile, SimpleSignFileSignature};
|
use crate::signfile::{CERTIFICATES_SEARCH_URL, HASH_ALGORITHM_SHA256, SIGNATURE_ALGORITHM_SHA256_WITH_ECDSA, SignFileRequest, SIMPLE_SIG_SCHEMA, SimpleSignFile, SimpleSignFileSignature};
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
@@ -36,14 +36,13 @@ pub struct CommandImpl;
|
|||||||
// all hex is in lower case default
|
// all hex is in lower case default
|
||||||
// file ext: *.simple-sig
|
// file ext: *.simple-sig
|
||||||
impl Command for CommandImpl {
|
impl Command for CommandImpl {
|
||||||
fn name(&self) -> &str { "sign-file" }
|
fn name(&self) -> &str { "file-sign" }
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV sign(with SHA256) subcommand")
|
SubCommand::with_name(self.name()).about("PIV sign(with SHA256) subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot")
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.takes_value(true).required(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
|
||||||
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).required(true).help("Input file"))
|
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).required(true).help("Input file"))
|
||||||
.arg(Arg::with_name("filename").short("n").long("filename").takes_value(true).help("Filename"))
|
.arg(Arg::with_name("filename").short("n").long("filename").takes_value(true).help("Filename"))
|
||||||
.arg(Arg::with_name("sign-file").short("S").long("sign-file").takes_value(false).help("Sign file"))
|
.arg(Arg::with_name("sign-file").short("S").long("sign-file").takes_value(false).help("Sign file"))
|
||||||
@@ -11,14 +11,14 @@ use x509_parser::public_key::PublicKey;
|
|||||||
use x509_parser::time::ASN1Time;
|
use x509_parser::time::ASN1Time;
|
||||||
|
|
||||||
use crate::argsutil;
|
use crate::argsutil;
|
||||||
use crate::digest::sha256_bytes;
|
use crate::digestutil::sha256_bytes;
|
||||||
use crate::signfile::{SignFileRequest, SIMPLE_SIG_SCHEMA, SimpleSignFile};
|
use crate::signfile::{SignFileRequest, SIMPLE_SIG_SCHEMA, SimpleSignFile};
|
||||||
use crate::util::base64_decode;
|
use crate::util::base64_decode;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
impl Command for CommandImpl {
|
||||||
fn name(&self) -> &str { "verify-file" }
|
fn name(&self) -> &str { "file-verify" }
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV verify(with SHA256) subcommand")
|
SubCommand::with_name(self.name()).about("PIV verify(with SHA256) subcommand")
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
use crate::ecdsautil;
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
|
||||||
use rust_util::util_msg;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"generate-keypair"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
|
||||||
SubCommand::with_name(self.name())
|
|
||||||
.about("Generate software keypair")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("type")
|
|
||||||
.long("type")
|
|
||||||
.required(true)
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Key type (e.g. p256, p384)"),
|
|
||||||
)
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
|
||||||
let key_type = sub_arg_matches.value_of("type").unwrap().to_lowercase();
|
|
||||||
|
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (pkcs8_base64, secret_key_pem, public_key_pem) = match key_type.as_str() {
|
|
||||||
"p256" => ecdsautil::generate_p256_keypair()?,
|
|
||||||
"p384" => ecdsautil::generate_p384_keypair()?,
|
|
||||||
_ => {
|
|
||||||
return simple_error!("Key type must be p256 or p384");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if json_output {
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
json.insert("private_key_base64", pkcs8_base64);
|
|
||||||
json.insert("private_key_pem", secret_key_pem);
|
|
||||||
json.insert("public_key_pem", public_key_pem);
|
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
} else {
|
|
||||||
information!("Private key base64:\n{}\n", pkcs8_base64);
|
|
||||||
information!("Private key PEM:\n{}\n", secret_key_pem);
|
|
||||||
information!("Public key PEM:\n{}", public_key_pem);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
70
src/cmd_hmac_decrypt.rs
Normal file
70
src/cmd_hmac_decrypt.rs
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use rust_util::XResult;
|
||||||
|
use crate::{cmdutil, pbeutil, util};
|
||||||
|
use crate::hmacutil::{hmac_decrypt_to_string, is_hmac_encrypted};
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hmac-decrypt"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("YubiKey HMAC decrypt")
|
||||||
|
.arg(Arg::with_name("ciphertext").long("ciphertext").short("t").takes_value(true).required(true).help("Ciphertext"), )
|
||||||
|
.arg(Arg::with_name("auto-pbe").long("auto-pbe").help("Auto PBE decryption"))
|
||||||
|
.arg(Arg::with_name("password").long("password").short("P").takes_value(true).help("Password"))
|
||||||
|
.arg(Arg::with_name("outputs-password").long("outputs-password").help("Outputs password"))
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let ciphertext = sub_arg_matches.value_of("ciphertext").unwrap();
|
||||||
|
let mut pin_opt = sub_arg_matches.value_of("password").map(|p| p.to_string());
|
||||||
|
let auto_pbe = sub_arg_matches.is_present("auto-pbe");
|
||||||
|
let outputs_password = sub_arg_matches.is_present("outputs-password");
|
||||||
|
|
||||||
|
let text = try_decrypt_with_pbe_option(&mut pin_opt, ciphertext, auto_pbe)?;
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
json.insert("plaintext", text);
|
||||||
|
if let (true, Some(pin)) = (outputs_password, pin_opt.as_ref()) {
|
||||||
|
json.insert("password", pin.to_string());
|
||||||
|
}
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
success!("Plaintext: {}", text);
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_decrypt(pin_opt: &mut Option<String>,ciphertext: &str) -> XResult<String> {
|
||||||
|
try_decrypt_with_pbe_option(pin_opt, ciphertext, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_decrypt_with_pbe_option(pin_opt: &mut Option<String>, ciphertext: &str, auto_pbe: bool) -> XResult<String> {
|
||||||
|
if is_hmac_encrypted(ciphertext) {
|
||||||
|
hmac_decrypt(pin_opt, ciphertext, auto_pbe)
|
||||||
|
} else if pbeutil::is_simple_pbe_encrypted(ciphertext) {
|
||||||
|
pbeutil::simple_pbe_decrypt_with_prompt_to_string(pin_opt, ciphertext)
|
||||||
|
} else {
|
||||||
|
Ok(ciphertext.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hmac_decrypt(pin_opt: &mut Option<String>, ciphertext: &str, auto_pbe: bool) -> XResult<String> {
|
||||||
|
let text = hmac_decrypt_to_string(ciphertext)?;
|
||||||
|
if auto_pbe && pbeutil::is_simple_pbe_encrypted(&text) {
|
||||||
|
pbeutil::simple_pbe_decrypt_with_prompt_to_string(pin_opt, &text)
|
||||||
|
} else {
|
||||||
|
Ok(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/cmd_hmac_encrypt.rs
Normal file
81
src/cmd_hmac_encrypt.rs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use rust_util::XResult;
|
||||||
|
use crate::{cmdutil, hmacutil, pbeutil, util};
|
||||||
|
use crate::pinutil::get_pin;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"hmac-encrypt"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("YubiKey HMAC encrypt")
|
||||||
|
.arg(Arg::with_name("plaintext").long("plaintext").short("t").takes_value(true).required(true).help("Plaintext, @@PIN_ENTRY@@ means read from pin entry"))
|
||||||
|
.arg(Arg::with_name("password").long("password").short("P").takes_value(true).help("Password"))
|
||||||
|
.arg(cmdutil::build_with_pbe_encrypt_arg())
|
||||||
|
.arg(cmdutil::build_double_pin_check_arg())
|
||||||
|
.arg(cmdutil::build_pbe_iteration_arg())
|
||||||
|
.arg(Arg::with_name("without-hmac-encrypt").long("without-hmac-encrypt").help("Without HMAC encrypt"))
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
let without_hmac_encrypt = sub_arg_matches.is_present("without-hmac-encrypt");
|
||||||
|
if without_hmac_encrypt && !sub_arg_matches.is_present("with-pbe-encrypt") {
|
||||||
|
return simple_error!("hmac and pbe encryption must present at least one");
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = sub_arg_matches.value_of("plaintext").unwrap().to_string();
|
||||||
|
let text = if text == "@@PIN_ENTRY@@" {
|
||||||
|
match get_pin(None) {
|
||||||
|
None => return simple_error!(""),
|
||||||
|
Some(text) => text,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
let mut pin_opt = sub_arg_matches.value_of("password").map(|p| p.to_string());
|
||||||
|
let ciphertext = do_encrypt(&text, &mut pin_opt, sub_arg_matches)?;
|
||||||
|
|
||||||
|
let ciphertext = if without_hmac_encrypt {
|
||||||
|
ciphertext
|
||||||
|
} else {
|
||||||
|
hmacutil::hmac_encrypt_from_string(&ciphertext)?
|
||||||
|
};
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
json.insert("ciphertext", ciphertext);
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
success!("HMAC encrypt ciphertext: {}", ciphertext);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn do_encrypt(text: &str, password_opt: &mut Option<String>, sub_arg_matches: &ArgMatches) -> XResult<String> {
|
||||||
|
let with_hmac_encrypt = sub_arg_matches.is_present("with-hmac-encrypt");
|
||||||
|
let with_pbe_encrypt = sub_arg_matches.is_present("with-pbe-encrypt");
|
||||||
|
let text = if with_pbe_encrypt {
|
||||||
|
let double_pin_check = sub_arg_matches.is_present("double-pin-check");
|
||||||
|
let iteration = sub_arg_matches.value_of("pbe-iteration")
|
||||||
|
.map(|x| x.parse::<u32>().unwrap()).unwrap_or(100000);
|
||||||
|
pbeutil::simple_pbe_encrypt_with_prompt_from_string(iteration, text, password_opt, double_pin_check)?
|
||||||
|
} else {
|
||||||
|
text.to_string()
|
||||||
|
};
|
||||||
|
if with_hmac_encrypt {
|
||||||
|
Ok(hmacutil::hmac_encrypt_from_string(&text)?)
|
||||||
|
} else {
|
||||||
|
Ok(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::XResult;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
|
||||||
use crate::hmacutil;
|
use crate::{cmdutil, hmacutil};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -19,12 +19,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("sha256").short("2").long("sha256").help("Output SHA256"))
|
.arg(Arg::with_name("sha256").short("2").long("sha256").help("Output SHA256"))
|
||||||
.arg(Arg::with_name("sha384").short("3").long("sha384").help("Output SHA256"))
|
.arg(Arg::with_name("sha384").short("3").long("sha384").help("Output SHA256"))
|
||||||
.arg(Arg::with_name("sha512").short("5").long("sha512").help("Output SHA256"))
|
.arg(Arg::with_name("sha512").short("5").long("sha512").help("Output SHA256"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let variable = sub_arg_matches.is_present("variable");
|
let variable = sub_arg_matches.is_present("variable");
|
||||||
let secret_bytes = get_secret_bytes(sub_arg_matches)?;
|
let secret_bytes = get_secret_bytes(sub_arg_matches)?;
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
|
||||||
use rust_util::util_msg;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use crate::hmacutil;
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"hmac-decrypt"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
|
||||||
SubCommand::with_name(self.name())
|
|
||||||
.about("Yubikey HMAC decrypt")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("ciphertext")
|
|
||||||
.long("ciphertext")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Ciphertext"),
|
|
||||||
)
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let ciphertext = sub_arg_matches.value_of("ciphertext").unwrap();
|
|
||||||
let plaintext = hmacutil::hmac_decrypt_to_string(ciphertext)?;
|
|
||||||
|
|
||||||
if json_output {
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
json.insert("plaintext", plaintext);
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
serde_json::to_string_pretty(&json).expect("Convert to JSON failed!")
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
success!("Plaintext: {}", plaintext);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
|
||||||
use rust_util::util_msg;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use crate::hmacutil;
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"hmac-encrypt"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
|
||||||
SubCommand::with_name(self.name())
|
|
||||||
.about("Yubikey HMAC encrypt")
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("plaintext")
|
|
||||||
.long("plaintext")
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Plaintext"),
|
|
||||||
)
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let plaintext = sub_arg_matches.value_of("plaintext").unwrap();
|
|
||||||
let hmac_encrypt_ciphertext = hmacutil::hmac_encrypt_from_string(plaintext)?;
|
|
||||||
|
|
||||||
if json_output {
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
json.insert("ciphertext", hmac_encrypt_ciphertext);
|
|
||||||
println!(
|
|
||||||
"{}",
|
|
||||||
serde_json::to_string_pretty(&json).expect("Convert to JSON failed!")
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
success!("HMAC encrypt ciphertext: {}", hmac_encrypt_ciphertext);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
src/cmd_keypair_generate.rs
Normal file
169
src/cmd_keypair_generate.rs
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
use crate::ecdsautil::EcdsaAlgorithm;
|
||||||
|
use crate::keychain::{KeychainKey, KeychainKeyValue};
|
||||||
|
use crate::keyutil::{KeyAlgorithmId, KeyUri, YubikeyHmacEncSoftKey};
|
||||||
|
use crate::pivutil::FromStr;
|
||||||
|
use crate::util::base64_encode;
|
||||||
|
use crate::{cmd_hmac_encrypt, cmdutil, ecdsautil, hmacutil, mlkemutil, pbeutil, rsautil, util, yubikeyutil};
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"keypair-generate"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("Generate software keypair")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("type")
|
||||||
|
.long("type")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Key type (e.g. p256, p384, p521, rsa1024, rsa2048, rsa3072, rsa4096, mlkem512, mlkem768, mlkem1024)"),
|
||||||
|
)
|
||||||
|
.arg(cmdutil::build_with_hmac_encrypt_arg())
|
||||||
|
.arg(cmdutil::build_with_pbe_encrypt_arg())
|
||||||
|
.arg(cmdutil::build_double_pin_check_arg())
|
||||||
|
.arg(cmdutil::build_pbe_iteration_arg())
|
||||||
|
.arg(cmdutil::build_keychain_name_arg())
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let key_type = sub_arg_matches.value_of("type").unwrap().to_lowercase();
|
||||||
|
let keychain_name = sub_arg_matches.value_of("keychain-name");
|
||||||
|
|
||||||
|
if let Some(keychain_name) = keychain_name {
|
||||||
|
let keychain_key = KeychainKey::from_key_name_default(keychain_name);
|
||||||
|
if keychain_key.get_password()?.is_some() {
|
||||||
|
return simple_error!("Keychain key URI: {} exists", keychain_key.to_key_uri());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ecdsa_algorithm = match key_type.as_str() {
|
||||||
|
"p256" => Some(EcdsaAlgorithm::P256),
|
||||||
|
"p384" => Some(EcdsaAlgorithm::P384),
|
||||||
|
"p521" => Some(EcdsaAlgorithm::P521),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let rsa_bit_size: Option<usize> = match key_type.as_str() {
|
||||||
|
"rsa1024" => Some(1024),
|
||||||
|
"rsa2048" => Some(2048),
|
||||||
|
"rsa3072" => Some(3072),
|
||||||
|
"rsa4096" => Some(4096),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let mlkem_len: Option<usize> = match key_type.as_str() {
|
||||||
|
"mlkem512" => Some(512),
|
||||||
|
"mlkem768" => Some(768),
|
||||||
|
"mlkem1024" => Some(1024),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (pkcs8_base64, secret_key_pem, public_key_pem, public_key_der, jwk_key) =
|
||||||
|
if let Some(ecdsa_algorithm) = ecdsa_algorithm {
|
||||||
|
ecdsautil::generate_ecdsa_keypair(ecdsa_algorithm)?
|
||||||
|
} else if let Some(rsa_bit_size) = rsa_bit_size {
|
||||||
|
rsautil::generate_rsa_keypair(rsa_bit_size)?
|
||||||
|
} else if let Some(mlkem_len) = mlkem_len {
|
||||||
|
mlkemutil::generate_mlkem_keypair(mlkem_len)?
|
||||||
|
} else {
|
||||||
|
return simple_error!("Unsupported key type: {}", key_type);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut password_opt = None;
|
||||||
|
let (pkcs8_base64, secret_key_pem) = (
|
||||||
|
cmd_hmac_encrypt::do_encrypt(&pkcs8_base64, &mut password_opt, sub_arg_matches)?,
|
||||||
|
cmd_hmac_encrypt::do_encrypt(&secret_key_pem, &mut password_opt, sub_arg_matches)?,
|
||||||
|
);
|
||||||
|
let public_key_base64 = base64_encode(&public_key_der);
|
||||||
|
|
||||||
|
let keychain_key_uri = if let Some(keychain_name) = keychain_name {
|
||||||
|
let keychain_key_value = KeychainKeyValue {
|
||||||
|
keychain_name: keychain_name.to_string(),
|
||||||
|
pkcs8_base64: pkcs8_base64.clone(),
|
||||||
|
secret_key_pem: secret_key_pem.clone(),
|
||||||
|
public_key_pem: public_key_pem.clone(),
|
||||||
|
public_key_jwk: jwk_key.clone(),
|
||||||
|
};
|
||||||
|
let keychain_key_value_json = serde_json::to_string(&keychain_key_value)?;
|
||||||
|
|
||||||
|
let keychain_key = KeychainKey::from_key_name_default(keychain_name);
|
||||||
|
keychain_key.set_password(keychain_key_value_json.as_bytes())?;
|
||||||
|
Some(keychain_key.to_key_uri())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let algorithm_id = KeyAlgorithmId::from_str(&key_type);
|
||||||
|
|
||||||
|
let with_encrypt = hmacutil::is_hmac_encrypted(&pkcs8_base64)
|
||||||
|
|| pbeutil::is_simple_pbe_encrypted(&pkcs8_base64);
|
||||||
|
let yubikey_hmac_enc_soft_key_uri =
|
||||||
|
if let (true, Some(algorithm_id)) = (with_encrypt, algorithm_id) {
|
||||||
|
let yubikey_name = match yubikeyutil::open_yubikey() {
|
||||||
|
Ok(yk) => format!("yubikey{}-{}", yk.version().major, yk.serial().0),
|
||||||
|
Err(_) => "yubikey-unknown".to_string(),
|
||||||
|
};
|
||||||
|
let yubikey_hmac_enc_soft_key = YubikeyHmacEncSoftKey {
|
||||||
|
key_name: yubikey_name,
|
||||||
|
algorithm: algorithm_id,
|
||||||
|
hmac_enc_private_key: pkcs8_base64.clone(),
|
||||||
|
};
|
||||||
|
Some(KeyUri::YubikeyHmacEncSoft(yubikey_hmac_enc_soft_key).to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
match keychain_key_uri {
|
||||||
|
None => {
|
||||||
|
json.insert("private_key_base64", pkcs8_base64);
|
||||||
|
json.insert("private_key_pem", secret_key_pem);
|
||||||
|
|
||||||
|
if let Some(yubikey_hmac_enc_soft_key_uri) = yubikey_hmac_enc_soft_key_uri {
|
||||||
|
json.insert("key_uri", yubikey_hmac_enc_soft_key_uri.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(keychain_key_uri) => {
|
||||||
|
json.insert("keychain_key_uri", keychain_key_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json.insert("public_key_pem", public_key_pem);
|
||||||
|
json.insert("public_key_base64", public_key_base64);
|
||||||
|
if !jwk_key.is_empty() {
|
||||||
|
json.insert("public_key_jwk", jwk_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
match keychain_key_uri {
|
||||||
|
None => {
|
||||||
|
information!("Private key base64:\n{}\n", pkcs8_base64);
|
||||||
|
information!("Private key PEM:\n{}\n", secret_key_pem);
|
||||||
|
|
||||||
|
if let Some(yubikey_hmac_enc_soft_key_uri) = yubikey_hmac_enc_soft_key_uri {
|
||||||
|
information!("Key URI:\n{}\n", yubikey_hmac_enc_soft_key_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(keychain_key_uri) => {
|
||||||
|
information!("Keychain key URI:\n{}\n", keychain_key_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
information!("Public key PEM:\n{}", public_key_pem);
|
||||||
|
information!("Public key Base64:\n{}\n", public_key_base64);
|
||||||
|
if !jwk_key.is_empty() {
|
||||||
|
information!("Public key JWK:\n{}", jwk_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/cmd_keypair_keychain_export.rs
Normal file
38
src/cmd_keypair_keychain_export.rs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
use crate::{cmdutil, util};
|
||||||
|
use crate::keychain::{KeychainKey, KeychainKeyValue};
|
||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::util_msg;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"keypair-keychain-export"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("Export software keypair from keychain")
|
||||||
|
.arg(cmdutil::build_keychain_name_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let keychain_name = sub_arg_matches.value_of("keychain-name");
|
||||||
|
|
||||||
|
if let Some(keychain_name) = keychain_name {
|
||||||
|
let keychain_key = KeychainKey::from_key_name_default(keychain_name);
|
||||||
|
if let Some(keychain_key_value_bytes) = keychain_key.get_password()? {
|
||||||
|
let keychain_key_value: KeychainKeyValue =
|
||||||
|
serde_json::from_slice(&keychain_key_value_bytes)?;
|
||||||
|
util_msg::set_logger_std_out(false);
|
||||||
|
information!("Keychain key URI: {}", keychain_key.to_key_uri());
|
||||||
|
util::print_pretty_json(&keychain_key_value);
|
||||||
|
} else {
|
||||||
|
return simple_error!("Keychain key URI: {} not found", keychain_key.to_key_uri());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/cmd_keypair_keychain_import.rs
Normal file
42
src/cmd_keypair_keychain_import.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::cmdutil;
|
||||||
|
use crate::keychain::KeychainKey;
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"keypair-keychain-import"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("Import software keypair to keychain")
|
||||||
|
.arg(cmdutil::build_keychain_name_arg())
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("import-key-value")
|
||||||
|
.long("import-key-value")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Import key value"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let keychain_name = sub_arg_matches.value_of("keychain-name");
|
||||||
|
let import_key_value = sub_arg_matches.value_of("import-key-value");
|
||||||
|
|
||||||
|
if let Some(keychain_name) = keychain_name {
|
||||||
|
let keychain_key = KeychainKey::from_key_name_default(keychain_name);
|
||||||
|
if keychain_key.get_password()?.is_some() {
|
||||||
|
return simple_error!("Keychain key URI: {} exists", keychain_key.to_key_uri());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(import_key_value) = import_key_value {
|
||||||
|
keychain_key.set_password(import_key_value.as_bytes())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use serde_json::Value;
|
||||||
use yubikey::YubiKey;
|
use crate::{cmdutil, util, yubikeyutil};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -12,34 +12,37 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("YubiKey list")
|
SubCommand::with_name(self.name()).about("YubiKey list")
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, Value>::new();
|
||||||
json.insert("name", yk.name().to_string());
|
json.insert("name", yk.name().into());
|
||||||
json.insert("version", yk.version().to_string());
|
json.insert("version", yk.version().to_string().into());
|
||||||
json.insert("serial", yk.serial().0.to_string());
|
json.insert("serial", yk.serial().0.into());
|
||||||
if let Ok(pin_retries) = yk.get_pin_retries() {
|
if let Ok(pin_retries) = yk.get_pin_retries() {
|
||||||
json.insert("pin_retries", pin_retries.to_string());
|
json.insert("pin_retries", pin_retries.into());
|
||||||
}
|
}
|
||||||
if let Ok(chuid) = yk.chuid() {
|
if let Ok(chuid) = yk.chuid() {
|
||||||
json.insert("chuid", chuid.to_string());
|
json.insert("chuid", chuid.to_string().into());
|
||||||
}
|
}
|
||||||
if let Ok(ccuid) = yk.cccid() {
|
if let Ok(ccuid) = yk.cccid() {
|
||||||
json.insert("ccuid", ccuid.to_string());
|
json.insert("ccuid", ccuid.to_string().into());
|
||||||
}
|
}
|
||||||
if let Ok(piv_keys) = yk.piv_keys() {
|
if let Ok(piv_keys) = yk.piv_keys() {
|
||||||
json.insert("keys", piv_keys.iter().map(|k| format!("{}", k.slot())).collect::<Vec<_>>().join(", "));
|
let key_list = piv_keys.iter().map(|k| Value::String(format!("{}", k.slot()))).collect::<Vec<_>>();
|
||||||
|
json.insert("key_list", key_list.into());
|
||||||
|
let keys = piv_keys.iter().map(|k| format!("{}", k.slot())).collect::<Vec<_>>().join(", ");
|
||||||
|
json.insert("keys", keys.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).expect("Convert to JSON failed!"));
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
success!("Name: {}", yk.name());
|
success!("Name: {}", yk.name());
|
||||||
success!("Version: {}", yk.version());
|
success!("Version: {}", yk.version());
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use std::collections::BTreeMap;
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use crate::{cmdutil, util};
|
||||||
|
|
||||||
use crate::ecdsautil::parse_ecdsa_r_and_s;
|
use crate::ecdsautil::parse_ecdsa_r_and_s;
|
||||||
use crate::util::try_decode;
|
use crate::util::try_decode;
|
||||||
|
|
||||||
@@ -25,14 +24,11 @@ impl Command for CommandImpl {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("ECDSA signature"),
|
.help("ECDSA signature"),
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
|
||||||
@@ -55,7 +51,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
use chrono::{DateTime, Local};
|
use chrono::{DateTime, Local};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
@@ -21,7 +19,7 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("in").short("i").long("in").takes_value(true).help("File input, *.pgp or *.asc"))
|
.arg(Arg::with_name("in").short("i").long("in").takes_value(true).help("File input, *.pgp or *.asc"))
|
||||||
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
|
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
|
||||||
.arg(Arg::with_name("verbose").long("verbose").help("Verbose output"))
|
.arg(Arg::with_name("verbose").long("verbose").help("Verbose output"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
// .arg(Arg::with_name("json").long("json").help("JSON output"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
@@ -151,7 +149,7 @@ impl Command for CommandImpl {
|
|||||||
debugging!("Found PKESK: {:?}", pkesk);
|
debugging!("Found PKESK: {:?}", pkesk);
|
||||||
match pkesk {
|
match pkesk {
|
||||||
PKESK::V3(pkesk3) => {
|
PKESK::V3(pkesk3) => {
|
||||||
information!("Found public key encrypted session key, key ID: {}, alog: {}", pkesk3.recipient(), pkesk3.pk_algo());
|
information!("Found public key encrypted session key, key ID: {:?}, alog: {}", pkesk3.recipient(), pkesk3.pk_algo());
|
||||||
}
|
}
|
||||||
unknown => warning!("Unknown PKESK: {:?}", unknown),
|
unknown => warning!("Unknown PKESK: {:?}", unknown),
|
||||||
}
|
}
|
||||||
@@ -162,19 +160,21 @@ impl Command for CommandImpl {
|
|||||||
Packet::SEIP(seip) => {
|
Packet::SEIP(seip) => {
|
||||||
debugging!("Found SEIP: {:?}", seip);
|
debugging!("Found SEIP: {:?}", seip);
|
||||||
match seip {
|
match seip {
|
||||||
SEIP::V1(seip1) => match seip1.deref().body() {
|
SEIP::V1(seip1) => match seip1.body() {
|
||||||
Body::Processed(b) | Body::Unprocessed(b) => information!("Found encrypted data, len: {} byte(s)", b.len()),
|
Body::Processed(b) | Body::Unprocessed(b) => information!("Found encrypted data, len: {} byte(s)", b.len()),
|
||||||
Body::Structured(b) => information!("Found encrypted data packages, len: {}", b.len()),
|
Body::Structured(b) => information!("Found encrypted data packages, len: {}", b.len()),
|
||||||
}
|
}
|
||||||
|
SEIP::V2(seip2) => match seip2.body() {
|
||||||
|
Body::Processed(b) | Body::Unprocessed(b) => information!("Found encrypted data, len: {} byte(s)", b.len()),
|
||||||
|
Body::Structured(b) => information!("Found encrypted data packages, len: {}", b.len()),
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
Packet::MDC(mdc) => {
|
Packet::MDC(mdc) => {
|
||||||
information!("Found MDC: {:?}", mdc);
|
information!("Found MDC: {:?}", mdc);
|
||||||
}
|
}
|
||||||
Packet::AED(aed) => {
|
|
||||||
information!("Found AED: {:?}", aed);
|
|
||||||
}
|
|
||||||
Packet::Unknown(unknown) => {
|
Packet::Unknown(unknown) => {
|
||||||
warning!("Found unknown: {:?}", unknown);
|
warning!("Found unknown: {:?}", unknown);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use bech32::{ToBase32, Variant};
|
use bech32::{ToBase32, Variant};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use openpgp_card::algorithm::{Algo, Curve};
|
use openpgp_card::algorithm::{Algo, Curve};
|
||||||
use openpgp_card::crypto_data::{EccType, PublicKeyMaterial};
|
use openpgp_card::crypto_data::{EccType, PublicKeyMaterial};
|
||||||
use openpgp_card::{KeyType, OpenPgp};
|
use openpgp_card::{KeyType, OpenPgp};
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use crate::{cmdutil, util};
|
||||||
|
|
||||||
const AGE_PUBLIC_KEY_PREFIX: &str = "age";
|
const AGE_PUBLIC_KEY_PREFIX: &str = "age";
|
||||||
|
|
||||||
@@ -17,12 +17,11 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("OpenPGP Card encryption key to age address")
|
SubCommand::with_name(self.name()).about("OpenPGP Card encryption key to age address")
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let cards = opt_result!(PcscBackend::cards(None), "Failed to list OpenPGP cards: {}");
|
let cards = opt_result!(PcscBackend::cards(None), "Failed to list OpenPGP cards: {}");
|
||||||
|
|
||||||
@@ -67,7 +66,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&cards_output).unwrap());
|
util::print_pretty_json(&cards_output);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,10 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use openpgp_card::crypto_data::Cryptogram;
|
use openpgp_card::crypto_data::Cryptogram;
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::XResult;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
|
||||||
use crate::{pgpcardutil, pinutil};
|
use crate::{cmdutil, pgpcardutil, pinutil, util};
|
||||||
use crate::util::{base64_encode, read_stdin, try_decode};
|
use crate::util::{base64_encode, read_stdin, try_decode};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -36,12 +36,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("ciphertext").short("c").long("ciphertext").takes_value(true).help("Cipher text (HEX or Base64)"))
|
.arg(Arg::with_name("ciphertext").short("c").long("ciphertext").takes_value(true).help("Cipher text (HEX or Base64)"))
|
||||||
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
|
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
|
||||||
.arg(Arg::with_name("algo").long("algo").takes_value(true).help("Algo: RSA, X25519/ECDH"))
|
.arg(Arg::with_name("algo").long("algo").takes_value(true).help("Algo: RSA, X25519/ECDH"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin"));
|
let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin"));
|
||||||
let pin_opt = pinutil::get_pin(pin_opt);
|
let pin_opt = pinutil::get_pin(pin_opt);
|
||||||
@@ -89,7 +88,7 @@ impl Command for CommandImpl {
|
|||||||
json.insert("text_utf8", text);
|
json.insert("text_utf8", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,7 @@ use clap::{App, Arg, ArgMatches, SubCommand};
|
|||||||
use openpgp_card::{KeyType, OpenPgp};
|
use openpgp_card::{KeyType, OpenPgp};
|
||||||
use openpgp_card_pcsc::PcscBackend;
|
use openpgp_card_pcsc::PcscBackend;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use crate::{cmdutil, util};
|
||||||
|
|
||||||
use crate::pkiutil::openpgp_card_public_key_pem as public_key_pem;
|
use crate::pkiutil::openpgp_card_public_key_pem as public_key_pem;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -16,13 +15,13 @@ impl Command for CommandImpl {
|
|||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("OpenPGP Card list subcommand")
|
SubCommand::with_name(self.name()).about("OpenPGP Card list subcommand")
|
||||||
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
|
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
let detail_output = sub_arg_matches.is_present("detail");
|
let detail_output = sub_arg_matches.is_present("detail");
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let mut jsons = vec![];
|
let mut jsons = vec![];
|
||||||
let cards = opt_result!(PcscBackend::cards(None), "Failed to list OpenPGP cards: {}");
|
let cards = opt_result!(PcscBackend::cards(None), "Failed to list OpenPGP cards: {}");
|
||||||
@@ -136,7 +135,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&jsons).unwrap());
|
util::print_pretty_json(&jsons);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{ErrorKind, Read};
|
use std::io::{ErrorKind, Read};
|
||||||
|
use std::ops::Deref;
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use openpgp_card::crypto_data::Hash;
|
use openpgp_card::crypto_data::Hash;
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::XResult;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use sha2::{Sha256, Sha384, Sha512};
|
use sha2::{Sha256, Sha384, Sha512};
|
||||||
|
|
||||||
use crate::{pgpcardutil, pinutil};
|
use crate::{cmdutil, pgpcardutil, pinutil, util};
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
const BUFF_SIZE: usize = 512 * 1024;
|
const BUFF_SIZE: usize = 512 * 1024;
|
||||||
@@ -49,12 +49,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("use-sha384").long("use-sha384").help("Use SHA384 for file in"))
|
.arg(Arg::with_name("use-sha384").long("use-sha384").help("Use SHA384 for file in"))
|
||||||
.arg(Arg::with_name("use-sha512").long("use-sha512").help("Use SHA512 for file in"))
|
.arg(Arg::with_name("use-sha512").long("use-sha512").help("Use SHA512 for file in"))
|
||||||
.arg(Arg::with_name("algo").long("algo").takes_value(true).help("Algorithm, rsa, ecdsa, eddsa"))
|
.arg(Arg::with_name("algo").long("algo").takes_value(true).help("Algorithm, rsa, ecdsa, eddsa"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin"));
|
let pin_opt = sub_arg_matches.value_of("pass").or_else(|| sub_arg_matches.value_of("pin"));
|
||||||
let pin_opt = pinutil::get_pin(pin_opt);
|
let pin_opt = pinutil::get_pin(pin_opt);
|
||||||
@@ -115,7 +114,7 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
if let Some(sha256) = sha256 {
|
if let Some(sha256) = sha256 {
|
||||||
let sha256_hex = opt_result!(hex::decode(sha256.trim()), "Decode sha256 failed: {}");
|
let sha256_hex = opt_result!(hex::decode(sha256.trim()), "Decode sha256 failed: {}");
|
||||||
let sha256_hex = crate::digest::copy_sha256(&sha256_hex)?;
|
let sha256_hex = crate::digestutil::copy_sha256(&sha256_hex)?;
|
||||||
opt_result!(trans.verify_pw1_sign(pin.as_ref()), "User sign pin verify failed: {}");
|
opt_result!(trans.verify_pw1_sign(pin.as_ref()), "User sign pin verify failed: {}");
|
||||||
success!("User sign pin verify success!");
|
success!("User sign pin verify success!");
|
||||||
let sig = match algo {
|
let sig = match algo {
|
||||||
@@ -134,7 +133,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
if let Some(sha384) = sha384 {
|
if let Some(sha384) = sha384 {
|
||||||
let sha384_hex = opt_result!(hex::decode(sha384.trim()), "Decode sha384 failed: {}");
|
let sha384_hex = opt_result!(hex::decode(sha384.trim()), "Decode sha384 failed: {}");
|
||||||
let sha384_hex = crate::digest::copy_sha384(&sha384_hex)?;
|
let sha384_hex = crate::digestutil::copy_sha384(&sha384_hex)?;
|
||||||
opt_result!(trans.verify_pw1_sign(pin.as_ref()), "User sign pin verify failed: {}");
|
opt_result!(trans.verify_pw1_sign(pin.as_ref()), "User sign pin verify failed: {}");
|
||||||
success!("User sign pin verify success!");
|
success!("User sign pin verify success!");
|
||||||
let sig = match algo {
|
let sig = match algo {
|
||||||
@@ -153,7 +152,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
if let Some(sha512) = sha512 {
|
if let Some(sha512) = sha512 {
|
||||||
let sha512_hex = opt_result!(hex::decode(sha512.trim()), "Decode sha512 failed: {}");
|
let sha512_hex = opt_result!(hex::decode(sha512.trim()), "Decode sha512 failed: {}");
|
||||||
let sha512_hex = crate::digest::copy_sha512(&sha512_hex)?;
|
let sha512_hex = crate::digestutil::copy_sha512(&sha512_hex)?;
|
||||||
opt_result!(trans.verify_pw1_sign(pin.as_ref()), "User sign pin verify failed: {}");
|
opt_result!(trans.verify_pw1_sign(pin.as_ref()), "User sign pin verify failed: {}");
|
||||||
success!("User sign pin verify success!");
|
success!("User sign pin verify success!");
|
||||||
let sig = match algo {
|
let sig = match algo {
|
||||||
@@ -172,7 +171,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -190,7 +189,7 @@ where
|
|||||||
debugging!("File: {}, length: {}", file_name, file_len);
|
debugging!("File: {}, length: {}", file_name, file_len);
|
||||||
loop {
|
loop {
|
||||||
let len = match f.read(&mut buf) {
|
let len = match f.read(&mut buf) {
|
||||||
Ok(0) => return Ok(hasher.finalize().as_slice().to_vec()),
|
Ok(0) => return Ok(hasher.finalize().deref().to_vec()),
|
||||||
Ok(len) => len,
|
Ok(len) => len,
|
||||||
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
|
||||||
Err(e) => return simple_error!("Calc file digest failed: {}", e),
|
Err(e) => return simple_error!("Calc file digest failed: {}", e),
|
||||||
@@ -10,7 +10,7 @@ use spki::der::Encode;
|
|||||||
use x509_parser::parse_x509_certificate;
|
use x509_parser::parse_x509_certificate;
|
||||||
use yubikey::{Certificate, YubiKey};
|
use yubikey::{Certificate, YubiKey};
|
||||||
use yubikey::piv::SlotId;
|
use yubikey::piv::SlotId;
|
||||||
|
use crate::{cmdutil, yubikeyutil};
|
||||||
use crate::pivutil::get_algorithm_id;
|
use crate::pivutil::get_algorithm_id;
|
||||||
use crate::pkiutil::{bytes_to_pem, get_pki_algorithm};
|
use crate::pkiutil::{bytes_to_pem, get_pki_algorithm};
|
||||||
|
|
||||||
@@ -23,6 +23,7 @@ impl Command for CommandImpl {
|
|||||||
SubCommand::with_name(self.name()).about("PIV subcommand")
|
SubCommand::with_name(self.name()).about("PIV subcommand")
|
||||||
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
|
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
|
||||||
.arg(Arg::with_name("show-config").long("show-config").help("Show config output"))
|
.arg(Arg::with_name("show-config").long("show-config").help("Show config output"))
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
// .arg(Arg::with_name("json").long("json").help("JSON output"))
|
// .arg(Arg::with_name("json").long("json").help("JSON output"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ impl Command for CommandImpl {
|
|||||||
let detail_output = sub_arg_matches.is_present("detail");
|
let detail_output = sub_arg_matches.is_present("detail");
|
||||||
let show_config = sub_arg_matches.is_present("show-config");
|
let show_config = sub_arg_matches.is_present("show-config");
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
success!("Name: {}", yk.name());
|
success!("Name: {}", yk.name());
|
||||||
information!("Version: {}", yk.version());
|
information!("Version: {}", yk.version());
|
||||||
information!("Serial: {}", yk.serial());
|
information!("Serial: {}", yk.serial());
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use yubikey::piv::AlgorithmId;
|
use yubikey::piv::AlgorithmId;
|
||||||
use yubikey::YubiKey;
|
|
||||||
|
|
||||||
use crate::{pinutil, pivutil};
|
use crate::{cmdutil, pinutil, pivutil, util, yubikeyutil};
|
||||||
use crate::util::{read_stdin, try_decode};
|
use crate::util::{read_stdin, try_decode};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -16,17 +14,17 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV decrypt(RSA) subcommand")
|
SubCommand::with_name(self.name()).about("PIV decrypt(RSA) subcommand")
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.arg(Arg::with_name("ciphertext").long("ciphertext").short("c").takes_value(true).help("Encrypted data (HEX or Base64)"))
|
.arg(Arg::with_name("ciphertext").long("ciphertext").short("c").takes_value(true).help("Encrypted data (HEX or Base64)"))
|
||||||
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
|
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
||||||
|
|
||||||
@@ -40,7 +38,7 @@ impl Command for CommandImpl {
|
|||||||
return simple_error!("Argument --ciphertext must be assigned");
|
return simple_error!("Argument --ciphertext must be assigned");
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
if let Some(pin) = &pin_opt {
|
if let Some(pin) = &pin_opt {
|
||||||
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
||||||
}
|
}
|
||||||
@@ -75,7 +73,8 @@ impl Command for CommandImpl {
|
|||||||
json.insert("decrypted_data_hex", hex::encode(decrypted_data_bytes));
|
json.insert("decrypted_data_hex", hex::encode(decrypted_data_bytes));
|
||||||
json.insert("clear_data_hex", hex::encode(clear_data));
|
json.insert("clear_data_hex", hex::encode(clear_data));
|
||||||
json.insert("clear_data", String::from_utf8_lossy(clear_data).to_string());
|
json.insert("clear_data", String::from_utf8_lossy(clear_data).to_string());
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,10 @@ use std::fs;
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rand::rngs::OsRng;
|
use rand::rngs::OsRng;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use yubikey::PinPolicy;
|
||||||
use yubikey::{PinPolicy, YubiKey};
|
|
||||||
use yubikey::piv::{AlgorithmId, decrypt_data, metadata};
|
use yubikey::piv::{AlgorithmId, decrypt_data, metadata};
|
||||||
|
|
||||||
use crate::{ecdhutil, pinutil, pivutil};
|
use crate::{cmdutil, ecdhutil, pinutil, pivutil, util, yubikeyutil};
|
||||||
use crate::pivutil::get_algorithm_id;
|
use crate::pivutil::get_algorithm_id;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -18,9 +17,9 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV ECDH subcommand")
|
SubCommand::with_name(self.name()).about("PIV ECDH subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ..."))
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.arg(Arg::with_name("public-256").long("public-256").help("Public key (P-256)"))
|
.arg(Arg::with_name("public-256").long("public-256").help("Public key (P-256)"))
|
||||||
.arg(Arg::with_name("public-384").long("public-384").help("Public key (P-384)"))
|
.arg(Arg::with_name("public-384").long("public-384").help("Public key (P-384)"))
|
||||||
.arg(Arg::with_name("private").long("private").help("Private key(PIV)"))
|
.arg(Arg::with_name("private").long("private").help("Private key(PIV)"))
|
||||||
@@ -28,12 +27,12 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("public-key").long("public-key").takes_value(true).help("Public key"))
|
.arg(Arg::with_name("public-key").long("public-key").takes_value(true).help("Public key"))
|
||||||
.arg(Arg::with_name("public-key-file").long("public-key-file").takes_value(true).help("Public key"))
|
.arg(Arg::with_name("public-key-file").long("public-key-file").takes_value(true).help("Public key"))
|
||||||
.arg(Arg::with_name("public-key-point-hex").long("public-key-point-hex").takes_value(true).help("Public key point hex"))
|
.arg(Arg::with_name("public-key-point-hex").long("public-key-point-hex").takes_value(true).help("Public key point hex"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let public256 = sub_arg_matches.is_present("public-256");
|
let public256 = sub_arg_matches.is_present("public-256");
|
||||||
let public384 = sub_arg_matches.is_present("public-384");
|
let public384 = sub_arg_matches.is_present("public-384");
|
||||||
@@ -76,7 +75,7 @@ impl Command for CommandImpl {
|
|||||||
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ...");
|
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ...");
|
||||||
let epk = opt_value_result!(sub_arg_matches.value_of("epk"), "--epk must assigned");
|
let epk = opt_value_result!(sub_arg_matches.value_of("epk"), "--epk must assigned");
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
let slot_id = pivutil::get_slot_id(slot)?;
|
let slot_id = pivutil::get_slot_id(slot)?;
|
||||||
debugging!("Slot id: {}", slot_id);
|
debugging!("Slot id: {}", slot_id);
|
||||||
if let Ok(meta) = metadata(&mut yk, slot_id) {
|
if let Ok(meta) = metadata(&mut yk, slot_id) {
|
||||||
@@ -128,7 +127,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -2,13 +2,12 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use x509_parser::nom::AsBytes;
|
use x509_parser::nom::AsBytes;
|
||||||
use yubikey::piv::{metadata, sign_data, AlgorithmId, ManagementAlgorithmId};
|
use yubikey::piv::{metadata, sign_data, AlgorithmId, ManagementAlgorithmId};
|
||||||
use yubikey::YubiKey;
|
|
||||||
|
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
use crate::{argsutil, pivutil};
|
use crate::{argsutil, cmdutil, pivutil, util, yubikeyutil};
|
||||||
|
use crate::digestutil::DigestAlgorithm;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -16,32 +15,32 @@ impl Command for CommandImpl {
|
|||||||
fn name(&self) -> &str { "piv-ecsign" }
|
fn name(&self) -> &str { "piv-ecsign" }
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV EC sign(with SHA256) subcommand")
|
SubCommand::with_name(self.name()).about("PIV EC sign(with SHA256/SHA384) subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.arg(Arg::with_name("algorithm").short("a").long("algorithm").takes_value(true).help("Algorithm, p256 or p384"))
|
.arg(Arg::with_name("algorithm").short("a").long("algorithm").takes_value(true).help("Algorithm, p256 or p384"))
|
||||||
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file"))
|
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file"))
|
||||||
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
|
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
|
||||||
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
|
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
|
||||||
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
||||||
let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?;
|
let (algorithm, algorithm_str, digest_algorithm) = match sub_arg_matches.value_of("algorithm") {
|
||||||
let (algorithm, algorithm_str) = match sub_arg_matches.value_of("algorithm") {
|
None | Some("p256") => (AlgorithmId::EccP256, "ecdsa_p256_with_sha256", DigestAlgorithm::Sha256),
|
||||||
None | Some("p256") => (AlgorithmId::EccP256, "ecdsa_p256_with_sha256"),
|
Some("p384") => (AlgorithmId::EccP384, "ecdsa_p384_with_sha384", DigestAlgorithm::Sha384),
|
||||||
Some("p384") => (AlgorithmId::EccP384, "ecdsa_p384_with_sha256"),
|
|
||||||
Some(unknown_algorithm) => return simple_error!("Unknown algorithm {}, e.g. p256 or p384", unknown_algorithm),
|
Some(unknown_algorithm) => return simple_error!("Unknown algorithm {}, e.g. p256 or p384", unknown_algorithm),
|
||||||
};
|
};
|
||||||
|
let hash_bytes = argsutil::get_digest_or_hash(sub_arg_matches, digest_algorithm)?;
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
let slot_id = pivutil::get_slot_id(slot)?;
|
let slot_id = pivutil::get_slot_id(slot)?;
|
||||||
let pin_opt = pivutil::check_read_pin(&mut yk, slot_id, sub_arg_matches);
|
let pin_opt = pivutil::check_read_pin(&mut yk, slot_id, sub_arg_matches);
|
||||||
|
|
||||||
@@ -82,7 +81,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use yubikey::{PinPolicy, piv, TouchPolicy};
|
||||||
use yubikey::{PinPolicy, piv, TouchPolicy, YubiKey};
|
|
||||||
use yubikey::piv::{AlgorithmId, SlotId};
|
use yubikey::piv::{AlgorithmId, SlotId};
|
||||||
|
|
||||||
use crate::pinutil;
|
use crate::{cmdutil, pinutil, yubikeyutil};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -13,15 +12,13 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV generate subcommand")
|
SubCommand::with_name(self.name()).about("PIV generate subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("force").long("force").help("Force generate"))
|
.arg(Arg::with_name("force").long("force").help("Force generate"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_serial_arg())
|
||||||
|
// .arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
warning!("This feature is not works");
|
warning!("This feature is not works");
|
||||||
let pin_opt = sub_arg_matches.value_of("pin");
|
let pin_opt = sub_arg_matches.value_of("pin");
|
||||||
let pin_opt = pinutil::get_pin(pin_opt);
|
let pin_opt = pinutil::get_pin(pin_opt);
|
||||||
@@ -32,7 +29,7 @@ impl Command for CommandImpl {
|
|||||||
failure_and_exit!("--force must be assigned");
|
failure_and_exit!("--force must be assigned");
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
||||||
|
|
||||||
let public_key_info = opt_result!(piv::generate(&mut yk,SlotId::Signature, AlgorithmId::Rsa2048,
|
let public_key_info = opt_result!(piv::generate(&mut yk,SlotId::Signature, AlgorithmId::Rsa2048,
|
||||||
@@ -40,7 +37,6 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
success!("Generate key success: {:?}", public_key_info);
|
success!("Generate key success: {:?}", public_key_info);
|
||||||
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use p256::pkcs8::der::Encode;
|
use p256::pkcs8::der::Encode;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use rust_util::util_msg;
|
||||||
use rust_util::util_msg::MessageType;
|
use rust_util::util_msg::MessageType;
|
||||||
use x509_parser::parse_x509_certificate;
|
use x509_parser::parse_x509_certificate;
|
||||||
use yubikey::{Key, YubiKey};
|
use yubikey::Key;
|
||||||
use yubikey::piv::{AlgorithmId, metadata};
|
use yubikey::piv::{AlgorithmId, metadata};
|
||||||
|
|
||||||
use crate::pivutil;
|
use crate::{cmdutil, pivutil, util, yubikeyutil};
|
||||||
|
use crate::keyutil::{KeyAlgorithmId, KeyUri, YubikeyPivKey};
|
||||||
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
|
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
|
||||||
use crate::pkiutil::bytes_to_pem;
|
use crate::pkiutil::bytes_to_pem;
|
||||||
use crate::sshutil::SshVecWriter;
|
use crate::sshutil::SshVecWriter;
|
||||||
@@ -22,19 +23,19 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV meta subcommand")
|
SubCommand::with_name(self.name()).about("PIV meta subcommand")
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
|
||||||
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
|
|
||||||
let slot_id = pivutil::get_slot_id(slot)?;
|
let slot_id = pivutil::get_slot_id(slot)?;
|
||||||
json.insert("slot", pivutil::to_slot_hex(&slot_id));
|
json.insert("slot", pivutil::to_slot_hex(&slot_id));
|
||||||
@@ -97,11 +98,18 @@ impl Command for CommandImpl {
|
|||||||
ssh_public_key.write_string(format!("nistp{}", ec_bit_len).as_bytes());
|
ssh_public_key.write_string(format!("nistp{}", ec_bit_len).as_bytes());
|
||||||
ssh_public_key.write_string(pk_point_hex);
|
ssh_public_key.write_string(pk_point_hex);
|
||||||
let ssh_public_key_str = format!(
|
let ssh_public_key_str = format!(
|
||||||
"ecdsa-sha2-nistp{} {} PIV:{}", ec_bit_len, base64_encode(ssh_public_key), slot_id);
|
"ecdsa-sha2-nistp{} {} YubiKey-PIV-{}", ec_bit_len, base64_encode(ssh_public_key), slot_id);
|
||||||
json.insert("ssh_public_key", ssh_public_key_str.to_string());
|
json.insert("ssh_public_key", ssh_public_key_str.to_string());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let yubikey_piv_key = YubikeyPivKey {
|
||||||
|
key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0),
|
||||||
|
algorithm: KeyAlgorithmId::from_algorithm_id(algorithm_id),
|
||||||
|
slot: slot_id,
|
||||||
|
};
|
||||||
|
json.insert("key_uri", KeyUri::YubikeyPiv(yubikey_piv_key).to_string());
|
||||||
}
|
}
|
||||||
let serial_lower = cert.serial_number.to_string().to_lowercase();
|
let serial_lower = cert.serial_number.to_string().to_lowercase();
|
||||||
json.insert("serial", if serial_lower.starts_with("00:") { serial_lower.chars().skip(3).collect() } else { serial_lower });
|
json.insert("serial", if serial_lower.starts_with("00:") { serial_lower.chars().skip(3).collect() } else { serial_lower });
|
||||||
@@ -135,7 +143,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -2,11 +2,10 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use yubikey::piv;
|
||||||
use yubikey::{piv, YubiKey};
|
|
||||||
use yubikey::piv::{AlgorithmId, SlotId};
|
use yubikey::piv::{AlgorithmId, SlotId};
|
||||||
|
|
||||||
use crate::{pinutil, pivutil, rsautil};
|
use crate::{cmdutil, pinutil, pivutil, rsautil, util, yubikeyutil};
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -16,22 +15,22 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV RSA sign(with SHA256) subcommand")
|
SubCommand::with_name(self.name()).about("PIV RSA sign(with SHA256) subcommand")
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX"))
|
.arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let pin_opt = pinutil::read_pin(sub_arg_matches);
|
let pin_opt = pinutil::read_pin(sub_arg_matches);
|
||||||
|
|
||||||
let sha256_hex_opt = sub_arg_matches.value_of("sha256").map(|s| s.to_string());
|
let sha256_hex_opt = sub_arg_matches.value_of("sha256").map(|s| s.to_string());
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
if let Some(pin) = &pin_opt {
|
if let Some(pin) = &pin_opt {
|
||||||
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
||||||
}
|
}
|
||||||
@@ -55,7 +54,8 @@ impl Command for CommandImpl {
|
|||||||
json.insert("hash_hex", hex::encode(&sha256));
|
json.insert("hash_hex", hex::encode(&sha256));
|
||||||
json.insert("sign_hex", hex::encode(sign_bytes));
|
json.insert("sign_hex", hex::encode(sign_bytes));
|
||||||
json.insert("sign_base64", base64_encode(sign_bytes));
|
json.insert("sign_base64", base64_encode(sign_bytes));
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
success!("Signature HEX: {}", hex::encode(sign_bytes));
|
success!("Signature HEX: {}", hex::encode(sign_bytes));
|
||||||
success!("Signature base64: {}", base64_encode(sign_bytes));
|
success!("Signature base64: {}", base64_encode(sign_bytes));
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::XResult;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{Map, Value};
|
use serde_json::{Map, Value};
|
||||||
use spki::der::Encode;
|
use spki::der::Encode;
|
||||||
@@ -9,7 +9,7 @@ use tabled::{Table, Tabled};
|
|||||||
use x509_parser::parse_x509_certificate;
|
use x509_parser::parse_x509_certificate;
|
||||||
use yubikey::piv::{metadata, SlotId};
|
use yubikey::piv::{metadata, SlotId};
|
||||||
use yubikey::{Certificate, YubiKey};
|
use yubikey::{Certificate, YubiKey};
|
||||||
|
use crate::{cmdutil, util, yubikeyutil};
|
||||||
use crate::pivutil::{get_algorithm_id_by_certificate, ToStr, ORDERED_SLOTS};
|
use crate::pivutil::{get_algorithm_id_by_certificate, ToStr, ORDERED_SLOTS};
|
||||||
|
|
||||||
const NA: &str = "N/A";
|
const NA: &str = "N/A";
|
||||||
@@ -35,20 +35,21 @@ impl Command for CommandImpl {
|
|||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV subcommand")
|
SubCommand::with_name(self.name()).about("PIV subcommand")
|
||||||
.arg(Arg::with_name("table").long("table").help("Show table"))
|
.arg(Arg::with_name("table").long("table").help("Show table"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
.arg(Arg::with_name("all").long("all").help("Show all"))
|
.arg(Arg::with_name("all").long("all").help("Show all"))
|
||||||
.arg(Arg::with_name("ordered").long("ordered").help("Show ordered"))
|
.arg(Arg::with_name("ordered").long("ordered").help("Show ordered"))
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
let show_table = sub_arg_matches.is_present("table");
|
let show_table = sub_arg_matches.is_present("table");
|
||||||
let show_all = sub_arg_matches.is_present("all");
|
let show_all = sub_arg_matches.is_present("all");
|
||||||
let show_ordered = sub_arg_matches.is_present("ordered");
|
let show_ordered = sub_arg_matches.is_present("ordered");
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let mut output = Map::new();
|
let mut output = Map::new();
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
|
||||||
|
|
||||||
success!("Name: {}", yk.name());
|
success!("Name: {}", yk.name());
|
||||||
information!("Version: {}", yk.version());
|
information!("Version: {}", yk.version());
|
||||||
@@ -99,7 +100,7 @@ impl Command for CommandImpl {
|
|||||||
output.insert("piv_slots".to_string(), Value::Array(piv_slots_values));
|
output.insert("piv_slots".to_string(), Value::Array(piv_slots_values));
|
||||||
}
|
}
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&output).unwrap());
|
util::print_pretty_json(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -2,14 +2,11 @@ use std::collections::BTreeMap;
|
|||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use openssl::rsa::{Padding, Rsa};
|
use openssl::rsa::{Padding, Rsa};
|
||||||
use rust_util::{util_msg, XResult};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use yubikey::{Key, YubiKey};
|
use yubikey::piv::AlgorithmId;
|
||||||
use yubikey::piv::{AlgorithmId, SlotId};
|
|
||||||
|
|
||||||
use crate::{argsutil, ecdsautil, pivutil};
|
use crate::{argsutil, cmdutil, ecdsautil, pivutil, util, yubikeyutil};
|
||||||
use crate::ecdsautil::EcdsaAlgorithm;
|
use crate::ecdsautil::EcdsaAlgorithm;
|
||||||
use crate::pivutil::slot_equals;
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -18,17 +15,17 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("PIV verify subcommand")
|
SubCommand::with_name(self.name()).about("PIV verify subcommand")
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("signature-hex").short("t").long("signature-hex").takes_value(true).help("Signature"))
|
.arg(Arg::with_name("signature-hex").short("t").long("signature-hex").takes_value(true).help("Signature"))
|
||||||
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file"))
|
.arg(Arg::with_name("file").short("f").long("file").takes_value(true).help("Input file"))
|
||||||
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
|
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
|
||||||
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
|
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?;
|
let hash_bytes = argsutil::get_sha256_digest_or_hash(sub_arg_matches)?;
|
||||||
let signature = if let Some(signature_hex) = sub_arg_matches.value_of("signature-hex") {
|
let signature = if let Some(signature_hex) = sub_arg_matches.value_of("signature-hex") {
|
||||||
@@ -43,7 +40,7 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
let slot_id = pivutil::get_slot_id(slot)?;
|
let slot_id = pivutil::get_slot_id(slot)?;
|
||||||
json.insert("slot", pivutil::to_slot_hex(&slot_id));
|
json.insert("slot", pivutil::to_slot_hex(&slot_id));
|
||||||
if let Some(key) = find_key(&slot_id)? {
|
if let Some(key) = yubikeyutil::open_and_find_key(&slot_id, sub_arg_matches)? {
|
||||||
let certificate = key.certificate();
|
let certificate = key.certificate();
|
||||||
let tbs_certificate = &certificate.cert.tbs_certificate;
|
let tbs_certificate = &certificate.cert.tbs_certificate;
|
||||||
if let Ok(algorithm_id) = pivutil::get_algorithm_id_by_certificate(certificate) {
|
if let Ok(algorithm_id) = pivutil::get_algorithm_id_by_certificate(certificate) {
|
||||||
@@ -61,7 +58,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let algorithm = iff!(algorithm_id == AlgorithmId::EccP256, EcdsaAlgorithm::P256, EcdsaAlgorithm::P384);
|
let algorithm = iff!(algorithm_id == AlgorithmId::EccP256, EcdsaAlgorithm::P256, EcdsaAlgorithm::P384);
|
||||||
match ecdsautil::ecdsaverify(algorithm, pk_point, &hash_bytes, &signature) {
|
match ecdsautil::ecdsa_verify(algorithm, pk_point, &hash_bytes, &signature) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
success!("Verify ECDSA succeed.");
|
success!("Verify ECDSA succeed.");
|
||||||
if json_output {
|
if json_output {
|
||||||
@@ -100,22 +97,8 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_key(slot_id: &SlotId) -> XResult<Option<Key>> {
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
|
||||||
match Key::list(&mut yk) {
|
|
||||||
Err(e) => warning!("List keys failed: {}", e),
|
|
||||||
Ok(keys) => for k in keys {
|
|
||||||
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
|
|
||||||
if slot_equals(slot_id, &slot_str) {
|
|
||||||
return Ok(Some(k));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ use openssl::rsa::Rsa;
|
|||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use rust_util::util_msg;
|
||||||
use rust_util::util_msg::MessageType;
|
use rust_util::util_msg::MessageType;
|
||||||
|
use crate::{cmdutil, util};
|
||||||
use crate::util::{read_stdin, try_decode};
|
use crate::util::{read_stdin, try_decode};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -25,12 +25,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
|
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
|
||||||
.arg(Arg::with_name("padding").long("padding").takes_value(true)
|
.arg(Arg::with_name("padding").long("padding").takes_value(true)
|
||||||
.possible_values(&["pkcs1", "oaep", "pss", "none"]).help("Padding"))
|
.possible_values(&["pkcs1", "oaep", "pss", "none"]).help("Padding"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let pri_key_in = opt_value_result!(sub_arg_matches.value_of("pri-key-in"), "Require private key in");
|
let pri_key_in = opt_value_result!(sub_arg_matches.value_of("pri-key-in"), "Require private key in");
|
||||||
let pri_key_bytes = opt_result!(std::fs::read(pri_key_in), "Read file: {}, failed: {}", pri_key_in);
|
let pri_key_bytes = opt_result!(std::fs::read(pri_key_in), "Read file: {}, failed: {}", pri_key_in);
|
||||||
@@ -39,8 +38,6 @@ impl Command for CommandImpl {
|
|||||||
let padding = crate::rsautil::parse_padding(padding_opt)?;
|
let padding = crate::rsautil::parse_padding(padding_opt)?;
|
||||||
let padding_str = crate::rsautil::padding_to_string(padding);
|
let padding_str = crate::rsautil::padding_to_string(padding);
|
||||||
|
|
||||||
let mut json = BTreeMap::new();
|
|
||||||
|
|
||||||
let keypair = opt_result!(Rsa::private_key_from_pem(&pri_key_bytes), "Parse RSA failed: {}");
|
let keypair = opt_result!(Rsa::private_key_from_pem(&pri_key_bytes), "Parse RSA failed: {}");
|
||||||
let keypair = opt_result!(PKey::from_rsa(keypair), "RSA to PKey failed: {}");
|
let keypair = opt_result!(PKey::from_rsa(keypair), "RSA to PKey failed: {}");
|
||||||
|
|
||||||
@@ -81,13 +78,12 @@ impl Command for CommandImpl {
|
|||||||
success!("Message HEX: {}", hex::encode(&data));
|
success!("Message HEX: {}", hex::encode(&data));
|
||||||
success!("Message: {}", String::from_utf8_lossy(&data));
|
success!("Message: {}", String::from_utf8_lossy(&data));
|
||||||
if json_output {
|
if json_output {
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
json.insert("data", hex::encode(&data));
|
json.insert("data", hex::encode(&data));
|
||||||
json.insert("padding", padding_str.to_string());
|
json.insert("padding", padding_str.to_string());
|
||||||
json.insert("encrypted", encrypted_hex);
|
json.insert("encrypted", encrypted_hex);
|
||||||
}
|
|
||||||
|
|
||||||
if json_output {
|
util::print_pretty_json(&json);
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -6,9 +6,8 @@ use openssl::encrypt::Encrypter;
|
|||||||
use openssl::pkey::PKey;
|
use openssl::pkey::PKey;
|
||||||
use openssl::rsa::Rsa;
|
use openssl::rsa::Rsa;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use crate::{cmdutil, util};
|
||||||
|
use crate::digestutil::sha256_bytes;
|
||||||
use crate::digest::sha256_bytes;
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -23,12 +22,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("data-hex").long("data-hex").takes_value(true).help("Data in HEX"))
|
.arg(Arg::with_name("data-hex").long("data-hex").takes_value(true).help("Data in HEX"))
|
||||||
.arg(Arg::with_name("padding").long("padding").takes_value(true)
|
.arg(Arg::with_name("padding").long("padding").takes_value(true)
|
||||||
.possible_values(&["pkcs1", "oaep", "pss", "none"]).help("Padding"))
|
.possible_values(&["pkcs1", "oaep", "pss", "none"]).help("Padding"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let pub_key_in = opt_value_result!(sub_arg_matches.value_of("pub-key-in"), "Require public key in");
|
let pub_key_in = opt_value_result!(sub_arg_matches.value_of("pub-key-in"), "Require public key in");
|
||||||
let pub_key_bytes = opt_result!(fs::read(pub_key_in), "Read file: {}, failed: {}", pub_key_in);
|
let pub_key_bytes = opt_result!(fs::read(pub_key_in), "Read file: {}, failed: {}", pub_key_in);
|
||||||
@@ -37,8 +35,6 @@ impl Command for CommandImpl {
|
|||||||
let padding = crate::rsautil::parse_padding(padding_opt)?;
|
let padding = crate::rsautil::parse_padding(padding_opt)?;
|
||||||
let padding_str = crate::rsautil::padding_to_string(padding);
|
let padding_str = crate::rsautil::padding_to_string(padding);
|
||||||
|
|
||||||
let mut json = BTreeMap::new();
|
|
||||||
|
|
||||||
let keypair = opt_result!(Rsa::public_key_from_pem(&pub_key_bytes), "Parse RSA failed: {}");
|
let keypair = opt_result!(Rsa::public_key_from_pem(&pub_key_bytes), "Parse RSA failed: {}");
|
||||||
let pub_key_der = opt_result!(keypair.public_key_to_der(), "RSA public key to der failed: {}");
|
let pub_key_der = opt_result!(keypair.public_key_to_der(), "RSA public key to der failed: {}");
|
||||||
let pub_key_fingerprint = hex::encode(sha256_bytes(&pub_key_der));
|
let pub_key_fingerprint = hex::encode(sha256_bytes(&pub_key_der));
|
||||||
@@ -66,14 +62,13 @@ impl Command for CommandImpl {
|
|||||||
information!("Public key fingerprint: {}", pub_key_fingerprint);
|
information!("Public key fingerprint: {}", pub_key_fingerprint);
|
||||||
success!("Encrypted message: {}", encrypted_hex);
|
success!("Encrypted message: {}", encrypted_hex);
|
||||||
if json_output {
|
if json_output {
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
json.insert("data", hex::encode(&data));
|
json.insert("data", hex::encode(&data));
|
||||||
json.insert("public_key_fingerprint", pub_key_fingerprint);
|
json.insert("public_key_fingerprint", pub_key_fingerprint);
|
||||||
json.insert("padding", padding_str.to_string());
|
json.insert("padding", padding_str.to_string());
|
||||||
json.insert("encrypted", encrypted_hex);
|
json.insert("encrypted", encrypted_hex);
|
||||||
}
|
|
||||||
|
|
||||||
if json_output {
|
util::print_pretty_json(&json);
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
@@ -12,7 +12,7 @@ use rust_util::util_clap::{Command, CommandError};
|
|||||||
use rust_util::util_msg::MessageType;
|
use rust_util::util_msg::MessageType;
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::{util_msg, XResult};
|
||||||
|
|
||||||
use crate::digest::sha256_bytes;
|
use crate::digestutil::sha256_bytes;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::seutil;
|
use crate::{cmdutil, seutil, util};
|
||||||
use clap::{App, ArgMatches, SubCommand};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -10,15 +11,28 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("Secure Enclave subcommand")
|
SubCommand::with_name(self.name())
|
||||||
// .arg(Arg::with_name("json").long("json").help("JSON output"))
|
.about("Secure Enclave subcommand")
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
if seutil::is_support_se() {
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
success!("Secure Enclave is supported.")
|
|
||||||
|
if which::which("swift-secure-enclave-tool").is_err() {
|
||||||
|
failure!("Secure Enclave tool not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
|
json.insert("se_supported", seutil::is_support_se());
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
failure!("Secure Enclave is NOT supported.")
|
success!(
|
||||||
|
"Secure Enclave is {}supported.",
|
||||||
|
iff!(seutil::is_support_se(), "", "NOT ")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use crate::keyutil::{parse_key_uri, KeyUri};
|
use crate::keyutil::parse_key_uri;
|
||||||
use crate::seutil;
|
use crate::{cmd_hmac_decrypt, cmdutil, seutil, util};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
use p256::elliptic_curve::sec1::FromEncodedPoint;
|
||||||
use p256::{EncodedPoint, PublicKey};
|
use p256::{EncodedPoint, PublicKey};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use spki::EncodePublicKey;
|
use spki::EncodePublicKey;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use rust_util::XResult;
|
||||||
|
use crate::util::base64_decode;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -18,39 +19,46 @@ impl Command for CommandImpl {
|
|||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name())
|
SubCommand::with_name(self.name())
|
||||||
.about("Secure Enclave ECDH subcommand")
|
.about("Secure Enclave ECDH subcommand")
|
||||||
.arg(
|
.arg(cmdutil::build_key_uri_arg())
|
||||||
Arg::with_name("key")
|
.arg(cmdutil::build_epk_arg())
|
||||||
.long("key")
|
.arg(cmdutil::build_json_arg())
|
||||||
.required(true)
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Key uri"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::with_name("epk")
|
|
||||||
.long("epk")
|
|
||||||
.required(true)
|
|
||||||
.takes_value(true)
|
|
||||||
.help("E-Public key"),
|
|
||||||
)
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
if !seutil::is_support_se() {
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
return simple_error!("Secure Enclave is NOT supported.");
|
|
||||||
}
|
seutil::check_se_supported()?;
|
||||||
let key = sub_arg_matches.value_of("key").unwrap();
|
let key = sub_arg_matches.value_of("key").unwrap();
|
||||||
let epk = sub_arg_matches.value_of("epk").unwrap();
|
let epk = sub_arg_matches.value_of("epk").unwrap();
|
||||||
|
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let key_uri = parse_key_uri(key)?;
|
||||||
if json_output {
|
let se_key_uri = key_uri.as_secure_enclave_key()?;
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?;
|
|
||||||
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
||||||
|
|
||||||
let ephemeral_public_key_der_bytes = if epk.starts_with("04") {
|
let ephemeral_public_key_der_bytes = parse_epk(epk)?;
|
||||||
|
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &se_key_uri.private_key)?;
|
||||||
|
let dh = seutil::secure_enclave_p256_dh(
|
||||||
|
&private_key,
|
||||||
|
&ephemeral_public_key_der_bytes,
|
||||||
|
)?;
|
||||||
|
let dh_hex = hex::encode(&dh);
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
json.insert("shared_secret_hex", dh_hex);
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
information!("Shared secret: {}", dh_hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_epk(epk: &str) -> XResult<Vec<u8>> {
|
||||||
|
if epk.starts_with("04") {
|
||||||
let ephemeral_public_key_point_bytes = opt_result!(
|
let ephemeral_public_key_point_bytes = opt_result!(
|
||||||
hex::decode(epk),
|
hex::decode(epk),
|
||||||
"Decode public key point from hex failed: {}"
|
"Decode public key point from hex failed: {}"
|
||||||
@@ -64,26 +72,14 @@ impl Command for CommandImpl {
|
|||||||
return simple_error!("Parse public key failed.");
|
return simple_error!("Parse public key failed.");
|
||||||
}
|
}
|
||||||
let public_key = public_key_opt.unwrap();
|
let public_key = public_key_opt.unwrap();
|
||||||
public_key.to_public_key_der()?.as_bytes().to_vec()
|
Ok(public_key.to_public_key_der()?.as_bytes().to_vec())
|
||||||
} else {
|
} else {
|
||||||
opt_result!(hex::decode(epk), "Decode public key from hex failed: {}")
|
match hex::decode(epk) {
|
||||||
};
|
Ok(epk_bytes) => Ok(epk_bytes),
|
||||||
|
Err(e) => match base64_decode(epk) {
|
||||||
let dh = seutil::secure_enclave_p256_dh(
|
Ok(epk_bytes) => Ok(epk_bytes),
|
||||||
&se_key_uri.private_key,
|
Err(_) => simple_error!("Decode public key from hex failed: {}", e)
|
||||||
&ephemeral_public_key_der_bytes,
|
}
|
||||||
)?;
|
|
||||||
let dh_hex = hex::encode(&dh);
|
|
||||||
|
|
||||||
if json_output {
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
json.insert("shared_secret_hex", dh_hex);
|
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
} else {
|
|
||||||
information!("Shared secret: {}", dh_hex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::keyutil::{parse_key_uri, KeyUri};
|
use crate::keyutil::parse_key_uri;
|
||||||
use crate::seutil;
|
use crate::{cmd_hmac_decrypt, cmdutil, seutil, util};
|
||||||
use crate::util::{base64_decode, base64_encode};
|
use crate::util::{base64_decode, base64_encode};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use swift_secure_enclave_tool_rs::DigestType;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -16,13 +16,7 @@ impl Command for CommandImpl {
|
|||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name())
|
SubCommand::with_name(self.name())
|
||||||
.about("Secure Enclave EC sign subcommand")
|
.about("Secure Enclave EC sign subcommand")
|
||||||
.arg(
|
.arg(cmdutil::build_key_uri_arg())
|
||||||
Arg::with_name("key")
|
|
||||||
.long("key")
|
|
||||||
.required(true)
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Key uri"),
|
|
||||||
)
|
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("input")
|
Arg::with_name("input")
|
||||||
.short("i")
|
.short("i")
|
||||||
@@ -36,13 +30,13 @@ impl Command for CommandImpl {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Input in base64"),
|
.help("Input in base64"),
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
if !seutil::is_support_se() {
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
return simple_error!("Secure Enclave is NOT supported.");
|
|
||||||
}
|
seutil::check_se_supported()?;
|
||||||
let key = sub_arg_matches.value_of("key").unwrap();
|
let key = sub_arg_matches.value_of("key").unwrap();
|
||||||
let input_bytes = match sub_arg_matches.value_of("input") {
|
let input_bytes = match sub_arg_matches.value_of("input") {
|
||||||
None => match sub_arg_matches.value_of("input-base64") {
|
None => match sub_arg_matches.value_of("input-base64") {
|
||||||
@@ -51,22 +45,20 @@ impl Command for CommandImpl {
|
|||||||
},
|
},
|
||||||
Some(input) => input.as_bytes().to_vec(),
|
Some(input) => input.as_bytes().to_vec(),
|
||||||
};
|
};
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?;
|
let key_uri = parse_key_uri(key)?;
|
||||||
|
let se_key_uri = key_uri.as_secure_enclave_key()?;
|
||||||
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
||||||
|
|
||||||
let signature = seutil::secure_enclave_p256_sign(&se_key_uri.private_key, &input_bytes)?;
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &se_key_uri.private_key)?;
|
||||||
|
let signature = seutil::secure_enclave_p256_sign(&private_key, &input_bytes, DigestType::Raw)?;
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
json.insert("signature_base64", base64_encode(&signature));
|
json.insert("signature_base64", base64_encode(&signature));
|
||||||
json.insert("signature_hex", hex::encode(&signature));
|
json.insert("signature_hex", hex::encode(&signature));
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
success!("Signature: {}", base64_encode(&signature));
|
success!("Signature: {}", base64_encode(&signature));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
use crate::cmd_hmac_encrypt;
|
||||||
use crate::pkiutil::bytes_to_pem;
|
use crate::pkiutil::bytes_to_pem;
|
||||||
use crate::seutil;
|
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
use crate::{cmdutil, seutil, util};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use p256::PublicKey;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
use spki::DecodePublicKey;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use swift_secure_enclave_tool_rs::{ControlFlag, KeyMlKem};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -30,50 +33,105 @@ impl Command for CommandImpl {
|
|||||||
.takes_value(true)
|
.takes_value(true)
|
||||||
.help("Host name"),
|
.help("Host name"),
|
||||||
)
|
)
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(
|
||||||
|
Arg::with_name("control-flag")
|
||||||
|
.long("control-flag")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Control flag, e.g. none, user-presence, device-passcode, biometry-any, biometry-current-set"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("algorithm")
|
||||||
|
.long("algorithm")
|
||||||
|
.required(true)
|
||||||
|
.takes_value(true)
|
||||||
|
.default_value("p256")
|
||||||
|
.help("Algorithm, e.g. p256, mlkem768, mlkem1024"),
|
||||||
|
)
|
||||||
|
.arg(cmdutil::build_with_hmac_encrypt_arg())
|
||||||
|
.arg(cmdutil::build_with_pbe_encrypt_arg())
|
||||||
|
.arg(cmdutil::build_double_pin_check_arg())
|
||||||
|
.arg(cmdutil::build_pbe_iteration_arg())
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
if !seutil::is_support_se() {
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
return simple_error!("Secure Enclave is NOT supported.");
|
|
||||||
}
|
seutil::check_se_supported()?;
|
||||||
let ty = sub_arg_matches.value_of("type").unwrap();
|
let ty = sub_arg_matches.value_of("type").unwrap();
|
||||||
let host = sub_arg_matches.value_of("host").unwrap_or("macbook");
|
let host = sub_arg_matches.value_of("host").unwrap_or("macbook");
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sign = match ty {
|
let sign = match ty {
|
||||||
"signing" | "ecsign" | "sign" => true,
|
"signing" | "ecsign" | "sign" => true,
|
||||||
"key_agreement" | "ecdh" | "dh" => false,
|
"key_agreement" | "ecdh" | "dh" => false,
|
||||||
_ => return simple_error!("Invalid type: {}", ty),
|
_ => return simple_error!("Invalid type: {}", ty),
|
||||||
};
|
};
|
||||||
|
let control_flag = sub_arg_matches.value_of("control-flag").unwrap();
|
||||||
|
let control_flag = match control_flag {
|
||||||
|
"none" => ControlFlag::None,
|
||||||
|
"user-presence" | "up" => ControlFlag::UserPresence,
|
||||||
|
"device-passcode" | "passcode" | "pass" => ControlFlag::DevicePasscode,
|
||||||
|
"biometry-any" | "bio-any" => ControlFlag::BiometryAny,
|
||||||
|
"biometry-current-set" | "bio-current" => ControlFlag::BiometryCurrentSet,
|
||||||
|
_ => return simple_error!("Invalid control flag: {}", control_flag),
|
||||||
|
};
|
||||||
|
let algorithm = sub_arg_matches.value_of("algorithm").unwrap();
|
||||||
|
let (public_key_point, public_key_der, private_key) = match algorithm {
|
||||||
|
"p256" => {
|
||||||
|
seutil::generate_secure_enclave_p256_keypair(sign, control_flag)?
|
||||||
|
}
|
||||||
|
"mlkem768" | "mlkem1024" => {
|
||||||
|
if sign {
|
||||||
|
return simple_error!("Algorithm: {} only supports key_agreement", algorithm);
|
||||||
|
}
|
||||||
|
seutil::generate_secure_enclave_mlkem_keypair(
|
||||||
|
iff!(algorithm == "mlkem768", KeyMlKem::MlKem768, KeyMlKem::MlKem1024), control_flag)?
|
||||||
|
}
|
||||||
|
_ => return simple_error!("Unknown algorithm: {}", algorithm),
|
||||||
|
};
|
||||||
|
|
||||||
let (public_key_point, public_key_der, private_key) =
|
let private_key = cmd_hmac_encrypt::do_encrypt(&private_key, &mut None, sub_arg_matches)?;
|
||||||
seutil::generate_secure_enclave_p256_keypair(sign)?;
|
let key_uri = format!(
|
||||||
|
"key://{}:se/{}:{}:{}",
|
||||||
let public_key_point_hex = hex::encode(&public_key_point);
|
|
||||||
let public_key_pem = bytes_to_pem("PUBLIC KEY", &*public_key_der);
|
|
||||||
let key = format!(
|
|
||||||
"key://{}:se/p256:{}:{}",
|
|
||||||
host,
|
host,
|
||||||
|
algorithm,
|
||||||
iff!(sign, "signing", "key_agreement"),
|
iff!(sign, "signing", "key_agreement"),
|
||||||
private_key,
|
private_key,
|
||||||
);
|
);
|
||||||
if json_output {
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
json.insert("public_key_point", public_key_point_hex);
|
|
||||||
json.insert("public_key_pem", base64_encode(&*public_key_der));
|
|
||||||
json.insert("key", key);
|
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
} else {
|
|
||||||
success!("Public key(point): {}", public_key_point_hex);
|
|
||||||
success!("Public key PEM: \n{}", public_key_pem);
|
|
||||||
success!("Key: {}", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
print_se_key(json_output, &public_key_point, &public_key_der, &key_uri);
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_se_key(
|
||||||
|
json_output: bool,
|
||||||
|
public_key_point: &[u8],
|
||||||
|
public_key_der: &[u8],
|
||||||
|
key_uri: &str,
|
||||||
|
) {
|
||||||
|
let public_key_point_hex = hex::encode(public_key_point);
|
||||||
|
let public_key_pem = bytes_to_pem("PUBLIC KEY", public_key_der);
|
||||||
|
let public_key = PublicKey::from_public_key_pem(&public_key_pem).ok();
|
||||||
|
let public_key_jwk = public_key.map(|key| key.to_jwk_string());
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
json.insert("public_key_point", public_key_point_hex);
|
||||||
|
json.insert("public_key_pem", base64_encode(public_key_der));
|
||||||
|
if let Some(public_key_jwk) = public_key_jwk {
|
||||||
|
json.insert("public_key_jwk", base64_encode(public_key_jwk));
|
||||||
|
}
|
||||||
|
json.insert("key_uri", key_uri.to_string());
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
success!("Public key(point): {}", public_key_point_hex);
|
||||||
|
success!("Public key PEM: \n{}", public_key_pem);
|
||||||
|
if let Some(public_key_jwk) = public_key_jwk {
|
||||||
|
success!("Public key JWK: \n{}", &public_key_jwk);
|
||||||
|
}
|
||||||
|
success!("Key: {}", key_uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
|
use crate::cmd_se_generate::print_se_key;
|
||||||
use crate::pkiutil::bytes_to_pem;
|
use crate::keyutil::{parse_key_uri, KeyUsage};
|
||||||
use crate::seutil;
|
use crate::{cmd_hmac_decrypt, cmdutil, seutil};
|
||||||
use crate::util::base64_encode;
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -17,50 +14,27 @@ impl Command for CommandImpl {
|
|||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name())
|
SubCommand::with_name(self.name())
|
||||||
.about("Secure Enclave recover subcommand")
|
.about("Secure Enclave recover subcommand")
|
||||||
.arg(
|
.arg(cmdutil::build_key_uri_arg())
|
||||||
Arg::with_name("key")
|
.arg(cmdutil::build_json_arg())
|
||||||
.long("key")
|
|
||||||
.required(true)
|
|
||||||
.takes_value(true)
|
|
||||||
.help("Key uri"),
|
|
||||||
)
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
if !seutil::is_support_se() {
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
return simple_error!("Secure Enclave is NOT supported.");
|
|
||||||
}
|
seutil::check_se_supported()?;
|
||||||
let key = sub_arg_matches.value_of("key").unwrap();
|
let key = sub_arg_matches.value_of("key").unwrap();
|
||||||
|
let key_uri = parse_key_uri(key)?;
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let se_key_uri = key_uri.as_secure_enclave_key()?;
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?;
|
|
||||||
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
||||||
|
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, &se_key_uri.private_key)?;
|
||||||
let (public_key_point, public_key_der, _private_key) =
|
let (public_key_point, public_key_der, _private_key) =
|
||||||
seutil::recover_secure_enclave_p256_public_key(
|
seutil::recover_secure_enclave_p256_public_key(
|
||||||
&se_key_uri.private_key,
|
&private_key,
|
||||||
se_key_uri.usage == KeyUsage::Singing,
|
se_key_uri.usage == KeyUsage::Singing,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let public_key_point_hex = hex::encode(&public_key_point);
|
print_se_key(json_output, &public_key_point, &public_key_der, key);
|
||||||
let public_key_pem = bytes_to_pem("PUBLIC KEY", &*public_key_der);
|
|
||||||
if json_output {
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
json.insert("public_key_point", public_key_point_hex);
|
|
||||||
json.insert("public_key_pem", base64_encode(&*public_key_der));
|
|
||||||
json.insert("key", key.to_string());
|
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
} else {
|
|
||||||
success!("Public key(point): {}", public_key_point_hex);
|
|
||||||
success!("Public key PEM: \n{}", public_key_pem);
|
|
||||||
success!("Key: {}", key);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|||||||
85
src/cmd_sign_jwt.rs
Normal file
85
src/cmd_sign_jwt.rs
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
use crate::cmd_sign_jwt_piv::{
|
||||||
|
build_jwt_parts, fill_sign_jwt_app_args, merge_header_claims,
|
||||||
|
merge_payload_claims, print_jwt_token,
|
||||||
|
};
|
||||||
|
use crate::ecdsautil::parse_ecdsa_to_rs;
|
||||||
|
use crate::keyutil::parse_key_uri;
|
||||||
|
use crate::{cmd_external_sign, cmdutil, util};
|
||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
|
use jwt::ToBase64;
|
||||||
|
use jwt::{AlgorithmType, Header};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
use crate::pivutil::ToStr;
|
||||||
|
|
||||||
|
const SEPARATOR: &str = ".";
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"sign-jwt"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
let app = SubCommand::with_name(self.name())
|
||||||
|
.about("Sign JWT subcommand")
|
||||||
|
.arg(cmdutil::build_key_uri_arg().required(false))
|
||||||
|
.arg(cmdutil::build_parameter_arg().required(false))
|
||||||
|
.arg(cmdutil::build_pin_arg())
|
||||||
|
.arg(cmdutil::build_serial_arg())
|
||||||
|
.arg(cmdutil::build_json_arg());
|
||||||
|
fill_sign_jwt_app_args(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let (header, payload, jwt_claims) = build_jwt_parts(sub_arg_matches)?;
|
||||||
|
let token_string = sign_jwt(header, &payload, &jwt_claims, sub_arg_matches)?;
|
||||||
|
print_jwt_token(json_output, token_string);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_jwt(
|
||||||
|
mut header: Header,
|
||||||
|
payload: &Option<String>,
|
||||||
|
claims: &Map<String, Value>,
|
||||||
|
sub_arg_matches: &ArgMatches,
|
||||||
|
) -> XResult<String> {
|
||||||
|
let key = match sub_arg_matches.value_of("key") {
|
||||||
|
Some(key) => key,
|
||||||
|
None => match sub_arg_matches.value_of("parameter") {
|
||||||
|
Some(parameter) => parameter,
|
||||||
|
None => return simple_error!("Parameter --key or --parameter required"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let key_uri = parse_key_uri(key)?;
|
||||||
|
|
||||||
|
let jwt_algorithm = key_uri.get_preferred_algorithm_type();
|
||||||
|
|
||||||
|
header.algorithm = jwt_algorithm;
|
||||||
|
debugging!("Header: {:?}", header);
|
||||||
|
debugging!("Claims: {:?}", claims);
|
||||||
|
|
||||||
|
let header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
||||||
|
let claims = merge_payload_claims(payload, claims)?;
|
||||||
|
let tobe_signed = merge_header_claims(header.as_bytes(), claims.as_bytes());
|
||||||
|
|
||||||
|
let signature = cmd_external_sign::sign(jwt_algorithm.to_str(), &tobe_signed, None, key_uri, sub_arg_matches)?;
|
||||||
|
|
||||||
|
let signed_data = match jwt_algorithm {
|
||||||
|
AlgorithmType::Rs256 => signature,
|
||||||
|
AlgorithmType::Es256 | AlgorithmType::Es384 | AlgorithmType::Es512 => {
|
||||||
|
parse_ecdsa_to_rs(signature.as_slice())?
|
||||||
|
}
|
||||||
|
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
||||||
|
};
|
||||||
|
|
||||||
|
let signature = util::base64_encode_url_safe_no_pad(&signed_data);
|
||||||
|
|
||||||
|
Ok([&*header, &*claims, &signature].join(SEPARATOR))
|
||||||
|
}
|
||||||
302
src/cmd_sign_jwt_piv.rs
Normal file
302
src/cmd_sign_jwt_piv.rs
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use jwt::header::HeaderType;
|
||||||
|
use jwt::{AlgorithmType, Header, ToBase64};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::{util_time, XResult};
|
||||||
|
use serde_json::{Map, Number, Value};
|
||||||
|
use yubikey::piv::{sign_data, AlgorithmId, SlotId};
|
||||||
|
use yubikey::{Certificate, YubiKey};
|
||||||
|
|
||||||
|
use crate::ecdsautil::parse_ecdsa_to_rs;
|
||||||
|
use crate::{cmdutil, digestutil, pivutil, rsautil, util};
|
||||||
|
|
||||||
|
const SEPARATOR: &str = ".";
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"sign-jwt-piv"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
let app = SubCommand::with_name(self.name()).about("Sign JWT(PIV) subcommand")
|
||||||
|
.arg(cmdutil::build_slot_arg())
|
||||||
|
.arg(cmdutil::build_pin_arg())
|
||||||
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
|
.arg(cmdutil::build_json_arg());
|
||||||
|
fill_sign_jwt_app_args(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let slot = opt_value_result!(
|
||||||
|
sub_arg_matches.value_of("slot"),
|
||||||
|
"--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (header, payload, jwt_claims) = build_jwt_parts(sub_arg_matches)?;
|
||||||
|
|
||||||
|
let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}");
|
||||||
|
let slot_id = opt_result!(pivutil::get_slot_id(slot), "Get slot id failed: {}");
|
||||||
|
let pin_opt = pivutil::check_read_pin(&mut yk, slot_id, sub_arg_matches);
|
||||||
|
|
||||||
|
let token_string = sign_jwt(&mut yk, slot_id, &pin_opt, header, &payload, &jwt_claims)?;
|
||||||
|
print_jwt_token(json_output, token_string);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_jwt_token(json_output: bool, token_string: String) {
|
||||||
|
if json_output {
|
||||||
|
debugging!("Singed JWT: {}", token_string);
|
||||||
|
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
json.insert("token", token_string.clone());
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
success!("Singed JWT: {}", token_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fill_sign_jwt_app_args<'a>(app: App<'a, 'a>) -> App<'a, 'a> {
|
||||||
|
app.arg(Arg::with_name("key-id").short("K").long("key-id").takes_value(true).help("Header key ID"))
|
||||||
|
.arg(Arg::with_name("claims").short("C").long("claims").takes_value(true).multiple(true).help("Claims, key:value"))
|
||||||
|
.arg(Arg::with_name("payload").short("P").long("payload").takes_value(true).help("Claims in JSON"))
|
||||||
|
.arg(Arg::with_name("jti").long("jti").help("Claims jti"))
|
||||||
|
.arg(Arg::with_name("validity").long("validity").takes_value(true)
|
||||||
|
.help("Claims validity period e.g. 10m means 10 minutes (s - second, m - minute, h - hour, d - day)"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_jwt(
|
||||||
|
yk: &mut YubiKey,
|
||||||
|
slot_id: SlotId,
|
||||||
|
pin_opt: &Option<String>,
|
||||||
|
mut header: Header,
|
||||||
|
payload: &Option<String>,
|
||||||
|
claims: &Map<String, Value>,
|
||||||
|
) -> XResult<String> {
|
||||||
|
if let Some(pin) = pin_opt {
|
||||||
|
opt_result!(
|
||||||
|
yk.verify_pin(pin.as_bytes()),
|
||||||
|
"YubiKey verify pin failed: {}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let cert = match Certificate::read(yk, slot_id) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => return simple_error!("Read YubiKey certificate failed: {}", e),
|
||||||
|
};
|
||||||
|
let piv_algorithm_id =
|
||||||
|
pivutil::get_algorithm_id(&cert.cert.tbs_certificate.subject_public_key_info)?;
|
||||||
|
|
||||||
|
let (jwt_algorithm, yk_algorithm) = match piv_algorithm_id {
|
||||||
|
AlgorithmId::Rsa1024 => return simple_error!("RSA 1024 bits not supported."),
|
||||||
|
AlgorithmId::Rsa2048 => (AlgorithmType::Rs256, AlgorithmId::Rsa2048),
|
||||||
|
AlgorithmId::EccP256 => (AlgorithmType::Es256, AlgorithmId::EccP256),
|
||||||
|
AlgorithmId::EccP384 => (AlgorithmType::Es384, AlgorithmId::EccP384),
|
||||||
|
};
|
||||||
|
|
||||||
|
header.algorithm = jwt_algorithm;
|
||||||
|
debugging!("Header: {:?}", header);
|
||||||
|
debugging!("Claims: {:?}", claims);
|
||||||
|
|
||||||
|
let header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
||||||
|
let claims = merge_payload_claims(payload, claims)?;
|
||||||
|
let tobe_signed = merge_header_claims(header.as_bytes(), claims.as_bytes());
|
||||||
|
|
||||||
|
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &tobe_signed)?;
|
||||||
|
|
||||||
|
let signed_data = opt_result!(
|
||||||
|
sign_data(yk, &raw_in, yk_algorithm, slot_id),
|
||||||
|
"Sign YubiKey failed: {}"
|
||||||
|
);
|
||||||
|
let signed_data = match jwt_algorithm {
|
||||||
|
AlgorithmType::Rs256 => signed_data.to_vec(),
|
||||||
|
AlgorithmType::Es256 | AlgorithmType::Es384 => parse_ecdsa_to_rs(signed_data.as_slice())?,
|
||||||
|
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
||||||
|
};
|
||||||
|
|
||||||
|
let signature = util::base64_encode_url_safe_no_pad(&signed_data);
|
||||||
|
|
||||||
|
Ok([&*header, &*claims, &signature].join(SEPARATOR))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest_by_jwt_algorithm(jwt_algorithm: AlgorithmType, tobe_signed: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
Ok(match jwt_algorithm {
|
||||||
|
AlgorithmType::Rs256 => {
|
||||||
|
rsautil::pkcs15_sha256_rsa_2048_padding_for_sign(&digestutil::sha256_bytes(tobe_signed))
|
||||||
|
}
|
||||||
|
AlgorithmType::Es256 => digestutil::sha256_bytes(tobe_signed),
|
||||||
|
AlgorithmType::Es384 => digestutil::sha384_bytes(tobe_signed),
|
||||||
|
AlgorithmType::Es512 => digestutil::sha512_bytes(tobe_signed),
|
||||||
|
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_header_claims(header: &[u8], claims: &[u8]) -> Vec<u8> {
|
||||||
|
let mut tobe_signed = vec![];
|
||||||
|
tobe_signed.extend_from_slice(header);
|
||||||
|
tobe_signed.extend_from_slice(SEPARATOR.as_bytes());
|
||||||
|
tobe_signed.extend_from_slice(claims);
|
||||||
|
tobe_signed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge_payload_claims<'a>(
|
||||||
|
payload: &'a Option<String>,
|
||||||
|
claims: &'a Map<String, Value>,
|
||||||
|
) -> XResult<Cow<'a, str>> {
|
||||||
|
Ok(match (payload, claims.is_empty()) {
|
||||||
|
(Some(payload), true) => {
|
||||||
|
Cow::Owned(util::base64_encode_url_safe_no_pad(payload.as_bytes()))
|
||||||
|
}
|
||||||
|
(_, _) => opt_result!(claims.to_base64(), "Claims to base64 failed: {}"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_jwt_parts(
|
||||||
|
sub_arg_matches: &ArgMatches,
|
||||||
|
) -> XResult<(Header, Option<String>, Map<String, Value>)> {
|
||||||
|
let key_id = sub_arg_matches.value_of("key-id");
|
||||||
|
let claims = sub_arg_matches.values_of("claims");
|
||||||
|
let payload = sub_arg_matches.value_of("payload");
|
||||||
|
let validity = sub_arg_matches.value_of("validity");
|
||||||
|
let jti = sub_arg_matches.is_present("jti");
|
||||||
|
|
||||||
|
let header = Header {
|
||||||
|
key_id: key_id.map(ToString::to_string),
|
||||||
|
type_: Some(HeaderType::JsonWebToken),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut jwt_claims = Map::new();
|
||||||
|
if let Some(payload) = payload {
|
||||||
|
match serde_json::from_str::<Value>(payload) {
|
||||||
|
Ok(Value::Object(claims_map)) => {
|
||||||
|
claims_map.into_iter().for_each(|(k, v)| {
|
||||||
|
jwt_claims.insert(k, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(value) => {
|
||||||
|
warning!("Not valid payload map: {}", value);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warning!("Not valid payload value: {}", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match (payload, claims) {
|
||||||
|
(Some(_), None) => {}
|
||||||
|
(_, Some(claims)) => {
|
||||||
|
for claim in claims {
|
||||||
|
match split_claim(claim) {
|
||||||
|
None => {
|
||||||
|
warning!("Claim '{}' do not contains ':'", claim);
|
||||||
|
}
|
||||||
|
Some((k, v)) => {
|
||||||
|
match jwt_claims.get_mut(&k) {
|
||||||
|
None => { jwt_claims.insert(k, v); },
|
||||||
|
Some(val) => {
|
||||||
|
match val {
|
||||||
|
Value::Array(arr) => {
|
||||||
|
arr.push(v);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut arr = vec![];
|
||||||
|
arr.push(val.clone());
|
||||||
|
arr.push(v);
|
||||||
|
jwt_claims.insert(k, Value::Array(arr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// jwt_claims.insert(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !jwt_claims.contains_key("sub") {
|
||||||
|
return simple_error!("Claim sub is not assigned.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return simple_error!("Payload or Claims is required."),
|
||||||
|
}
|
||||||
|
|
||||||
|
// set jti, iat and sub
|
||||||
|
if jti && !jwt_claims.contains_key("jti") {
|
||||||
|
jwt_claims.insert(
|
||||||
|
"jti".to_string(),
|
||||||
|
Value::String(format!("jti-{}", util_time::get_current_millis())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Some(validity) = validity {
|
||||||
|
match util_time::parse_duration(validity) {
|
||||||
|
None => {
|
||||||
|
warning!("Bad validity: {}", validity)
|
||||||
|
}
|
||||||
|
Some(validity) => {
|
||||||
|
let current_secs = (util_time::get_current_millis() / 1000) as u64;
|
||||||
|
jwt_claims.insert("iat".to_string(), Value::Number(Number::from(current_secs)));
|
||||||
|
jwt_claims.insert(
|
||||||
|
"exp".to_string(),
|
||||||
|
Value::Number(Number::from(current_secs + validity.as_secs())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((header, payload.map(ToString::to_string), jwt_claims))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn split_claim(claim: &str) -> Option<(String, Value)> {
|
||||||
|
let mut k = String::new();
|
||||||
|
let mut v = String::new();
|
||||||
|
|
||||||
|
let mut claim_chars = claim.chars().peekable();
|
||||||
|
let ty = if let Some('^') = claim_chars.peek() {
|
||||||
|
let _ = claim_chars.next();
|
||||||
|
claim_chars.next()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut is_k = true;
|
||||||
|
for c in claim_chars {
|
||||||
|
if is_k {
|
||||||
|
if c == ':' {
|
||||||
|
is_k = false;
|
||||||
|
} else {
|
||||||
|
k.push(c);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_k {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ty {
|
||||||
|
None | Some('s') => Some((k, Value::String(v))),
|
||||||
|
Some('b') => Some((k, Value::Bool(["true", "yes", "1"].contains(&v.as_str())))),
|
||||||
|
Some('i') | Some('n') => {
|
||||||
|
if let Ok(i) = v.parse::<i64>() {
|
||||||
|
return Some((k, Value::Number(Number::from(i))));
|
||||||
|
}
|
||||||
|
if let Ok(f) = v.parse::<f64>() {
|
||||||
|
if let Some(number_f64) = Number::from_f64(f) {
|
||||||
|
return Some((k, Value::Number(number_f64)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
warning!("Bad number: {} in claim: {}", v, claim);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
warning!("Unknown type: {} in claim: {}", ty.unwrap(), claim);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/cmd_sign_jwt_se.rs
Normal file
74
src/cmd_sign_jwt_se.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
|
|
||||||
|
use jwt::{AlgorithmType, Header, ToBase64};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::cmd_sign_jwt_piv::{build_jwt_parts, merge_header_claims, merge_payload_claims, print_jwt_token};
|
||||||
|
use crate::ecdsautil::parse_ecdsa_to_rs;
|
||||||
|
use crate::keyutil::parse_key_uri;
|
||||||
|
use crate::{cmd_hmac_decrypt, cmd_sign_jwt_piv, cmdutil, util};
|
||||||
|
use crate::util::base64_decode;
|
||||||
|
|
||||||
|
const SEPARATOR: &str = ".";
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"sign-jwt-se"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
let app = SubCommand::with_name(self.name()).about("Sign JWT(SE) subcommand")
|
||||||
|
.arg(cmdutil::build_key_uri_arg())
|
||||||
|
.arg(cmdutil::build_json_arg());
|
||||||
|
cmd_sign_jwt_piv::fill_sign_jwt_app_args(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let private_key = opt_value_result!(
|
||||||
|
sub_arg_matches.value_of("key"),
|
||||||
|
"Private key PKCS#8 DER base64 encoded or PEM"
|
||||||
|
);
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, private_key)?;
|
||||||
|
let key_uri = parse_key_uri(&private_key)?;
|
||||||
|
let se_key_uri = key_uri.as_secure_enclave_key()?;
|
||||||
|
debugging!("Secure enclave key URI: {:?}", se_key_uri);
|
||||||
|
|
||||||
|
let (header, payload, jwt_claims) = build_jwt_parts(sub_arg_matches)?;
|
||||||
|
|
||||||
|
let token_string = sign_jwt(&se_key_uri.private_key, header, &payload, &jwt_claims)?;
|
||||||
|
print_jwt_token(json_output, token_string);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_jwt(
|
||||||
|
private_key: &str,
|
||||||
|
mut header: Header,
|
||||||
|
payload: &Option<String>,
|
||||||
|
claims: &Map<String, Value>,
|
||||||
|
) -> XResult<String> {
|
||||||
|
header.algorithm = AlgorithmType::Es256;
|
||||||
|
debugging!("Header: {:?}", header);
|
||||||
|
debugging!("Claims: {:?}", claims);
|
||||||
|
|
||||||
|
let header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
||||||
|
let claims = merge_payload_claims(payload, claims)?;
|
||||||
|
let tobe_signed = merge_header_claims(header.as_bytes(), claims.as_bytes());
|
||||||
|
|
||||||
|
let private_key_representation = base64_decode(private_key)?;
|
||||||
|
let signed_data_der =
|
||||||
|
swift_secure_enclave_tool_rs::private_key_sign(&private_key_representation, &tobe_signed)?;
|
||||||
|
|
||||||
|
let signed_data = parse_ecdsa_to_rs(signed_data_der.as_slice())?;
|
||||||
|
|
||||||
|
let signature = util::base64_encode_url_safe_no_pad(&signed_data);
|
||||||
|
|
||||||
|
Ok([&*header, &*claims, &signature].join(SEPARATOR))
|
||||||
|
}
|
||||||
109
src/cmd_sign_jwt_soft.rs
Normal file
109
src/cmd_sign_jwt_soft.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use jwt::{AlgorithmType, Header, ToBase64};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
|
||||||
|
use crate::cmd_sign_jwt_piv::{build_jwt_parts, digest_by_jwt_algorithm, merge_header_claims, merge_payload_claims, print_jwt_token};
|
||||||
|
use crate::keychain::{KeychainKey, KeychainKeyValue};
|
||||||
|
use crate::{cmd_hmac_decrypt, cmd_sign_jwt_piv, cmdutil, ecdsautil, keychain, util};
|
||||||
|
use crate::ecdsautil::{EcdsaAlgorithm, EcdsaSignType};
|
||||||
|
|
||||||
|
const SEPARATOR: &str = ".";
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"sign-jwt-soft"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
let app = SubCommand::with_name(self.name()).about("Sign JWT(Soft EC) subcommand")
|
||||||
|
.arg(Arg::with_name("private-key").short("k").long("private-key").takes_value(true).help("Private key PKCS#8"))
|
||||||
|
.arg(cmdutil::build_json_arg());
|
||||||
|
cmd_sign_jwt_piv::fill_sign_jwt_app_args(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let private_key = opt_value_result!(
|
||||||
|
sub_arg_matches.value_of("private-key"),
|
||||||
|
"Private key PKCS#8 DER base64 encoded or PEM"
|
||||||
|
);
|
||||||
|
|
||||||
|
let private_key = cmd_hmac_decrypt::try_decrypt(&mut None, private_key)?;
|
||||||
|
|
||||||
|
let private_key = if keychain::is_keychain_key_uri(&private_key) {
|
||||||
|
debugging!("Private key keychain key URI: {}", &private_key);
|
||||||
|
let keychain_key = KeychainKey::parse_key_uri(&private_key)?;
|
||||||
|
let keychain_key_value_bytes = opt_value_result!(
|
||||||
|
keychain_key.get_password()?,
|
||||||
|
"Keychain key URI: {} not found",
|
||||||
|
&private_key
|
||||||
|
);
|
||||||
|
let keychain_key_value: KeychainKeyValue =
|
||||||
|
serde_json::from_slice(&keychain_key_value_bytes)?;
|
||||||
|
debugging!("Keychain key value {:?}", &keychain_key_value);
|
||||||
|
keychain_key_value.pkcs8_base64
|
||||||
|
} else {
|
||||||
|
private_key
|
||||||
|
};
|
||||||
|
|
||||||
|
let (header, payload, jwt_claims) = build_jwt_parts(sub_arg_matches)?;
|
||||||
|
|
||||||
|
let token_string = sign_jwt(&private_key, header, &payload, &jwt_claims)?;
|
||||||
|
print_jwt_token(json_output, token_string);
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_jwt(
|
||||||
|
private_key: &str,
|
||||||
|
mut header: Header,
|
||||||
|
payload: &Option<String>,
|
||||||
|
claims: &Map<String, Value>,
|
||||||
|
) -> XResult<String> {
|
||||||
|
let (jwt_algorithm, private_key_d) = parse_ecdsa_private_key(private_key)?;
|
||||||
|
|
||||||
|
header.algorithm = jwt_algorithm;
|
||||||
|
debugging!("Header: {:?}", header);
|
||||||
|
debugging!("Claims: {:?}", claims);
|
||||||
|
|
||||||
|
let header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
||||||
|
let claims = merge_payload_claims(payload, claims)?;
|
||||||
|
let tobe_signed = merge_header_claims(header.as_bytes(), claims.as_bytes());
|
||||||
|
|
||||||
|
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &tobe_signed)?;
|
||||||
|
let ecdsa_algorithm = convert_jwt_algorithm_to_ecdsa_algorithm(jwt_algorithm)?;
|
||||||
|
let signed_data = ecdsautil::ecdsa_sign(ecdsa_algorithm, &private_key_d, &raw_in, EcdsaSignType::Rs)?;
|
||||||
|
|
||||||
|
let signature = util::base64_encode_url_safe_no_pad(&signed_data);
|
||||||
|
|
||||||
|
Ok([&*header, &*claims, &signature].join(SEPARATOR))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_jwt_algorithm_to_ecdsa_algorithm(jwt_algorithm: AlgorithmType) -> XResult<EcdsaAlgorithm> {
|
||||||
|
match jwt_algorithm {
|
||||||
|
AlgorithmType::Es256 => Ok(EcdsaAlgorithm::P256),
|
||||||
|
AlgorithmType::Es384 => Ok(EcdsaAlgorithm::P384),
|
||||||
|
AlgorithmType::Es512 => Ok(EcdsaAlgorithm::P521),
|
||||||
|
_ => simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_ecdsa_private_key(private_key: &str) -> XResult<(AlgorithmType, Vec<u8>)> {
|
||||||
|
let p256_private_key_d = ecdsautil::parse_p256_private_key(private_key).ok();
|
||||||
|
let p384_private_key_d = ecdsautil::parse_p384_private_key(private_key).ok();
|
||||||
|
let p521_private_key_d = ecdsautil::parse_p521_private_key(private_key).ok();
|
||||||
|
|
||||||
|
let (jwt_algorithm, private_key_d) = match (p256_private_key_d, p384_private_key_d, p521_private_key_d) {
|
||||||
|
(Some(p256_private_key_d), None, None) => (AlgorithmType::Es256, p256_private_key_d),
|
||||||
|
(None, Some(p384_private_key_d), None) => (AlgorithmType::Es384, p384_private_key_d),
|
||||||
|
(None, None, Some(p521_private_key_d)) => (AlgorithmType::Es512, p521_private_key_d),
|
||||||
|
_ => return simple_error!("Invalid private key: {}", private_key),
|
||||||
|
};
|
||||||
|
Ok((jwt_algorithm, private_key_d))
|
||||||
|
}
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use jwt::{AlgorithmType, Header, ToBase64};
|
|
||||||
use jwt::header::HeaderType;
|
|
||||||
use rust_util::{util_msg, util_time, XResult};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
|
||||||
use serde_json::{Map, Number, Value};
|
|
||||||
use yubikey::{Certificate, YubiKey};
|
|
||||||
use yubikey::piv::{AlgorithmId, sign_data, SlotId};
|
|
||||||
|
|
||||||
use crate::{digest, pivutil, rsautil, util};
|
|
||||||
use crate::ecdsautil::parse_ecdsa_to_rs;
|
|
||||||
|
|
||||||
const SEPARATOR: &str = ".";
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
|
||||||
fn name(&self) -> &str { "sign-jwt" }
|
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
|
||||||
SubCommand::with_name(self.name()).about("Sign JWT subcommand")
|
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
|
||||||
.arg(Arg::with_name("key-id").short("K").long("key-id").takes_value(true).help("Header key ID"))
|
|
||||||
.arg(Arg::with_name("claims").short("C").long("claims").takes_value(true).multiple(true).help("Claims, key:value"))
|
|
||||||
.arg(Arg::with_name("payload").short("P").long("payload").takes_value(true).help("Claims in JSON"))
|
|
||||||
.arg(Arg::with_name("jti").long("jti").help("Claims jti"))
|
|
||||||
.arg(Arg::with_name("validity").long("validity").takes_value(true).help("Claims validity period e.g. 10m means 10 minutes (s - second, m - minute, h - hour, d - day)"))
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
|
|
||||||
let slot = opt_value_result!(
|
|
||||||
sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
|
||||||
|
|
||||||
let key_id = sub_arg_matches.value_of("key-id");
|
|
||||||
let claims = sub_arg_matches.values_of("claims");
|
|
||||||
let payload = sub_arg_matches.value_of("payload");
|
|
||||||
let validity = sub_arg_matches.value_of("validity");
|
|
||||||
let jti = sub_arg_matches.is_present("jti");
|
|
||||||
|
|
||||||
let header = Header {
|
|
||||||
key_id: key_id.map(ToString::to_string),
|
|
||||||
type_: Some(HeaderType::JsonWebToken),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let mut jwt_claims = Map::new();
|
|
||||||
if let Some(payload) = payload {
|
|
||||||
match serde_json::from_str::<Value>(payload) {
|
|
||||||
Ok(Value::Object(claims_map)) => {
|
|
||||||
claims_map.into_iter().for_each(|(k, v)| {
|
|
||||||
jwt_claims.insert(k, v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(value) => { warning!("Not valid payload map: {}", value); }
|
|
||||||
Err(e) => { warning!("Not valid payload value: {}", e); }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match (payload, claims) {
|
|
||||||
(Some(_), None) => {}
|
|
||||||
(_, Some(claims)) => {
|
|
||||||
for claim in claims {
|
|
||||||
match split_claim(claim) {
|
|
||||||
None => { warning!("Claim '{}' do not contains ':'", claim); }
|
|
||||||
Some((k, v)) => { jwt_claims.insert(k, v); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !jwt_claims.contains_key("sub") {
|
|
||||||
return simple_error!("Claim sub is not assigned.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return simple_error!("Payload or Claims is required."),
|
|
||||||
}
|
|
||||||
|
|
||||||
// set jti, iat and sub
|
|
||||||
if jti && !jwt_claims.contains_key("jti") {
|
|
||||||
jwt_claims.insert("jti".to_string(), Value::String(format!("jti-{}", util_time::get_current_millis())));
|
|
||||||
}
|
|
||||||
if let Some(validity) = validity {
|
|
||||||
match util_time::parse_duration(validity) {
|
|
||||||
None => { warning!("Bad validity: {}", validity) }
|
|
||||||
Some(validity) => {
|
|
||||||
let current_secs = (util_time::get_current_millis() / 1000) as u64;
|
|
||||||
jwt_claims.insert("iat".to_string(), Value::Number(Number::from(current_secs)));
|
|
||||||
jwt_claims.insert("exp".to_string(), Value::Number(Number::from(current_secs + validity.as_secs())));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}");
|
|
||||||
let slot_id = opt_result!(pivutil::get_slot_id(slot), "Get slot id failed: {}");
|
|
||||||
let pin_opt = pivutil::check_read_pin(&mut yk, slot_id, sub_arg_matches);
|
|
||||||
|
|
||||||
let token_string = sign_jwt(&mut yk, slot_id, &pin_opt, header, &payload, &jwt_claims)?;
|
|
||||||
debugging!("Singed JWT: {}", token_string);
|
|
||||||
if json_output { json.insert("token", token_string.clone()); }
|
|
||||||
|
|
||||||
if json_output {
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn sign_jwt(yk: &mut YubiKey, slot_id: SlotId, pin_opt: &Option<String>, mut header: Header, payload: &Option<&str>, claims: &Map<String, Value>) -> XResult<String> {
|
|
||||||
if let Some(pin) = pin_opt {
|
|
||||||
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
|
|
||||||
}
|
|
||||||
let cert = match Certificate::read(yk, slot_id) {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => return simple_error!("Read YubiKey certificate failed: {}", e),
|
|
||||||
};
|
|
||||||
let piv_algorithm_id = pivutil::get_algorithm_id(&cert.cert.tbs_certificate.subject_public_key_info)?;
|
|
||||||
|
|
||||||
let (jwt_algorithm, yk_algorithm) = match piv_algorithm_id {
|
|
||||||
AlgorithmId::Rsa1024 => return simple_error!("RSA 1024 bits not supported."),
|
|
||||||
AlgorithmId::Rsa2048 => (AlgorithmType::Rs256, AlgorithmId::Rsa2048),
|
|
||||||
AlgorithmId::EccP256 => (AlgorithmType::Es256, AlgorithmId::EccP256),
|
|
||||||
AlgorithmId::EccP384 => (AlgorithmType::Es384, AlgorithmId::EccP384),
|
|
||||||
};
|
|
||||||
|
|
||||||
header.algorithm = jwt_algorithm;
|
|
||||||
debugging!("Header: {:?}", header);
|
|
||||||
debugging!("Claims: {:?}", claims);
|
|
||||||
|
|
||||||
let header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
|
||||||
let claims = match (payload, claims.is_empty()) {
|
|
||||||
(Some(payload), true) => Cow::Owned(util::base64_encode_url_safe_no_pad(payload.as_bytes())),
|
|
||||||
(_, _) => opt_result!(claims.to_base64(), "Claims to base64 failed: {}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tobe_signed = vec![];
|
|
||||||
tobe_signed.extend_from_slice(header.as_bytes());
|
|
||||||
tobe_signed.extend_from_slice(SEPARATOR.as_bytes());
|
|
||||||
tobe_signed.extend_from_slice(claims.as_bytes());
|
|
||||||
let raw_in = match jwt_algorithm {
|
|
||||||
AlgorithmType::Rs256 => rsautil::pkcs15_sha256_rsa_2048_padding_for_sign(
|
|
||||||
&digest::sha256_bytes(&tobe_signed)),
|
|
||||||
AlgorithmType::Es256 => digest::sha256_bytes(&tobe_signed),
|
|
||||||
AlgorithmType::Es384 => digest::sha384_bytes(&tobe_signed),
|
|
||||||
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signed_data = opt_result!(
|
|
||||||
sign_data(yk, &raw_in, yk_algorithm, slot_id), "Sign YubiKey failed: {}");
|
|
||||||
let signed_data = match jwt_algorithm {
|
|
||||||
AlgorithmType::Rs256 => signed_data.to_vec(),
|
|
||||||
AlgorithmType::Es256 | AlgorithmType::Es384 => parse_ecdsa_to_rs(signed_data.as_slice())?,
|
|
||||||
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signature = util::base64_encode_url_safe_no_pad(&signed_data);
|
|
||||||
|
|
||||||
Ok([&*header, &*claims, &signature].join(SEPARATOR))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn split_claim(claim: &str) -> Option<(String, Value)> {
|
|
||||||
let mut k = String::new();
|
|
||||||
let mut v = String::new();
|
|
||||||
|
|
||||||
let mut claim_chars = claim.chars().peekable();
|
|
||||||
let ty = if let Some('^') = claim_chars.peek() {
|
|
||||||
let _ = claim_chars.next();
|
|
||||||
claim_chars.next()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let mut is_k = true;
|
|
||||||
for c in claim_chars {
|
|
||||||
if is_k {
|
|
||||||
if c == ':' {
|
|
||||||
is_k = false;
|
|
||||||
} else {
|
|
||||||
k.push(c);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_k {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
match ty {
|
|
||||||
None | Some('s') => Some((k, Value::String(v))),
|
|
||||||
Some('b') => Some((k, Value::Bool(["true", "yes", "1"].contains(&v.as_str())))),
|
|
||||||
Some('i') | Some('n') => {
|
|
||||||
if let Ok(i) = v.parse::<i64>() {
|
|
||||||
return Some((k, Value::Number(Number::from(i))));
|
|
||||||
}
|
|
||||||
if let Ok(f) = v.parse::<f64>() {
|
|
||||||
if let Some(number_f64) = Number::from_f64(f) {
|
|
||||||
return Some((k, Value::Number(number_f64)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
warning!("Bad number: {} in claim: {}", v, claim);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
warning!("Unknown type: {} in claim: {}", ty.unwrap(), claim);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use jwt::header::HeaderType;
|
|
||||||
use jwt::{AlgorithmType, Header, ToBase64};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
|
||||||
use rust_util::{util_msg, util_time, XResult};
|
|
||||||
use serde_json::{Map, Number, Value};
|
|
||||||
|
|
||||||
use crate::{cmd_signjwt, digest, ecdsautil, hmacutil, rsautil, util};
|
|
||||||
|
|
||||||
const SEPARATOR: &str = ".";
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"sign-jwt-soft"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
|
||||||
SubCommand::with_name(self.name()).about("Sign JWT subcommand")
|
|
||||||
.arg(Arg::with_name("private-key").short("k").long("private-key").takes_value(true).help("Private key PKCS#8"))
|
|
||||||
.arg(Arg::with_name("key-id").short("K").long("key-id").takes_value(true).help("Header key ID"))
|
|
||||||
.arg(Arg::with_name("claims").short("C").long("claims").takes_value(true).multiple(true).help("Claims, key:value"))
|
|
||||||
.arg(Arg::with_name("payload").short("P").long("payload").takes_value(true).help("Claims in JSON"))
|
|
||||||
.arg(Arg::with_name("jti").long("jti").help("Claims jti"))
|
|
||||||
.arg(Arg::with_name("validity").long("validity").takes_value(true).help("Claims validity period e.g. 10m means 10 minutes (s - second, m - minute, h - hour, d - day)"))
|
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
|
||||||
if json_output {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
|
||||||
|
|
||||||
let private_key = opt_value_result!(
|
|
||||||
sub_arg_matches.value_of("private-key"),
|
|
||||||
"Private key PKCS#8 DER base64 encoded or PEM"
|
|
||||||
);
|
|
||||||
let private_key = hmacutil::try_hmac_decrypt_to_string(private_key)?;
|
|
||||||
|
|
||||||
let key_id = sub_arg_matches.value_of("key-id");
|
|
||||||
let claims = sub_arg_matches.values_of("claims");
|
|
||||||
let payload = sub_arg_matches.value_of("payload");
|
|
||||||
let validity = sub_arg_matches.value_of("validity");
|
|
||||||
let jti = sub_arg_matches.is_present("jti");
|
|
||||||
|
|
||||||
let header = Header {
|
|
||||||
key_id: key_id.map(ToString::to_string),
|
|
||||||
type_: Some(HeaderType::JsonWebToken),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let mut jwt_claims = Map::new();
|
|
||||||
if let Some(payload) = payload {
|
|
||||||
match serde_json::from_str::<Value>(payload) {
|
|
||||||
Ok(Value::Object(claims_map)) => {
|
|
||||||
claims_map.into_iter().for_each(|(k, v)| {
|
|
||||||
jwt_claims.insert(k, v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(value) => {
|
|
||||||
warning!("Not valid payload map: {}", value);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
warning!("Not valid payload value: {}", e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
match (payload, claims) {
|
|
||||||
(Some(_), None) => {}
|
|
||||||
(_, Some(claims)) => {
|
|
||||||
for claim in claims {
|
|
||||||
match cmd_signjwt::split_claim(claim) {
|
|
||||||
None => {
|
|
||||||
warning!("Claim '{}' do not contains ':'", claim);
|
|
||||||
}
|
|
||||||
Some((k, v)) => {
|
|
||||||
jwt_claims.insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !jwt_claims.contains_key("sub") {
|
|
||||||
return simple_error!("Claim sub is not assigned.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => return simple_error!("Payload or Claims is required."),
|
|
||||||
}
|
|
||||||
|
|
||||||
// set jti, iat and sub
|
|
||||||
if jti && !jwt_claims.contains_key("jti") {
|
|
||||||
jwt_claims.insert(
|
|
||||||
"jti".to_string(),
|
|
||||||
Value::String(format!("jti-{}", util_time::get_current_millis())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if let Some(validity) = validity {
|
|
||||||
match util_time::parse_duration(validity) {
|
|
||||||
None => {
|
|
||||||
warning!("Bad validity: {}", validity)
|
|
||||||
}
|
|
||||||
Some(validity) => {
|
|
||||||
let current_secs = (util_time::get_current_millis() / 1000) as u64;
|
|
||||||
jwt_claims.insert("iat".to_string(), Value::Number(Number::from(current_secs)));
|
|
||||||
jwt_claims.insert(
|
|
||||||
"exp".to_string(),
|
|
||||||
Value::Number(Number::from(current_secs + validity.as_secs())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let token_string = sign_jwt(&private_key, header, &payload, &jwt_claims)?;
|
|
||||||
debugging!("Singed JWT: {}", token_string);
|
|
||||||
if json_output {
|
|
||||||
json.insert("token", token_string.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
if json_output {
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign_jwt(
|
|
||||||
private_key: &str,
|
|
||||||
mut header: Header,
|
|
||||||
payload: &Option<&str>,
|
|
||||||
claims: &Map<String, Value>,
|
|
||||||
) -> XResult<String> {
|
|
||||||
let p256_private_key_d = ecdsautil::parse_p256_private_key(private_key).ok();
|
|
||||||
let p384_private_key_d = ecdsautil::parse_p384_private_key(private_key).ok();
|
|
||||||
|
|
||||||
let (jwt_algorithm, private_key_d) = match (p256_private_key_d, p384_private_key_d) {
|
|
||||||
(Some(p256_private_key_d), None) => (AlgorithmType::Es256, p256_private_key_d),
|
|
||||||
(None, Some(p384_private_key_d)) => (AlgorithmType::Es384, p384_private_key_d),
|
|
||||||
_ => return simple_error!("Invalid private key: {}", private_key),
|
|
||||||
};
|
|
||||||
|
|
||||||
header.algorithm = jwt_algorithm;
|
|
||||||
debugging!("Header: {:?}", header);
|
|
||||||
debugging!("Claims: {:?}", claims);
|
|
||||||
|
|
||||||
let header = opt_result!(header.to_base64(), "Header to base64 failed: {}");
|
|
||||||
let claims = match (payload, claims.is_empty()) {
|
|
||||||
(Some(payload), true) => {
|
|
||||||
Cow::Owned(util::base64_encode_url_safe_no_pad(payload.as_bytes()))
|
|
||||||
}
|
|
||||||
(_, _) => opt_result!(claims.to_base64(), "Claims to base64 failed: {}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tobe_signed = vec![];
|
|
||||||
tobe_signed.extend_from_slice(header.as_bytes());
|
|
||||||
tobe_signed.extend_from_slice(SEPARATOR.as_bytes());
|
|
||||||
tobe_signed.extend_from_slice(claims.as_bytes());
|
|
||||||
let raw_in = match jwt_algorithm {
|
|
||||||
AlgorithmType::Rs256 => {
|
|
||||||
rsautil::pkcs15_sha256_rsa_2048_padding_for_sign(&digest::sha256_bytes(&tobe_signed))
|
|
||||||
}
|
|
||||||
AlgorithmType::Es256 => digest::sha256_bytes(&tobe_signed),
|
|
||||||
AlgorithmType::Es384 => digest::sha384_bytes(&tobe_signed),
|
|
||||||
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signed_data = match jwt_algorithm {
|
|
||||||
AlgorithmType::Es256 => ecdsautil::sign_p256_rs(&private_key_d, &raw_in)?,
|
|
||||||
AlgorithmType::Es384 => ecdsautil::sign_p384_rs(&private_key_d, &raw_in)?,
|
|
||||||
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
|
|
||||||
};
|
|
||||||
|
|
||||||
let signature = util::base64_encode_url_safe_no_pad(&signed_data);
|
|
||||||
|
|
||||||
Ok([&*header, &*claims, &signature].join(SEPARATOR))
|
|
||||||
}
|
|
||||||
209
src/cmd_ssh_agent.rs
Normal file
209
src/cmd_ssh_agent.rs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
use std::fs::remove_file;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::cmdutil;
|
||||||
|
use crate::ecdsautil::{parse_ec_public_key_to_point, parse_ecdsa_r_and_s};
|
||||||
|
use crate::util::base64_encode;
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rsa::RsaPublicKey;
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
use ssh_agent_lib::agent::{listen, Session};
|
||||||
|
use ssh_agent_lib::error::AgentError;
|
||||||
|
use ssh_agent_lib::proto::{Extension, Identity, SignRequest};
|
||||||
|
use ssh_agent_lib::ssh_encoding::Encode;
|
||||||
|
use ssh_agent_lib::ssh_key::public::KeyData;
|
||||||
|
use ssh_agent_lib::ssh_key::{Algorithm, Signature};
|
||||||
|
use ssh_key::public::EcdsaPublicKey;
|
||||||
|
use ssh_key::{EcdsaCurve, Mpint};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use tokio::net::UnixListener as Listener;
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
struct MySshAgent {
|
||||||
|
parameter: String,
|
||||||
|
comment: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MySshAgent {
|
||||||
|
fn new(parameter: &str) -> XResult<Self> {
|
||||||
|
Ok(MySshAgent {
|
||||||
|
parameter: parameter.to_string(),
|
||||||
|
comment: parameter.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[ssh_agent_lib::async_trait]
|
||||||
|
impl Session for MySshAgent {
|
||||||
|
async fn request_identities(&mut self) -> Result<Vec<Identity>, AgentError> {
|
||||||
|
information!("request_identities");
|
||||||
|
let identity = match get_identity(&self.parameter, &self.comment) {
|
||||||
|
Ok(identity) => identity,
|
||||||
|
Err(e) => {
|
||||||
|
failure!("Get identity failed: {}", e);
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut writer = vec![];
|
||||||
|
identity.pubkey.encode(&mut writer).unwrap();
|
||||||
|
println!("{}", base64_encode(&writer));
|
||||||
|
Ok(vec![identity])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sign(&mut self, request: SignRequest) -> Result<Signature, AgentError> {
|
||||||
|
information!("sign, request: {:?}", request);
|
||||||
|
|
||||||
|
let algorithm = &request.pubkey.algorithm();
|
||||||
|
match algorithm {
|
||||||
|
Algorithm::Ecdsa { curve: _ } => {}
|
||||||
|
Algorithm::Rsa { hash: _ } => {}
|
||||||
|
Algorithm::Ed25519 => {
|
||||||
|
debugging!("Algorithm::Ed25519 not supported");
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
Algorithm::Dsa => {
|
||||||
|
debugging!("Algorithm::Dsa not supported");
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
Algorithm::SkEcdsaSha2NistP256 => {
|
||||||
|
debugging!("Algorithm::SkEcdsaSha2NistP256 not supported");
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
Algorithm::SkEd25519 => {
|
||||||
|
debugging!("Algorithm::SkEd25519 not supported");
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
Algorithm::Other(algorithm_name) => {
|
||||||
|
debugging!(
|
||||||
|
"Algorithm::Other not supported, name: {}",
|
||||||
|
algorithm_name.as_str()
|
||||||
|
);
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
&_ => {
|
||||||
|
debugging!("Algorithm::Unknown not supported");
|
||||||
|
return Err(AgentError::Failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let signature = external_command_rs::external_sign(
|
||||||
|
"card-cli",
|
||||||
|
self.parameter.as_str(),
|
||||||
|
"ES384",
|
||||||
|
&request.data,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
information!("{}", hex::encode(&signature));
|
||||||
|
let (r, s) = parse_ecdsa_r_and_s(signature.as_slice()).unwrap();
|
||||||
|
let mut ssh_signature = vec![];
|
||||||
|
let r_mpint = Mpint::from_bytes(&r).unwrap();
|
||||||
|
let s_mpint = Mpint::from_bytes(&s).unwrap();
|
||||||
|
r_mpint.encode(&mut ssh_signature).unwrap();
|
||||||
|
s_mpint.encode(&mut ssh_signature).unwrap();
|
||||||
|
Ok(Signature::new(
|
||||||
|
Algorithm::Ecdsa {
|
||||||
|
curve: EcdsaCurve::NistP384,
|
||||||
|
},
|
||||||
|
ssh_signature,
|
||||||
|
)
|
||||||
|
.map_err(AgentError::other)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn extension(&mut self, extension: Extension) -> Result<Option<Extension>, AgentError> {
|
||||||
|
information!("extension: {:?}", extension);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_identity(uri: &str, comment: &str) -> XResult<Identity> {
|
||||||
|
let public_key_bytes = external_command_rs::external_public_key("card-cli", uri)?;
|
||||||
|
|
||||||
|
if let Ok(ec_point) = parse_ec_public_key_to_point(&public_key_bytes) {
|
||||||
|
let identity = Identity {
|
||||||
|
pubkey: KeyData::Ecdsa(EcdsaPublicKey::from_sec1_bytes(&ec_point).unwrap()),
|
||||||
|
comment: comment.to_string(),
|
||||||
|
};
|
||||||
|
return Ok(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(rsa_public_key) = RsaPublicKey::from_public_key_der(&public_key_bytes) {
|
||||||
|
let identity = Identity {
|
||||||
|
pubkey: KeyData::Rsa(ssh_key::public::RsaPublicKey::try_from(&rsa_public_key).unwrap()),
|
||||||
|
comment: comment.to_string(),
|
||||||
|
};
|
||||||
|
return Ok(identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
simple_error!("Unknown uri algorithm: {}", uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ssh-agent"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("SSH-Agent subcommand")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("sock-file")
|
||||||
|
.long("sock-file")
|
||||||
|
.default_value("connect.ssh")
|
||||||
|
.help("Sock file, usage SSH_AUTH_SOCK=sock-file ssh ..."),
|
||||||
|
)
|
||||||
|
.arg(cmdutil::build_parameter_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
warning!("Not works yet.");
|
||||||
|
|
||||||
|
debugging!("Sub args: {:?}", sub_arg_matches);
|
||||||
|
|
||||||
|
let parameter = sub_arg_matches.value_of("parameter").unwrap();
|
||||||
|
let sock_file = sub_arg_matches.value_of("sock-file").unwrap();
|
||||||
|
information!("Sock file: {}", sock_file);
|
||||||
|
|
||||||
|
let sock_file_path = PathBuf::from(".");
|
||||||
|
match std::fs::canonicalize(sock_file_path) {
|
||||||
|
Ok(canonicalized_sock_file_path) => information!(
|
||||||
|
"SSH_AUTH_SOCK={}/{}",
|
||||||
|
canonicalized_sock_file_path.to_str().unwrap_or("-"),
|
||||||
|
sock_file
|
||||||
|
),
|
||||||
|
Err(e) => warning!("Get canonicalized sock file path failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// let ssh_agent = SshAgent::new()?;
|
||||||
|
// // TODO information!("{}", &ssh_agent.ssh_string);
|
||||||
|
|
||||||
|
let _ = remove_file(sock_file);
|
||||||
|
|
||||||
|
information!("Start unix socket: {}", sock_file);
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
rt.block_on(async move {
|
||||||
|
listen(
|
||||||
|
Listener::bind(sock_file).unwrap(),
|
||||||
|
MySshAgent::new(parameter).unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
});
|
||||||
|
|
||||||
|
// opt_result!(
|
||||||
|
// ssh_agent.run_unix(sock_file),
|
||||||
|
// "Run unix socket: {}, failed: {}",
|
||||||
|
// sock_file
|
||||||
|
// );
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ use ssh_agent::proto::{from_bytes, RsaPublicKey, signature, Signature, to_bytes}
|
|||||||
use ssh_agent::proto::message::{self, Message};
|
use ssh_agent::proto::message::{self, Message};
|
||||||
use ssh_agent::proto::public_key::PublicKey;
|
use ssh_agent::proto::public_key::PublicKey;
|
||||||
|
|
||||||
use crate::digest::{copy_sha256, copy_sha512};
|
use crate::digestutil::{copy_sha256, copy_sha512};
|
||||||
use crate::pinutil;
|
use crate::pinutil;
|
||||||
use crate::sshutil::{generate_ssh_string, with_sign};
|
use crate::sshutil::{generate_ssh_string, with_sign};
|
||||||
|
|
||||||
@@ -139,10 +139,10 @@ impl Agent for SshAgent {
|
|||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
impl Command for CommandImpl {
|
||||||
fn name(&self) -> &str { "ssh-agent" }
|
fn name(&self) -> &str { "ssh-agent-gpg" }
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("SSH-Agent subcommand")
|
SubCommand::with_name(self.name()).about("SSH-Agent OpenPGP card subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").default_value("123456").help("OpenPGP card user pin"))
|
.arg(Arg::with_name("pin").short("p").long("pin").default_value("123456").help("OpenPGP card user pin"))
|
||||||
.arg(Arg::with_name("pgp").long("pgp").help("Use PGP"))
|
.arg(Arg::with_name("pgp").long("pgp").help("Use PGP"))
|
||||||
.arg(Arg::with_name("pgp-sign").long("pgp-sign").help("Use PGP sign"))
|
.arg(Arg::with_name("pgp-sign").long("pgp-sign").help("Use PGP sign"))
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use sshcerts::ssh::PublicKeyKind;
|
use sshcerts_hatter_fork::ssh::PublicKeyKind;
|
||||||
use sshcerts::{Certificate, PrivateKey, PublicKey};
|
use sshcerts_hatter_fork::{Certificate, PrivateKey, PublicKey};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -3,19 +3,19 @@ use ecdsa::elliptic_curve::pkcs8::der::Encode;
|
|||||||
use rand::random;
|
use rand::random;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::{util_time, XResult};
|
use rust_util::{util_time, XResult};
|
||||||
use sshcerts::ssh::{CurveKind, PublicKeyKind, SSHCertificateSigner};
|
use sshcerts_hatter_fork::ssh::{CurveKind, PublicKeyKind, SSHCertificateSigner};
|
||||||
use sshcerts::utils::format_signature_for_ssh;
|
use sshcerts_hatter_fork::utils::format_signature_for_ssh;
|
||||||
use sshcerts::x509::extract_ssh_pubkey_from_x509_certificate;
|
use sshcerts_hatter_fork::x509::extract_ssh_pubkey_from_x509_certificate;
|
||||||
use sshcerts::{CertType, Certificate, PublicKey};
|
use sshcerts_hatter_fork::{CertType, Certificate, PublicKey};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use yubikey::piv::{sign_data, AlgorithmId, SlotId};
|
use yubikey::piv::{sign_data, AlgorithmId, SlotId};
|
||||||
use yubikey::{Key, YubiKey};
|
use yubikey::{Key, YubiKey};
|
||||||
|
|
||||||
use crate::digest::{sha256_bytes, sha384_bytes};
|
use crate::digestutil::{sha256_bytes, sha384_bytes};
|
||||||
use crate::pivutil::slot_equals;
|
use crate::pivutil::slot_equals;
|
||||||
use crate::{pinutil, pivutil, util};
|
use crate::{cmdutil, pinutil, pivutil, util};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -27,9 +27,9 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("SSH PIV sign cert subcommand")
|
SubCommand::with_name(self.name()).about("SSH PIV sign cert subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.arg(Arg::with_name("key-id").short("k").long("key-id").takes_value(true).default_value("default_key_id").help("SSH user CA key id"))
|
.arg(Arg::with_name("key-id").short("k").long("key-id").takes_value(true).default_value("default_key_id").help("SSH user CA key id"))
|
||||||
.arg(Arg::with_name("principal").short("P").long("principal").takes_value(true).default_value("root").multiple(true).help("SSH user CA principal"))
|
.arg(Arg::with_name("principal").short("P").long("principal").takes_value(true).default_value("root").multiple(true).help("SSH user CA principal"))
|
||||||
.arg(Arg::with_name("pub").short("f").long("pub").alias("pub-file").required(true).takes_value(true).help("SSH public key file"))
|
.arg(Arg::with_name("pub").short("f").long("pub").alias("pub-file").required(true).takes_value(true).help("SSH public key file"))
|
||||||
@@ -6,7 +6,7 @@ use rust_util::util_msg;
|
|||||||
use yubikey::{Key, YubiKey};
|
use yubikey::{Key, YubiKey};
|
||||||
use yubikey::piv::{AlgorithmId, sign_data};
|
use yubikey::piv::{AlgorithmId, sign_data};
|
||||||
|
|
||||||
use crate::{pinutil, pivutil, util};
|
use crate::{cmdutil, pinutil, pivutil, util};
|
||||||
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
|
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
|
||||||
use crate::sshutil::SshVecWriter;
|
use crate::sshutil::SshVecWriter;
|
||||||
|
|
||||||
@@ -18,9 +18,9 @@ impl Command for CommandImpl {
|
|||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
SubCommand::with_name(self.name()).about("SSH piv sign subcommand")
|
SubCommand::with_name(self.name()).about("SSH piv sign subcommand")
|
||||||
.arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN"))
|
.arg(cmdutil::build_slot_arg())
|
||||||
.arg(Arg::with_name("no-pin").long("no-pin").help("No PIN"))
|
.arg(cmdutil::build_pin_arg())
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
.arg(cmdutil::build_no_pin_arg())
|
||||||
.arg(Arg::with_name("namespace").short("n").long("namespace").takes_value(true).help("Namespace"))
|
.arg(Arg::with_name("namespace").short("n").long("namespace").takes_value(true).help("Namespace"))
|
||||||
.arg(Arg::with_name("in").long("in").required(true).takes_value(true).help("In file, - for stdin"))
|
.arg(Arg::with_name("in").long("in").required(true).takes_value(true).help("In file, - for stdin"))
|
||||||
}
|
}
|
||||||
@@ -91,14 +91,14 @@ impl Command for CommandImpl {
|
|||||||
sign_message.write_string(namespace.as_bytes());
|
sign_message.write_string(namespace.as_bytes());
|
||||||
sign_message.write_string("".as_bytes());
|
sign_message.write_string("".as_bytes());
|
||||||
sign_message.write_string("sha512".as_bytes());
|
sign_message.write_string("sha512".as_bytes());
|
||||||
let data_digest = crate::digest::sha512_bytes(&data);
|
let data_digest = crate::digestutil::sha512_bytes(&data);
|
||||||
debugging!("Data digest: {} (sha512)", hex::encode(&data_digest));
|
debugging!("Data digest: {} (sha512)", hex::encode(&data_digest));
|
||||||
sign_message.write_string(&data_digest);
|
sign_message.write_string(&data_digest);
|
||||||
debugging!("Singed message: {}", hex::encode(&sign_message));
|
debugging!("Singed message: {}", hex::encode(&sign_message));
|
||||||
let tobe_signed_data = if ec_bit_len == 256 {
|
let tobe_signed_data = if ec_bit_len == 256 {
|
||||||
crate::digest::sha256_bytes(&sign_message)
|
crate::digestutil::sha256_bytes(&sign_message)
|
||||||
} else {
|
} else {
|
||||||
crate::digest::sha384_bytes(&sign_message)
|
crate::digestutil::sha384_bytes(&sign_message)
|
||||||
};
|
};
|
||||||
debugging!("Digest of signed message: {}", hex::encode(&tobe_signed_data));
|
debugging!("Digest of signed message: {}", hex::encode(&tobe_signed_data));
|
||||||
|
|
||||||
129
src/cmd_ssh_pub_key.rs
Normal file
129
src/cmd_ssh_pub_key.rs
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
use crate::digestutil::sha256_bytes;
|
||||||
|
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
|
||||||
|
use crate::sshutil::SshVecWriter;
|
||||||
|
use crate::{cmdutil, pivutil, util};
|
||||||
|
use clap::{App, Arg, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use yubikey::piv::AlgorithmId;
|
||||||
|
use yubikey::{Key, YubiKey};
|
||||||
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"ssh-pub-key"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name())
|
||||||
|
.about("SSH public key subcommand")
|
||||||
|
.arg(cmdutil::build_slot_arg())
|
||||||
|
.arg(Arg::with_name("ca").long("ca").help("SSH cert-authority"))
|
||||||
|
.arg(cmdutil::build_json_arg())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
|
let slot = opt_value_result!(
|
||||||
|
sub_arg_matches.value_of("slot"),
|
||||||
|
"--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"
|
||||||
|
);
|
||||||
|
let ca = sub_arg_matches.is_present("ca");
|
||||||
|
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
||||||
|
let slot_id = pivutil::get_slot_id(slot)?;
|
||||||
|
|
||||||
|
let mut algorithm_id_opt = None;
|
||||||
|
let mut ec_key_point = vec![];
|
||||||
|
match Key::list(&mut yk) {
|
||||||
|
Err(e) => warning!("List keys failed: {}", e),
|
||||||
|
Ok(keys) => {
|
||||||
|
for k in &keys {
|
||||||
|
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
|
||||||
|
if slot_equals(&slot_id, &slot_str) {
|
||||||
|
let cert = &k.certificate().cert.tbs_certificate;
|
||||||
|
let certificate = k.certificate();
|
||||||
|
if let Ok(algorithm_id) = get_algorithm_id_by_certificate(certificate) {
|
||||||
|
match algorithm_id {
|
||||||
|
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
|
||||||
|
let public_key_bit_string =
|
||||||
|
&cert.subject_public_key_info.subject_public_key;
|
||||||
|
ec_key_point
|
||||||
|
.extend_from_slice(public_key_bit_string.raw_bytes());
|
||||||
|
algorithm_id_opt = Some(algorithm_id);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return simple_error!(
|
||||||
|
"Not P256/384 key: {}",
|
||||||
|
algorithm_id.to_str()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let algorithm_id = match algorithm_id_opt {
|
||||||
|
None => return simple_error!("Slot key not found!"),
|
||||||
|
Some(algorithm_id) => algorithm_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let ssh_algorithm = match algorithm_id {
|
||||||
|
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => panic!("Not supported."),
|
||||||
|
AlgorithmId::EccP256 => "nistp256",
|
||||||
|
AlgorithmId::EccP384 => "nistp384",
|
||||||
|
};
|
||||||
|
|
||||||
|
information!("SSH algorithm: {}", ssh_algorithm);
|
||||||
|
information!("ECDSA public key: {}", hex::encode(&ec_key_point));
|
||||||
|
|
||||||
|
// ECDSA SSH public key format:
|
||||||
|
// string ecdsa-sha2-[identifier]
|
||||||
|
// byte[n] ecc_key_blob
|
||||||
|
//
|
||||||
|
// ecc_key_blob:
|
||||||
|
// string [identifier]
|
||||||
|
// string Q
|
||||||
|
//
|
||||||
|
// [identifier] will be nistp256 or nistp384
|
||||||
|
let mut ssh_pub_key = vec![];
|
||||||
|
ssh_pub_key.write_string(format!("ecdsa-sha2-{}", ssh_algorithm).as_bytes());
|
||||||
|
let mut ecc_key_blob = vec![];
|
||||||
|
ecc_key_blob.write_string(ssh_algorithm.as_bytes());
|
||||||
|
ecc_key_blob.write_string(&ec_key_point);
|
||||||
|
ssh_pub_key.write_bytes(&ecc_key_blob);
|
||||||
|
|
||||||
|
let ssh_pub_key_sha256 = sha256_bytes(&ssh_pub_key);
|
||||||
|
information!(
|
||||||
|
"SSH key SHA256: {} (base64)",
|
||||||
|
base64_encode(&ssh_pub_key_sha256)
|
||||||
|
);
|
||||||
|
information!("SSH key SHA256: {} (hex)", hex::encode(&ssh_pub_key_sha256));
|
||||||
|
eprintln!();
|
||||||
|
|
||||||
|
let ssh_pub_key = format!(
|
||||||
|
"{}ecdsa-sha2-{} {} YubiKey-PIV-{}",
|
||||||
|
if ca {
|
||||||
|
"cert-authority,principals=\"root\" "
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
|
ssh_algorithm,
|
||||||
|
base64_encode(&ssh_pub_key),
|
||||||
|
slot_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if json_output {
|
||||||
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
json.insert("ssh_pub_key", ssh_pub_key);
|
||||||
|
util::print_pretty_json(&json);
|
||||||
|
} else {
|
||||||
|
println!("{}", &ssh_pub_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
use crate::digest::sha256_bytes;
|
|
||||||
use crate::pivutil;
|
|
||||||
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
|
|
||||||
use crate::sshutil::SshVecWriter;
|
|
||||||
use base64::engine::general_purpose::STANDARD;
|
|
||||||
use base64::Engine;
|
|
||||||
use clap::{App, Arg, ArgMatches, SubCommand};
|
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
|
||||||
use rust_util::util_msg;
|
|
||||||
use yubikey::piv::AlgorithmId;
|
|
||||||
use yubikey::{Key, YubiKey};
|
|
||||||
|
|
||||||
pub struct CommandImpl;
|
|
||||||
|
|
||||||
impl Command for CommandImpl {
|
|
||||||
fn name(&self) -> &str { "ssh-pub-key" }
|
|
||||||
|
|
||||||
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
|
||||||
SubCommand::with_name(self.name()).about("SSH public key subcommand")
|
|
||||||
.arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"))
|
|
||||||
.arg(Arg::with_name("ca").long("ca").help("SSH cert-authority"))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
|
||||||
util_msg::set_logger_std_out(false);
|
|
||||||
|
|
||||||
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e");
|
|
||||||
let ca = sub_arg_matches.is_present("ca");
|
|
||||||
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
|
|
||||||
let slot_id = pivutil::get_slot_id(slot)?;
|
|
||||||
|
|
||||||
let mut algorithm_id_opt = None;
|
|
||||||
let mut ec_key_point = vec![];
|
|
||||||
match Key::list(&mut yk) {
|
|
||||||
Err(e) => warning!("List keys failed: {}", e),
|
|
||||||
Ok(keys) => for k in &keys {
|
|
||||||
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
|
|
||||||
if slot_equals(&slot_id, &slot_str) {
|
|
||||||
let cert = &k.certificate().cert.tbs_certificate;
|
|
||||||
let certificate = k.certificate();
|
|
||||||
if let Ok(algorithm_id) = get_algorithm_id_by_certificate(certificate) {
|
|
||||||
match algorithm_id {
|
|
||||||
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
|
|
||||||
let public_key_bit_string = &cert.subject_public_key_info.subject_public_key;
|
|
||||||
ec_key_point.extend_from_slice(public_key_bit_string.raw_bytes());
|
|
||||||
algorithm_id_opt = Some(algorithm_id);
|
|
||||||
}
|
|
||||||
_ => return simple_error!("Not P256/384 key: {}", algorithm_id.to_str()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let algorithm_id = match algorithm_id_opt {
|
|
||||||
None => return simple_error!("Slot key not found!"),
|
|
||||||
Some(algorithm_id) => algorithm_id,
|
|
||||||
};
|
|
||||||
|
|
||||||
let ssh_algorithm = match algorithm_id {
|
|
||||||
AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => panic!("Not supported."),
|
|
||||||
AlgorithmId::EccP256 => "nistp256",
|
|
||||||
AlgorithmId::EccP384 => "nistp384",
|
|
||||||
};
|
|
||||||
|
|
||||||
information!("SSH algorithm: {}", ssh_algorithm);
|
|
||||||
information!("ECDSA public key: {}", hex::encode(&ec_key_point));
|
|
||||||
|
|
||||||
// ECDSA SSH public key format:
|
|
||||||
// string ecdsa-sha2-[identifier]
|
|
||||||
// byte[n] ecc_key_blob
|
|
||||||
//
|
|
||||||
// ecc_key_blob:
|
|
||||||
// string [identifier]
|
|
||||||
// string Q
|
|
||||||
//
|
|
||||||
// [identifier] will be nistp256 or nistp384
|
|
||||||
let mut ssh_pub_key = vec![];
|
|
||||||
ssh_pub_key.write_string(format!("ecdsa-sha2-{}", ssh_algorithm).as_bytes());
|
|
||||||
let mut ecc_key_blob = vec![];
|
|
||||||
ecc_key_blob.write_string(ssh_algorithm.as_bytes());
|
|
||||||
ecc_key_blob.write_string(&ec_key_point);
|
|
||||||
ssh_pub_key.write_bytes(&ecc_key_blob);
|
|
||||||
|
|
||||||
let ssh_pub_key_sha256 = sha256_bytes(&ssh_pub_key);
|
|
||||||
information!("SSH key SHA256: {} (base64)", STANDARD.encode(&ssh_pub_key_sha256));
|
|
||||||
information!("SSH key SHA256: {} (hex)", hex::encode(&ssh_pub_key_sha256));
|
|
||||||
eprintln!();
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"{}ecdsa-sha2-{} {} Yubikey-PIV-{}",
|
|
||||||
if ca { "cert-authority,principals=\"root\" " } else { "" },
|
|
||||||
ssh_algorithm,
|
|
||||||
STANDARD.encode(&ssh_pub_key),
|
|
||||||
slot_id
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,12 @@ use openssl::hash::MessageDigest;
|
|||||||
use openssl::pkey::PKey;
|
use openssl::pkey::PKey;
|
||||||
use openssl::sign::Verifier;
|
use openssl::sign::Verifier;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use rust_util::util_msg;
|
|
||||||
use x509_parser::certificate::X509Certificate;
|
use x509_parser::certificate::X509Certificate;
|
||||||
use x509_parser::prelude::FromDer;
|
use x509_parser::prelude::FromDer;
|
||||||
|
|
||||||
use crate::digest;
|
use crate::{cmdutil, digestutil, util};
|
||||||
use crate::fido;
|
use crate::fidoutil;
|
||||||
use crate::fido::{U2fRegistrationData, U2fV2Challenge};
|
use crate::fidoutil::{U2fRegistrationData, U2fV2Challenge};
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -29,12 +28,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("timeout").short("t").long("timeout").default_value("30").help("Timeout in seconds"))
|
.arg(Arg::with_name("timeout").short("t").long("timeout").default_value("30").help("Timeout in seconds"))
|
||||||
.arg(Arg::with_name("challenge").long("challenge").takes_value(true).help("Challenge HEX"))
|
.arg(Arg::with_name("challenge").long("challenge").takes_value(true).help("Challenge HEX"))
|
||||||
.arg(Arg::with_name("challenge-with-timestamp-prefix").long("challenge-with-timestamp-prefix").help("Challenge with timestamp prefix"))
|
.arg(Arg::with_name("challenge-with-timestamp-prefix").long("challenge-with-timestamp-prefix").help("Challenge with timestamp prefix"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let timeout_ms = match sub_arg_matches.value_of("timeout").unwrap().parse::<u32>() {
|
let timeout_ms = match sub_arg_matches.value_of("timeout").unwrap().parse::<u32>() {
|
||||||
Ok(t) => (t * 1000) as u64,
|
Ok(t) => (t * 1000) as u64,
|
||||||
@@ -48,12 +46,12 @@ impl Command for CommandImpl {
|
|||||||
let u2fv2_challenge = U2fV2Challenge::new_challenge(challenge_hex, app_id, challenge_with_timestamp_prefix)?;
|
let u2fv2_challenge = U2fV2Challenge::new_challenge(challenge_hex, app_id, challenge_with_timestamp_prefix)?;
|
||||||
let u2fv2_challenge_str = u2fv2_challenge.to_json();
|
let u2fv2_challenge_str = u2fv2_challenge.to_json();
|
||||||
|
|
||||||
let app_id_hash = digest::sha256(app_id);
|
let app_id_hash = digestutil::sha256(app_id);
|
||||||
let challenge_hash = digest::sha256(&u2fv2_challenge_str);
|
let challenge_hash = digestutil::sha256(&u2fv2_challenge_str);
|
||||||
|
|
||||||
let flags = RegisterFlags::empty();
|
let flags = RegisterFlags::empty();
|
||||||
|
|
||||||
let status_tx = fido::start_status_updater();
|
let status_tx = fidoutil::start_status_updater();
|
||||||
|
|
||||||
let (register_tx, register_rx) = channel();
|
let (register_tx, register_rx) = channel();
|
||||||
let callback = StateCallback::new(Box::new(move |rv| {
|
let callback = StateCallback::new(Box::new(move |rv| {
|
||||||
@@ -98,8 +96,8 @@ impl Command for CommandImpl {
|
|||||||
+ u2f_registration_data.attestation_cert.as_ref().map(|c| c.len()).unwrap_or(0);
|
+ u2f_registration_data.attestation_cert.as_ref().map(|c| c.len()).unwrap_or(0);
|
||||||
let sign = ®ister_result.0[sign_prefix_len..];
|
let sign = ®ister_result.0[sign_prefix_len..];
|
||||||
|
|
||||||
let mut json = BTreeMap::new();
|
|
||||||
if json_output {
|
if json_output {
|
||||||
|
let mut json = BTreeMap::new();
|
||||||
// println!("{}", serde_json::to_string_pretty(&u2f_registration_data).unwrap());
|
// println!("{}", serde_json::to_string_pretty(&u2f_registration_data).unwrap());
|
||||||
if let Some(device_name) = u2f_registration_data.device_name {
|
if let Some(device_name) = u2f_registration_data.device_name {
|
||||||
json.insert("device_name", device_name);
|
json.insert("device_name", device_name);
|
||||||
@@ -117,6 +115,8 @@ impl Command for CommandImpl {
|
|||||||
json.insert("app_id_hash", hex::encode(&app_id_hash));
|
json.insert("app_id_hash", hex::encode(&app_id_hash));
|
||||||
json.insert("challenge", u2fv2_challenge_str);
|
json.insert("challenge", u2fv2_challenge_str);
|
||||||
json.insert("challenge_hash", hex::encode(&challenge_hash));
|
json.insert("challenge_hash", hex::encode(&challenge_hash));
|
||||||
|
|
||||||
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
success!("Device info: {}", u2f_registration_data.device_info);
|
success!("Device info: {}", u2f_registration_data.device_info);
|
||||||
information!("Register challenge: {}", u2fv2_challenge_str);
|
information!("Register challenge: {}", u2fv2_challenge_str);
|
||||||
@@ -149,9 +149,6 @@ impl Command for CommandImpl {
|
|||||||
warning!("Cannot find attestation cert!");
|
warning!("Cannot find attestation cert!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if json_output {
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
|
||||||
}
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,9 +13,9 @@ use openssl::pkey::PKey;
|
|||||||
use openssl::sign::Verifier;
|
use openssl::sign::Verifier;
|
||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
|
||||||
use crate::digest;
|
use crate::{cmdutil, digestutil, util};
|
||||||
use crate::fido;
|
use crate::fidoutil;
|
||||||
use crate::fido::U2fV2Challenge;
|
use crate::fidoutil::U2fV2Challenge;
|
||||||
use crate::util::base64_encode;
|
use crate::util::base64_encode;
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
@@ -31,12 +31,11 @@ impl Command for CommandImpl {
|
|||||||
.arg(Arg::with_name("challenge").long("challenge").takes_value(true).help("Challenge HEX"))
|
.arg(Arg::with_name("challenge").long("challenge").takes_value(true).help("Challenge HEX"))
|
||||||
.arg(Arg::with_name("challenge-with-timestamp-prefix").long("challenge-with-timestamp-prefix").help("Challenge with timestamp prefix"))
|
.arg(Arg::with_name("challenge-with-timestamp-prefix").long("challenge-with-timestamp-prefix").help("Challenge with timestamp prefix"))
|
||||||
.arg(Arg::with_name("key-handle").short("k").long("key-handle").takes_value(true).multiple(true).help("Key handle"))
|
.arg(Arg::with_name("key-handle").short("k").long("key-handle").takes_value(true).multiple(true).help("Key handle"))
|
||||||
.arg(Arg::with_name("json").long("json").help("JSON output"))
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = sub_arg_matches.is_present("json");
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
if json_output { rust_util::util_msg::set_logger_std_out(false); }
|
|
||||||
|
|
||||||
let timeout_ms = match sub_arg_matches.value_of("timeout").unwrap().parse::<u32>() {
|
let timeout_ms = match sub_arg_matches.value_of("timeout").unwrap().parse::<u32>() {
|
||||||
Ok(t) => (t * 1000) as u64,
|
Ok(t) => (t * 1000) as u64,
|
||||||
@@ -72,10 +71,10 @@ impl Command for CommandImpl {
|
|||||||
let u2fv2_challenge = U2fV2Challenge::new_challenge(challenge_hex, app_id, challenge_with_timestamp_prefix)?;
|
let u2fv2_challenge = U2fV2Challenge::new_challenge(challenge_hex, app_id, challenge_with_timestamp_prefix)?;
|
||||||
let u2fv2_challenge_str = u2fv2_challenge.to_json();
|
let u2fv2_challenge_str = u2fv2_challenge.to_json();
|
||||||
|
|
||||||
let app_id_hash = digest::sha256(app_id);
|
let app_id_hash = digestutil::sha256(app_id);
|
||||||
let challenge_hash = digest::sha256(&u2fv2_challenge_str);
|
let challenge_hash = digestutil::sha256(&u2fv2_challenge_str);
|
||||||
|
|
||||||
let status_tx = fido::start_status_updater();
|
let status_tx = fidoutil::start_status_updater();
|
||||||
|
|
||||||
information!("App id: {}, Start sign...", app_id);
|
information!("App id: {}, Start sign...", app_id);
|
||||||
debugging!("Wait timeout: {} ms", timeout_ms);
|
debugging!("Wait timeout: {} ms", timeout_ms);
|
||||||
@@ -163,7 +162,7 @@ impl Command for CommandImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if json_output {
|
if json_output {
|
||||||
println!("{}", serde_json::to_string_pretty(&json).unwrap());
|
util::print_pretty_json(&json);
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
44
src/cmd_yubikey.rs
Normal file
44
src/cmd_yubikey.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
use crate::util;
|
||||||
|
use clap::{App, ArgMatches, SubCommand};
|
||||||
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
|
use rust_util::util_msg;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use yubikey::Context;
|
||||||
|
|
||||||
|
pub struct CommandImpl;
|
||||||
|
|
||||||
|
impl Command for CommandImpl {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"yubikey"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subcommand<'a>(&self) -> App<'a, 'a> {
|
||||||
|
SubCommand::with_name(self.name()).about("Yubikey subcommand")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
|
util_msg::set_logger_std_out(false);
|
||||||
|
|
||||||
|
let mut list = vec![];
|
||||||
|
let mut readers = Context::open()?;
|
||||||
|
for reader in readers.iter()? {
|
||||||
|
let yubikey = match reader.open() {
|
||||||
|
Ok(yk) => yk,
|
||||||
|
Err(e) => {
|
||||||
|
warning!("Error opening YubiKey: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut key = BTreeMap::new();
|
||||||
|
key.insert("serial", Value::Number(yubikey.serial().0.into()));
|
||||||
|
key.insert("version", yubikey.version().to_string().into());
|
||||||
|
key.insert("name", yubikey.name().into());
|
||||||
|
|
||||||
|
list.push(key);
|
||||||
|
}
|
||||||
|
util::print_pretty_json(&list);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
82
src/cmdutil.rs
Normal file
82
src/cmdutil.rs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
use clap::{Arg, ArgMatches};
|
||||||
|
use rust_util::util_msg;
|
||||||
|
|
||||||
|
pub fn build_slot_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("slot")
|
||||||
|
.short("s")
|
||||||
|
.long("slot")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_with_hmac_encrypt_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("with-hmac-encrypt").long("with-hmac-encrypt").help("With HMAC encrypt")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_with_pbe_encrypt_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("with-pbe-encrypt").long("with-pbe-encrypt").help("With PBE encryption")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_double_pin_check_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("double-pin-check").long("double-pin-check").help("Double PIN check")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_pbe_iteration_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("pbe-iteration").long("pbe-iteration").takes_value(true).help("PBE iteration, default 100000")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_serial_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("serial").long("serial").takes_value(true).help("Serial number")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_key_uri_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("key").long("key").required(true).takes_value(true).help("Key uri")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_pin_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_alg_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("alg").long("alg").takes_value(true).required(true).help("Algorithm, e.g. RS256, ES256, ES384")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_parameter_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("parameter").long("parameter").takes_value(true).required(true).help("Parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_epk_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("epk").long("epk").required(true).takes_value(true).help("E-Public key")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_message_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("message-base64").long("message-base64").takes_value(true).required(true).help("Message in base64")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn build_message_type_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("message-type").long("message-type").takes_value(true).help("Optional, message type, raw, sha256, sha384 or sha512")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_no_pin_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("no-pin").long("no-pin").help("No PIN")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_keychain_name_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("keychain-name")
|
||||||
|
.long("keychain-name")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Key chain name")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_json_arg() -> Arg<'static, 'static> {
|
||||||
|
Arg::with_name("json").long("json").help("JSON output")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_json_output(sub_arg_matches: &ArgMatches) -> bool {
|
||||||
|
let json_output = sub_arg_matches.is_present("json");
|
||||||
|
if json_output {
|
||||||
|
util_msg::set_logger_std_out(false);
|
||||||
|
}
|
||||||
|
json_output
|
||||||
|
}
|
||||||
@@ -1,6 +1,25 @@
|
|||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
use sha2::{Digest, Sha256, Sha384, Sha512};
|
use sha2::{Digest, Sha256, Sha384, Sha512};
|
||||||
|
|
||||||
|
pub enum DigestAlgorithm {
|
||||||
|
Sha256,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
Sha384,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DigestAlgorithm {
|
||||||
|
pub fn digest(&self, data: &[u8]) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
DigestAlgorithm::Sha256 => sha256_bytes(data),
|
||||||
|
DigestAlgorithm::Sha384 => sha384_bytes(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest_str(&self, s: &str) -> Vec<u8> {
|
||||||
|
self.digest(s.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sha256(input: &str) -> Vec<u8> {
|
pub fn sha256(input: &str) -> Vec<u8> {
|
||||||
sha256_bytes(input.as_bytes())
|
sha256_bytes(input.as_bytes())
|
||||||
}
|
}
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
macro_rules! piv_ecdh {
|
macro_rules! piv_ecdh {
|
||||||
($p_algo: tt, $public_key_pem_opt: expr, $sub_arg_matches: expr, $json: expr, $json_output: expr) => ({
|
($p_algo: tt, $public_key_pem_opt: expr, $sub_arg_matches: expr, $json: expr, $json_output: expr) => ({
|
||||||
use $p_algo::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
use $p_algo::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
|
||||||
use $p_algo::ecdh::EphemeralSecret;
|
use $p_algo::{EncodedPoint, PublicKey, ecdh::EphemeralSecret};
|
||||||
use $p_algo::{EncodedPoint, PublicKey};
|
|
||||||
let public_key;
|
let public_key;
|
||||||
if let Some(public_key_pem) = $public_key_pem_opt {
|
if let Some(public_key_pem) = $public_key_pem_opt {
|
||||||
public_key = opt_result!(public_key_pem.parse::<PublicKey>(), "Parse public key failed: {}");
|
public_key = opt_result!(public_key_pem.parse::<PublicKey>(), "Parse public key failed: {}");
|
||||||
@@ -35,4 +34,30 @@ macro_rules! piv_ecdh {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! parse_private_and_ecdh {
|
||||||
|
($algo: tt, $private_key_bytes: tt, $ephemeral_public_key_bytes: tt) => ({
|
||||||
|
use $algo::{SecretKey, PublicKey, ecdh::diffie_hellman, pkcs8::DecodePrivateKey};
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
let secret_key= SecretKey::from_pkcs8_der($private_key_bytes)?;
|
||||||
|
let public_key = opt_result!(PublicKey::from_public_key_der(
|
||||||
|
$ephemeral_public_key_bytes),"Parse ephemeral public key failed: {}");
|
||||||
|
|
||||||
|
let shared_secret = diffie_hellman(secret_key.to_nonzero_scalar(), public_key.as_affine());
|
||||||
|
Ok(shared_secret.raw_secret_bytes().to_vec())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p256_private_and_ecdh(private_key_bytes: &[u8], ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
parse_private_and_ecdh!(p256, private_key_bytes, ephemeral_public_key_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p384_private_and_ecdh(private_key_bytes: &[u8], ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
parse_private_and_ecdh!(p384, private_key_bytes, ephemeral_public_key_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p521_private_and_ecdh(private_key_bytes: &[u8], ephemeral_public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
parse_private_and_ecdh!(p521, private_key_bytes, ephemeral_public_key_bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
use rust_util::XResult;
|
||||||
pub(crate) use piv_ecdh;
|
pub(crate) use piv_ecdh;
|
||||||
166
src/ecdsautil.rs
166
src/ecdsautil.rs
@@ -5,6 +5,7 @@ use p256::NistP256;
|
|||||||
use p256::ecdsa::signature::hazmat::PrehashVerifier;
|
use p256::ecdsa::signature::hazmat::PrehashVerifier;
|
||||||
use p384::NistP384;
|
use p384::NistP384;
|
||||||
use p256::pkcs8::EncodePrivateKey;
|
use p256::pkcs8::EncodePrivateKey;
|
||||||
|
use p521::NistP521;
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
use spki::EncodePublicKey;
|
use spki::EncodePublicKey;
|
||||||
use crate::util::{base64_encode, try_decode};
|
use crate::util::{base64_encode, try_decode};
|
||||||
@@ -13,6 +14,12 @@ use crate::util::{base64_encode, try_decode};
|
|||||||
pub enum EcdsaAlgorithm {
|
pub enum EcdsaAlgorithm {
|
||||||
P256,
|
P256,
|
||||||
P384,
|
P384,
|
||||||
|
P521,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
|
pub enum EcdsaSignType {
|
||||||
|
Der, Rs,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_ecdsa_to_rs(signature_der: &[u8]) -> XResult<Vec<u8>> {
|
pub fn parse_ecdsa_to_rs(signature_der: &[u8]) -> XResult<Vec<u8>> {
|
||||||
@@ -30,22 +37,14 @@ pub fn parse_ecdsa_r_and_s(signature_der: &[u8]) -> XResult<(Vec<u8>, Vec<u8>)>
|
|||||||
match &seq[0].content {
|
match &seq[0].content {
|
||||||
BerObjectContent::Integer(r) => {
|
BerObjectContent::Integer(r) => {
|
||||||
debugging!("Signature r: {}", hex::encode(r));
|
debugging!("Signature r: {}", hex::encode(r));
|
||||||
if r.len() == ((256 / 8) + 1) || r.len() == ((384 / 8) + 1) {
|
vec_r = trim_ecdsa_point_coord(r);
|
||||||
vec_r = r[1..].to_vec();
|
|
||||||
} else {
|
|
||||||
vec_r = r.to_vec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => return simple_error!("Parse signature failed: [0]not integer"),
|
_ => return simple_error!("Parse signature failed: [0]not integer"),
|
||||||
}
|
}
|
||||||
match &seq[1].content {
|
match &seq[1].content {
|
||||||
BerObjectContent::Integer(s) => {
|
BerObjectContent::Integer(s) => {
|
||||||
debugging!("Signature s: {}", hex::encode(s));
|
debugging!("Signature s: {}", hex::encode(s));
|
||||||
if s.len() == ((256 / 8) + 1) || s.len() == ((384 / 8) + 1) {
|
vec_s = trim_ecdsa_point_coord(s);
|
||||||
vec_s = s[1..].to_vec();
|
|
||||||
} else {
|
|
||||||
vec_s = s.to_vec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => return simple_error!("Parse signature failed: [1]not integer"),
|
_ => return simple_error!("Parse signature failed: [1]not integer"),
|
||||||
}
|
}
|
||||||
@@ -55,26 +54,112 @@ pub fn parse_ecdsa_r_and_s(signature_der: &[u8]) -> XResult<(Vec<u8>, Vec<u8>)>
|
|||||||
Ok((vec_r, vec_s))
|
Ok((vec_r, vec_s))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_p256_keypair() -> XResult<(String, String, String)> {
|
const P256_LEN: usize = 32;
|
||||||
let secret_key = p256::SecretKey::random(&mut rand::thread_rng());
|
const P384_LEN: usize = 48;
|
||||||
let secret_key_der_base64 = base64_encode(secret_key.to_pkcs8_der()?.as_bytes());
|
const P521_LEN: usize = 66;
|
||||||
let secret_key_pem = secret_key.to_pkcs8_pem(LineEnding::LF)?.to_string();
|
|
||||||
let public_key_pem = secret_key.public_key().to_public_key_pem(LineEnding::LF)?;
|
fn trim_ecdsa_point_coord(p: &[u8]) -> Vec<u8> {
|
||||||
Ok((secret_key_der_base64, secret_key_pem, public_key_pem))
|
if p.len() == (P256_LEN + 1) || p.len() == (P384_LEN + 1) || p.len() == (P521_LEN + 1) {
|
||||||
|
p[1..].to_vec()
|
||||||
|
} else if p.len() == (P256_LEN - 1) || p.len() == (P384_LEN - 1) || p.len() == (P521_LEN - 1) {
|
||||||
|
let mut v = vec![];
|
||||||
|
v.push(0_u8);
|
||||||
|
v.extend_from_slice(p);
|
||||||
|
v
|
||||||
|
} else {
|
||||||
|
p.to_vec()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_p384_keypair() -> XResult<(String, String, String)> {
|
|
||||||
let secret_key = p384::SecretKey::random(&mut rand::thread_rng());
|
macro_rules! generate_inner_ecdsa_keypair {
|
||||||
|
($algo: tt) => ({
|
||||||
|
use $algo::SecretKey;
|
||||||
|
|
||||||
|
let secret_key = SecretKey::random(&mut rand::thread_rng());
|
||||||
let secret_key_der_base64 = base64_encode(secret_key.to_pkcs8_der()?.as_bytes());
|
let secret_key_der_base64 = base64_encode(secret_key.to_pkcs8_der()?.as_bytes());
|
||||||
let secret_key_pem = secret_key.to_pkcs8_pem(LineEnding::LF)?.to_string();
|
let secret_key_pem = secret_key.to_pkcs8_pem(LineEnding::LF)?.to_string();
|
||||||
let public_key_pem = secret_key.public_key().to_public_key_pem(LineEnding::LF)?;
|
let public_key_pem = secret_key.public_key().to_public_key_pem(LineEnding::LF)?;
|
||||||
Ok((secret_key_der_base64, secret_key_pem, public_key_pem))
|
let public_key_der = secret_key.public_key().to_public_key_der()?.to_vec();
|
||||||
|
let jwk_ec_key = secret_key.public_key().to_jwk().to_string();
|
||||||
|
Ok((secret_key_der_base64, secret_key_pem, public_key_pem, public_key_der, jwk_ec_key))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_ecdsa_keypair(algo: EcdsaAlgorithm) -> XResult<(String, String, String, Vec<u8>, String)> {
|
||||||
|
match algo {
|
||||||
|
EcdsaAlgorithm::P256 => generate_inner_ecdsa_keypair!(p256),
|
||||||
|
EcdsaAlgorithm::P384 => generate_inner_ecdsa_keypair!(p384),
|
||||||
|
EcdsaAlgorithm::P521 => generate_inner_ecdsa_keypair!(p521),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_ec_public_key_to_point(public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
match parse_p521_public_key_to_point(public_key_bytes) {
|
||||||
|
Ok(point) => Ok(point),
|
||||||
|
Err(_) => match parse_p384_public_key_to_point(public_key_bytes) {
|
||||||
|
Ok(point) => Ok(point),
|
||||||
|
Err(_) => parse_p256_public_key_to_point(public_key_bytes),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p256_public_key_to_point(public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
use p256::{PublicKey, elliptic_curve::sec1::ToEncodedPoint};
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
let public_key = PublicKey::from_public_key_der(public_key_bytes)?;
|
||||||
|
Ok(public_key.to_encoded_point(false).as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p384_public_key_to_point(public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
use p384::{PublicKey, elliptic_curve::sec1::ToEncodedPoint};
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
let public_key = PublicKey::from_public_key_der(public_key_bytes)?;
|
||||||
|
Ok(public_key.to_encoded_point(false).as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p521_public_key_to_point(public_key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
use p521::{PublicKey, elliptic_curve::sec1::ToEncodedPoint};
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
let public_key = PublicKey::from_public_key_der(public_key_bytes)?;
|
||||||
|
Ok(public_key.to_encoded_point(false).as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! parse_ecdsa_private_key_to_public_key {
|
||||||
|
($algo: tt, $parse_ecdsa_private_key: tt) => ({
|
||||||
|
use $algo::{SecretKey, pkcs8::DecodePrivateKey};
|
||||||
|
|
||||||
|
let secret_key = match SecretKey::from_pkcs8_pem($parse_ecdsa_private_key) {
|
||||||
|
Ok(secret_key) => secret_key,
|
||||||
|
Err(_) => match try_decode($parse_ecdsa_private_key) {
|
||||||
|
Ok(private_key_der) => match SecretKey::from_pkcs8_der(&private_key_der) {
|
||||||
|
Ok(secret_key) => secret_key,
|
||||||
|
Err(e) => return simple_error!("Invalid PKCS#8 private key {}, error: {}", $parse_ecdsa_private_key, e),
|
||||||
|
}
|
||||||
|
Err(_) => return simple_error!("Invalid PKCS#8 private key: {}", $parse_ecdsa_private_key),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let public_key_document = opt_result!(secret_key.public_key().to_public_key_der(), "Conver to public key failed: {}");
|
||||||
|
Ok(public_key_document.to_vec())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p256_private_key_to_public_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
|
||||||
|
parse_ecdsa_private_key_to_public_key!(p256, private_key_pkcs8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p384_private_key_to_public_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
|
||||||
|
parse_ecdsa_private_key_to_public_key!(p384, private_key_pkcs8)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_p521_private_key_to_public_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
|
||||||
|
parse_ecdsa_private_key_to_public_key!(p521, private_key_pkcs8)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
macro_rules! parse_ecdsa_private_key {
|
macro_rules! parse_ecdsa_private_key {
|
||||||
($algo: tt, $parse_ecdsa_private_key: tt) => ({
|
($algo: tt, $parse_ecdsa_private_key: tt) => ({
|
||||||
use $algo::pkcs8::DecodePrivateKey;
|
use $algo::{SecretKey, pkcs8::DecodePrivateKey};
|
||||||
use $algo::SecretKey;
|
|
||||||
|
|
||||||
let secret_key = match SecretKey::from_pkcs8_pem($parse_ecdsa_private_key) {
|
let secret_key = match SecretKey::from_pkcs8_pem($parse_ecdsa_private_key) {
|
||||||
Ok(secret_key) => secret_key,
|
Ok(secret_key) => secret_key,
|
||||||
@@ -98,26 +183,36 @@ pub fn parse_p384_private_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
|
|||||||
parse_ecdsa_private_key!(p384, private_key_pkcs8)
|
parse_ecdsa_private_key!(p384, private_key_pkcs8)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_p256_rs(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
|
pub fn parse_p521_private_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
|
||||||
use p256::ecdsa::{SigningKey, Signature};
|
parse_ecdsa_private_key!(p521, private_key_pkcs8)
|
||||||
use p256::ecdsa::signature::hazmat::PrehashSigner;
|
|
||||||
|
|
||||||
let signing_key = SigningKey::from_slice(private_key_d)?;
|
|
||||||
let signature: Signature = signing_key.sign_prehash(pre_hash)?;
|
|
||||||
|
|
||||||
Ok(signature.to_bytes().to_vec())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sign_p384_rs(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
|
|
||||||
use p384::ecdsa::{SigningKey, Signature};
|
|
||||||
use p384::ecdsa::signature::hazmat::PrehashSigner;
|
|
||||||
|
|
||||||
let signing_key = SigningKey::from_slice(private_key_d)?;
|
macro_rules! sign_ecdsa_rs_or_der {
|
||||||
let signature: Signature = signing_key.sign_prehash(pre_hash)?;
|
($algo: tt, $private_key_d: tt, $pre_hash: tt, $is_rs: tt) => ({
|
||||||
|
use $algo::ecdsa::{SigningKey, Signature, signature::hazmat::PrehashSigner};
|
||||||
|
|
||||||
|
let signing_key = SigningKey::from_slice($private_key_d)?;
|
||||||
|
let signature: Signature = signing_key.sign_prehash($pre_hash)?;
|
||||||
|
|
||||||
|
if $is_rs {
|
||||||
Ok(signature.to_bytes().to_vec())
|
Ok(signature.to_bytes().to_vec())
|
||||||
|
} else {
|
||||||
|
Ok(signature.to_der().as_bytes().to_vec())
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ecdsa_sign(algo: EcdsaAlgorithm, private_key_d: &[u8], pre_hash: &[u8], sign_type: EcdsaSignType) -> XResult<Vec<u8>> {
|
||||||
|
let is_rs = sign_type == EcdsaSignType::Rs;
|
||||||
|
match algo {
|
||||||
|
EcdsaAlgorithm::P256 => sign_ecdsa_rs_or_der!(p256, private_key_d, pre_hash, is_rs),
|
||||||
|
EcdsaAlgorithm::P384 => sign_ecdsa_rs_or_der!(p384, private_key_d, pre_hash, is_rs),
|
||||||
|
EcdsaAlgorithm::P521 => sign_ecdsa_rs_or_der!(p521, private_key_d, pre_hash, is_rs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
macro_rules! ecdsa_verify_signature {
|
macro_rules! ecdsa_verify_signature {
|
||||||
($algo: tt, $pk_point: tt, $prehash: tt, $signature: tt) => ({
|
($algo: tt, $pk_point: tt, $prehash: tt, $signature: tt) => ({
|
||||||
use ecdsa::Signature;
|
use ecdsa::Signature;
|
||||||
@@ -133,10 +228,11 @@ macro_rules! ecdsa_verify_signature {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ecdsaverify(algo: EcdsaAlgorithm, pk_point: &[u8], prehash: &[u8], signature: &[u8]) -> XResult<()> {
|
pub fn ecdsa_verify(algo: EcdsaAlgorithm, pk_point: &[u8], prehash: &[u8], signature: &[u8]) -> XResult<()> {
|
||||||
match algo {
|
match algo {
|
||||||
EcdsaAlgorithm::P256 => ecdsa_verify_signature!(NistP256, pk_point, prehash, signature),
|
EcdsaAlgorithm::P256 => ecdsa_verify_signature!(NistP256, pk_point, prehash, signature),
|
||||||
EcdsaAlgorithm::P384 => ecdsa_verify_signature!(NistP384, pk_point, prehash, signature),
|
EcdsaAlgorithm::P384 => ecdsa_verify_signature!(NistP384, pk_point, prehash, signature),
|
||||||
|
EcdsaAlgorithm::P521 => ecdsa_verify_signature!(NistP521, pk_point, prehash, signature),
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
66
src/ecutil.rs
Normal file
66
src/ecutil.rs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
use crate::util::base64_decode;
|
||||||
|
use p256::pkcs8::LineEnding;
|
||||||
|
use rust_util::XResult;
|
||||||
|
use spki::{DecodePublicKey, EncodePublicKey};
|
||||||
|
|
||||||
|
pub fn convert_ec_public_key_to_jwk(public_key: &str) -> XResult<String> {
|
||||||
|
if let Ok(jwk) = convert_ec_public_key_p256_to_jwk(public_key) {
|
||||||
|
return Ok(jwk);
|
||||||
|
}
|
||||||
|
if let Ok(jwk) = convert_ec_public_key_p384_to_jwk(public_key) {
|
||||||
|
return Ok(jwk);
|
||||||
|
}
|
||||||
|
simple_error!("Parse public key failed, MUST be pem or base64 encoded DER.")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_ec_public_key_p256_to_jwk(public_key: &str) -> XResult<String> {
|
||||||
|
let public_key_p256 = if public_key.contains("BEGIN PUBLIC KEY") {
|
||||||
|
debugging!("Try parse P256 public key PEM.");
|
||||||
|
p256::PublicKey::from_public_key_pem(public_key)?
|
||||||
|
} else {
|
||||||
|
debugging!("Try parse P256 public key DER.");
|
||||||
|
let der = base64_decode(public_key)?;
|
||||||
|
p256::PublicKey::from_public_key_der(&der)?
|
||||||
|
};
|
||||||
|
Ok(public_key_p256.to_jwk_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_ec_public_key_p384_to_jwk(public_key: &str) -> XResult<String> {
|
||||||
|
let public_key_p384 = if public_key.contains("BEGIN PUBLIC KEY") {
|
||||||
|
debugging!("Try parse P384 public key PEM.");
|
||||||
|
p384::PublicKey::from_public_key_pem(public_key)?
|
||||||
|
} else {
|
||||||
|
debugging!("Try parse P384 public key DER.");
|
||||||
|
let der = base64_decode(public_key)?;
|
||||||
|
p384::PublicKey::from_public_key_der(&der)?
|
||||||
|
};
|
||||||
|
Ok(public_key_p384.to_jwk_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_ec_jwk_to_public_key(jwk: &str) -> XResult<(String, Vec<u8>)> {
|
||||||
|
if let Ok(public_key) = convert_ec_jwk_p256_to_public_key(jwk) {
|
||||||
|
return Ok(public_key);
|
||||||
|
}
|
||||||
|
if let Ok(public_key) = convert_ec_jwk_p384_to_public_key(jwk) {
|
||||||
|
return Ok(public_key);
|
||||||
|
}
|
||||||
|
simple_error!("Parse JWK failed, MUST be P256 or P384.")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_ec_jwk_p256_to_public_key(jwk: &str) -> XResult<(String, Vec<u8>)> {
|
||||||
|
debugging!("Try parse P256 JWK.");
|
||||||
|
let public_key = p256::PublicKey::from_jwk_str(jwk)?;
|
||||||
|
Ok((
|
||||||
|
public_key.to_public_key_pem(LineEnding::LF)?,
|
||||||
|
public_key.to_public_key_der()?.to_vec(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn convert_ec_jwk_p384_to_public_key(jwk: &str) -> XResult<(String, Vec<u8>)> {
|
||||||
|
debugging!("Try parse P384 JWK.");
|
||||||
|
let public_key = p384::PublicKey::from_jwk_str(jwk)?;
|
||||||
|
Ok((
|
||||||
|
public_key.to_public_key_pem(LineEnding::LF)?,
|
||||||
|
public_key.to_public_key_der()?.to_vec(),
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -4,14 +4,12 @@ use std::thread;
|
|||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use authenticator::{RegisterResult, StatusUpdate};
|
use authenticator::{RegisterResult, StatusUpdate};
|
||||||
use base64::Engine;
|
|
||||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::pkiutil::bytes_to_pem;
|
use crate::pkiutil::bytes_to_pem;
|
||||||
use crate::util::base64_encode;
|
use crate::util::{base64_encode, base64_encode_url_safe_no_pad};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct U2FDeviceInfo {
|
pub struct U2FDeviceInfo {
|
||||||
@@ -103,7 +101,7 @@ impl U2fV2Challenge {
|
|||||||
None => U2fV2Challenge::new_random(app_id, with_time_stamp_prefix),
|
None => U2fV2Challenge::new_random(app_id, with_time_stamp_prefix),
|
||||||
Some(challenge_hex) => {
|
Some(challenge_hex) => {
|
||||||
let challenge_bytes = opt_result!(hex::decode(challenge_hex), "Decode challenge hex failed: {}");
|
let challenge_bytes = opt_result!(hex::decode(challenge_hex), "Decode challenge hex failed: {}");
|
||||||
let challenge = URL_SAFE_NO_PAD.encode(challenge_bytes);
|
let challenge = base64_encode_url_safe_no_pad(challenge_bytes);
|
||||||
U2fV2Challenge::new(challenge, app_id)
|
U2fV2Challenge::new(challenge, app_id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -122,7 +120,7 @@ impl U2fV2Challenge {
|
|||||||
rand_bytes[..8].clone_from_slice(×tamp_be_bytes[..8]);
|
rand_bytes[..8].clone_from_slice(×tamp_be_bytes[..8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let challenge = URL_SAFE_NO_PAD.encode(rand_bytes);
|
let challenge = base64_encode_url_safe_no_pad(rand_bytes);
|
||||||
Self::new(challenge, app_id)
|
Self::new(challenge, app_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8,8 +8,9 @@ use yubico_manager::config::{Config, Mode, Slot};
|
|||||||
use yubico_manager::hmacmode::HmacKey;
|
use yubico_manager::hmacmode::HmacKey;
|
||||||
use yubico_manager::sec::hmac_sha1;
|
use yubico_manager::sec::hmac_sha1;
|
||||||
use yubico_manager::Yubico;
|
use yubico_manager::Yubico;
|
||||||
use crate::digest::{copy_sha256, sha256_bytes};
|
use crate::digestutil::{copy_sha256, sha256_bytes};
|
||||||
use crate::util::{base64_decode, base64_encode};
|
use crate::util;
|
||||||
|
use crate::util::{base64_decode, base64_encode, base64_encode_url_safe_no_pad, base64_uri_decode};
|
||||||
|
|
||||||
const HMAC_ENC_PREFIX: &str = "hmac_enc:";
|
const HMAC_ENC_PREFIX: &str = "hmac_enc:";
|
||||||
|
|
||||||
@@ -19,7 +20,7 @@ pub fn hmac_encrypt_from_string(plaintext: &str) -> XResult<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn hmac_encrypt(plaintext: &[u8]) -> XResult<String> {
|
pub fn hmac_encrypt(plaintext: &[u8]) -> XResult<String> {
|
||||||
let hmac_nonce: [u8; 8] = random();
|
let hmac_nonce: [u8; 16] = random();
|
||||||
let aes_gcm_nonce: [u8; 16] = random();
|
let aes_gcm_nonce: [u8; 16] = random();
|
||||||
|
|
||||||
let hmac_key = compute_yubikey_hmac(&hmac_nonce)?;
|
let hmac_key = compute_yubikey_hmac(&hmac_nonce)?;
|
||||||
@@ -33,8 +34,8 @@ pub fn hmac_encrypt(plaintext: &[u8]) -> XResult<String> {
|
|||||||
|
|
||||||
Ok(format!("{}{}:{}:{}",
|
Ok(format!("{}{}:{}:{}",
|
||||||
HMAC_ENC_PREFIX,
|
HMAC_ENC_PREFIX,
|
||||||
hex::encode(hmac_nonce),
|
base64_encode_url_safe_no_pad(hmac_nonce),
|
||||||
hex::encode(aes_gcm_nonce),
|
base64_encode_url_safe_no_pad(aes_gcm_nonce),
|
||||||
base64_encode(&ciphertext)
|
base64_encode(&ciphertext)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -43,14 +44,6 @@ pub fn is_hmac_encrypted(ciphertext: &str) -> bool {
|
|||||||
ciphertext.starts_with(HMAC_ENC_PREFIX)
|
ciphertext.starts_with(HMAC_ENC_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
|
|
||||||
if is_hmac_encrypted(ciphertext) {
|
|
||||||
hmac_decrypt_to_string(ciphertext)
|
|
||||||
} else {
|
|
||||||
Ok(ciphertext.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
|
pub fn hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
|
||||||
let plaintext = hmac_decrypt(ciphertext)?;
|
let plaintext = hmac_decrypt(ciphertext)?;
|
||||||
Ok(String::from_utf8(plaintext)?)
|
Ok(String::from_utf8(plaintext)?)
|
||||||
@@ -61,8 +54,8 @@ pub fn hmac_decrypt(ciphertext: &str) -> XResult<Vec<u8>> {
|
|||||||
return simple_error!("Invalid ciphertext: {}", ciphertext);
|
return simple_error!("Invalid ciphertext: {}", ciphertext);
|
||||||
}
|
}
|
||||||
let parts = ciphertext.split(":").collect::<Vec<_>>();
|
let parts = ciphertext.split(":").collect::<Vec<_>>();
|
||||||
let hmac_nonce = hex::decode(parts[1])?;
|
let hmac_nonce = try_decode_hmac_val(parts[1])?;
|
||||||
let aes_gcm_nonce = hex::decode(parts[2])?;
|
let aes_gcm_nonce = try_decode_hmac_val(parts[2])?;
|
||||||
let ciphertext = base64_decode(parts[3])?;
|
let ciphertext = base64_decode(parts[3])?;
|
||||||
|
|
||||||
let hmac_key = compute_yubikey_hmac(&hmac_nonce)?;
|
let hmac_key = compute_yubikey_hmac(&hmac_nonce)?;
|
||||||
@@ -76,6 +69,16 @@ pub fn hmac_decrypt(ciphertext: &str) -> XResult<Vec<u8>> {
|
|||||||
Ok(plaintext)
|
Ok(plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_decode_hmac_val(s: &str) -> XResult<Vec<u8>> {
|
||||||
|
match hex::decode(s) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => match base64_uri_decode(s) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(_) => simple_error!("Try decode failed: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn compute_yubikey_hmac(challenge_bytes: &[u8]) -> XResult<Vec<u8>> {
|
pub fn compute_yubikey_hmac(challenge_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
let mut yubi = Yubico::new();
|
let mut yubi = Yubico::new();
|
||||||
let device = match yubi.find_yubikey() {
|
let device = match yubi.find_yubikey() {
|
||||||
@@ -128,10 +131,10 @@ pub fn output_hmac_result(sub_arg_matches: &ArgMatches, json_output: bool, chall
|
|||||||
let sha512_output = sub_arg_matches.is_present("sha512");
|
let sha512_output = sub_arg_matches.is_present("sha512");
|
||||||
|
|
||||||
let hex_string = hex::encode(result);
|
let hex_string = hex::encode(result);
|
||||||
let hex_sha1 = iff!(sha1_output, Some(crate::digest::sha1_bytes(result)), None);
|
let hex_sha1 = iff!(sha1_output, Some(crate::digestutil::sha1_bytes(result)), None);
|
||||||
let hex_sha256 = iff!(sha256_output, Some(crate::digest::sha256_bytes(result)), None);
|
let hex_sha256 = iff!(sha256_output, Some(crate::digestutil::sha256_bytes(result)), None);
|
||||||
let hex_sha384 = iff!(sha384_output, Some(crate::digest::sha384_bytes(result)), None);
|
let hex_sha384 = iff!(sha384_output, Some(crate::digestutil::sha384_bytes(result)), None);
|
||||||
let hex_sha512 = iff!(sha512_output, Some(crate::digest::sha512_bytes(result)), None);
|
let hex_sha512 = iff!(sha512_output, Some(crate::digestutil::sha512_bytes(result)), None);
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
@@ -142,7 +145,7 @@ pub fn output_hmac_result(sub_arg_matches: &ArgMatches, json_output: bool, chall
|
|||||||
hex_sha384.map(|hex_sha384| json.insert("response_sha384_hex", hex::encode(hex_sha384)));
|
hex_sha384.map(|hex_sha384| json.insert("response_sha384_hex", hex::encode(hex_sha384)));
|
||||||
hex_sha512.map(|hex_sha512| json.insert("response_sha512_hex", hex::encode(hex_sha512)));
|
hex_sha512.map(|hex_sha512| json.insert("response_sha512_hex", hex::encode(hex_sha512)));
|
||||||
|
|
||||||
println!("{}", serde_json::to_string_pretty(&json).expect("Convert to JSON failed!"));
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
success!("Challenge HEX: {}", hex::encode(challenge_bytes));
|
success!("Challenge HEX: {}", hex::encode(challenge_bytes));
|
||||||
success!("Response HEX: {}", hex_string);
|
success!("Response HEX: {}", hex_string);
|
||||||
|
|||||||
168
src/keychain.rs
Normal file
168
src/keychain.rs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
use rust_util::{util_file, XResult};
|
||||||
|
use security_framework::os::macos::keychain::{CreateOptions, SecKeychain};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const KEYCHAIN_KEY_PREFIX: &str = "keychain:";
|
||||||
|
const DEFAULT_SERVICE_NAME: &str = "card-cli";
|
||||||
|
|
||||||
|
pub struct KeychainKey {
|
||||||
|
pub keychain_name: String,
|
||||||
|
pub service_name: String,
|
||||||
|
pub key_name: String,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct KeychainKeyValue {
|
||||||
|
pub keychain_name: String,
|
||||||
|
pub pkcs8_base64: String,
|
||||||
|
pub secret_key_pem: String,
|
||||||
|
pub public_key_pem: String,
|
||||||
|
pub public_key_jwk: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_keychain_key_uri(name: &str) -> bool {
|
||||||
|
name.starts_with(KEYCHAIN_KEY_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeychainKey {
|
||||||
|
pub fn from_key_name_default(key_name: &str) -> Self {
|
||||||
|
Self::from("", DEFAULT_SERVICE_NAME, key_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from(keychain_name: &str, service_name: &str, key_name: &str) -> Self {
|
||||||
|
debugging!(
|
||||||
|
"Keychain key: {} - {} - {}",
|
||||||
|
keychain_name,
|
||||||
|
service_name,
|
||||||
|
key_name
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
keychain_name: keychain_name.to_string(),
|
||||||
|
service_name: service_name.to_string(),
|
||||||
|
key_name: key_name.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_key_uri(keychain_key: &str) -> XResult<Self> {
|
||||||
|
if !keychain_key.starts_with(KEYCHAIN_KEY_PREFIX) {
|
||||||
|
return simple_error!("Not a valid keychain key: {}", keychain_key);
|
||||||
|
}
|
||||||
|
//keychain:keychain_name:service_name:key_name
|
||||||
|
let keychain_key_parts = keychain_key.split(':').collect::<Vec<_>>();
|
||||||
|
if keychain_key_parts.len() != 4 {
|
||||||
|
return simple_error!("Not a valid keychain key: {}", keychain_key);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
keychain_name: keychain_key_parts[1].to_string(),
|
||||||
|
service_name: keychain_key_parts[2].to_string(),
|
||||||
|
key_name: keychain_key_parts[3].to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_key_uri(&self) -> String {
|
||||||
|
let mut s = String::new();
|
||||||
|
s.push_str(KEYCHAIN_KEY_PREFIX);
|
||||||
|
s.push_str(&self.keychain_name);
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&self.service_name);
|
||||||
|
s.push(':');
|
||||||
|
s.push_str(&self.key_name);
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_password(&self) -> XResult<Option<Vec<u8>>> {
|
||||||
|
let sec_keychain = self.get_keychain()?;
|
||||||
|
debugging!(
|
||||||
|
"Try find generic password: {}.{}",
|
||||||
|
&self.service_name,
|
||||||
|
&self.key_name
|
||||||
|
);
|
||||||
|
match sec_keychain.find_generic_password(&self.service_name, &self.key_name) {
|
||||||
|
Ok((item_password, _keychain_item)) => Ok(Some(item_password.as_ref().to_vec())),
|
||||||
|
Err(e) => {
|
||||||
|
debugging!("Get password: {} failed: {}", &self.to_key_uri(), e);
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_password(&self, password: &[u8]) -> XResult<()> {
|
||||||
|
let sec_keychain = self.get_keychain()?;
|
||||||
|
if sec_keychain
|
||||||
|
.find_generic_password(&self.service_name, &self.key_name)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return simple_error!("Password {}.{} exists", &self.service_name, &self.key_name);
|
||||||
|
}
|
||||||
|
opt_result!(
|
||||||
|
sec_keychain.set_generic_password(&self.service_name, &self.key_name, password),
|
||||||
|
"Set password {}.{} error: {}",
|
||||||
|
&self.service_name,
|
||||||
|
&self.key_name
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_keychain(&self) -> XResult<SecKeychain> {
|
||||||
|
if !self.keychain_name.is_empty() {
|
||||||
|
let keychain_file_name = format!("{}.keychain", &self.keychain_name);
|
||||||
|
debugging!("Open or create keychain: {}", &keychain_file_name);
|
||||||
|
let keychain_exists = check_keychain_exists(&keychain_file_name);
|
||||||
|
if keychain_exists {
|
||||||
|
Ok(opt_result!(
|
||||||
|
SecKeychain::open(&keychain_file_name),
|
||||||
|
"Open keychain: {}, failed: {}",
|
||||||
|
&keychain_file_name
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
match CreateOptions::new()
|
||||||
|
.prompt_user(true)
|
||||||
|
.create(&keychain_file_name)
|
||||||
|
{
|
||||||
|
Ok(sec_keychain) => Ok(sec_keychain),
|
||||||
|
Err(ce) => match SecKeychain::open(&keychain_file_name) {
|
||||||
|
Ok(sec_keychain) => Ok(sec_keychain),
|
||||||
|
Err(oe) => simple_error!(
|
||||||
|
"Create keychain: {}, failed: {}, open also failed: {}",
|
||||||
|
&self.keychain_name,
|
||||||
|
ce,
|
||||||
|
oe
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(opt_result!(
|
||||||
|
SecKeychain::default(),
|
||||||
|
"Get keychain failed: {}"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_keychain_exists(keychain_file_name: &str) -> bool {
|
||||||
|
let keychain_path = PathBuf::from(util_file::resolve_file_path("~/Library/Keychains/"));
|
||||||
|
match keychain_path.read_dir() {
|
||||||
|
Ok(read_dir) => {
|
||||||
|
for dir in read_dir {
|
||||||
|
match dir {
|
||||||
|
Ok(dir) => {
|
||||||
|
if let Some(file_name) = dir.file_name().to_str() {
|
||||||
|
if file_name.starts_with(keychain_file_name) {
|
||||||
|
debugging!("Found key chain file: {:?}", dir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debugging!("Read path sub dir: {:?} failed: {}", keychain_path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debugging!("Read path: {:?} failed: {}", keychain_path, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
395
src/keyutil.rs
395
src/keyutil.rs
@@ -1,29 +1,233 @@
|
|||||||
|
use crate::pivutil::{FromStr, ToStr};
|
||||||
|
use jwt::AlgorithmType;
|
||||||
|
use percent_encoding::NON_ALPHANUMERIC;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
|
use yubikey::piv::{AlgorithmId, SlotId};
|
||||||
|
|
||||||
// reference: https://git.hatter.ink/hatter/card-cli/issues/6
|
// reference: https://git.hatter.ink/hatter/card-cli/issues/6
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum KeyUri {
|
pub enum KeyUri {
|
||||||
SecureEnclaveKey(SecureEnclaveKey),
|
SecureEnclave(SecureEnclaveKey),
|
||||||
|
YubikeyPiv(YubikeyPivKey),
|
||||||
|
YubikeyHmacEncSoft(YubikeyHmacEncSoftKey),
|
||||||
|
ExternalCommand(ExternalCommandKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[derive(Debug, PartialEq, Eq)]
|
impl KeyUri {
|
||||||
// pub enum KeyModule {
|
pub fn as_secure_enclave_key(&self) -> XResult<&SecureEnclaveKey> {
|
||||||
// SecureEnclave,
|
match self {
|
||||||
// OpenPgpCard,
|
KeyUri::SecureEnclave(key) => Ok(key),
|
||||||
// PersonalIdentityVerification,
|
_ => simple_error!("Not a secure enclave key."),
|
||||||
// }
|
}
|
||||||
//
|
}
|
||||||
// impl KeyModule {
|
|
||||||
// pub fn from(module: &str) -> Option<Self> {
|
pub fn get_preferred_algorithm_type(&self) -> AlgorithmType {
|
||||||
// match module {
|
let algorithm_id = match &self {
|
||||||
// "se" => Some(Self::SecureEnclave),
|
KeyUri::SecureEnclave(_) => return AlgorithmType::Es256,
|
||||||
// "pgp" => Some(Self::OpenPgpCard),
|
KeyUri::YubikeyPiv(key) => key.algorithm,
|
||||||
// "piv" => Some(Self::PersonalIdentityVerification),
|
KeyUri::YubikeyHmacEncSoft(key) => key.algorithm,
|
||||||
// _ => None,
|
KeyUri::ExternalCommand(key) => key.algorithm,
|
||||||
// }
|
};
|
||||||
// }
|
match algorithm_id {
|
||||||
// }
|
KeyAlgorithmId::Rsa1024
|
||||||
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => AlgorithmType::Rs256,
|
||||||
|
KeyAlgorithmId::EccP256 => AlgorithmType::Es256,
|
||||||
|
KeyAlgorithmId::EccP384 => AlgorithmType::Es384,
|
||||||
|
KeyAlgorithmId::EccP521 => AlgorithmType::Es512,
|
||||||
|
// ML-KEM not supports JWS
|
||||||
|
KeyAlgorithmId::MlKem512
|
||||||
|
| KeyAlgorithmId::MlKem768
|
||||||
|
| KeyAlgorithmId::MlKem1024 => AlgorithmType::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::to_string_trait_impl)]
|
||||||
|
impl ToString for KeyUri {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
let mut key_uri = String::with_capacity(64);
|
||||||
|
key_uri.push_str("key://");
|
||||||
|
match self {
|
||||||
|
// key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)
|
||||||
|
// key://hatter-mac-pro:se/p256:key_agreement:BASE64(dataRepresentation)
|
||||||
|
KeyUri::SecureEnclave(key) => {
|
||||||
|
key_uri.push_str(&key.host);
|
||||||
|
key_uri.push_str(":se/p256:");
|
||||||
|
key_uri.push_str(&key.usage.to_string());
|
||||||
|
key_uri.push(':');
|
||||||
|
key_uri.push_str(&key.private_key);
|
||||||
|
}
|
||||||
|
// key://yubikey-5n:piv/p256::9a
|
||||||
|
KeyUri::YubikeyPiv(key) => {
|
||||||
|
key_uri.push_str(&key.key_name);
|
||||||
|
key_uri.push_str(":piv/");
|
||||||
|
key_uri.push_str(key.algorithm.to_str());
|
||||||
|
key_uri.push_str("::");
|
||||||
|
key_uri.push_str(key.slot.to_str());
|
||||||
|
}
|
||||||
|
// key://-:soft/p256::hmac_enc:...
|
||||||
|
KeyUri::YubikeyHmacEncSoft(key) => {
|
||||||
|
key_uri.push_str(&key.key_name);
|
||||||
|
key_uri.push_str(":soft/");
|
||||||
|
key_uri.push_str(key.algorithm.to_str());
|
||||||
|
key_uri.push_str("::");
|
||||||
|
key_uri.push_str(key.hmac_enc_private_key.as_str());
|
||||||
|
}
|
||||||
|
// key://external-command-file-name:external_command/p256::parameter
|
||||||
|
KeyUri::ExternalCommand(key) => {
|
||||||
|
let encoded_external_command =
|
||||||
|
percent_encoding::utf8_percent_encode(&key.external_command, NON_ALPHANUMERIC)
|
||||||
|
.to_string();
|
||||||
|
key_uri.push_str(&encoded_external_command);
|
||||||
|
key_uri.push_str(":external_command/");
|
||||||
|
key_uri.push_str(key.algorithm.to_str());
|
||||||
|
key_uri.push_str("::");
|
||||||
|
key_uri.push_str(&key.parameter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum KeyAlgorithmId {
|
||||||
|
Rsa1024,
|
||||||
|
Rsa2048,
|
||||||
|
Rsa3072,
|
||||||
|
Rsa4096,
|
||||||
|
EccP256,
|
||||||
|
EccP384,
|
||||||
|
EccP521,
|
||||||
|
MlKem512,
|
||||||
|
MlKem768,
|
||||||
|
MlKem1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyAlgorithmId {
|
||||||
|
pub fn from_algorithm_id(algorithm_id: AlgorithmId) -> Self {
|
||||||
|
match algorithm_id {
|
||||||
|
AlgorithmId::Rsa1024 => Self::Rsa1024,
|
||||||
|
AlgorithmId::Rsa2048 => Self::Rsa2048,
|
||||||
|
AlgorithmId::EccP256 => Self::EccP256,
|
||||||
|
AlgorithmId::EccP384 => Self::EccP384,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_algorithm_id(self) -> Option<AlgorithmId> {
|
||||||
|
match self {
|
||||||
|
KeyAlgorithmId::Rsa1024 => Some(AlgorithmId::Rsa1024),
|
||||||
|
KeyAlgorithmId::Rsa2048 => Some(AlgorithmId::Rsa2048),
|
||||||
|
KeyAlgorithmId::Rsa3072 => None,
|
||||||
|
KeyAlgorithmId::Rsa4096 => None,
|
||||||
|
KeyAlgorithmId::EccP256 => Some(AlgorithmId::EccP256),
|
||||||
|
KeyAlgorithmId::EccP384 => Some(AlgorithmId::EccP384),
|
||||||
|
KeyAlgorithmId::EccP521 => None,
|
||||||
|
KeyAlgorithmId::MlKem512 => None,
|
||||||
|
KeyAlgorithmId::MlKem768 => None,
|
||||||
|
KeyAlgorithmId::MlKem1024 => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_rsa(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
KeyAlgorithmId::Rsa1024
|
||||||
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => true,
|
||||||
|
KeyAlgorithmId::EccP256
|
||||||
|
| KeyAlgorithmId::EccP384
|
||||||
|
| KeyAlgorithmId::EccP521
|
||||||
|
| KeyAlgorithmId::MlKem512
|
||||||
|
| KeyAlgorithmId::MlKem768
|
||||||
|
| KeyAlgorithmId::MlKem1024 => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_ecc(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
KeyAlgorithmId::EccP256 | KeyAlgorithmId::EccP384 | KeyAlgorithmId::EccP521 => true,
|
||||||
|
KeyAlgorithmId::Rsa1024
|
||||||
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096
|
||||||
|
| KeyAlgorithmId::MlKem512
|
||||||
|
| KeyAlgorithmId::MlKem768
|
||||||
|
| KeyAlgorithmId::MlKem1024 => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn is_mlkem(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
| KeyAlgorithmId::MlKem512
|
||||||
|
| KeyAlgorithmId::MlKem768
|
||||||
|
| KeyAlgorithmId::MlKem1024 => true,
|
||||||
|
KeyAlgorithmId::EccP256
|
||||||
|
| KeyAlgorithmId::EccP384
|
||||||
|
| KeyAlgorithmId::EccP521
|
||||||
|
| KeyAlgorithmId::Rsa1024
|
||||||
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_jwa_name(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
KeyAlgorithmId::Rsa1024
|
||||||
|
| KeyAlgorithmId::Rsa2048
|
||||||
|
| KeyAlgorithmId::Rsa3072
|
||||||
|
| KeyAlgorithmId::Rsa4096 => "RS256",
|
||||||
|
KeyAlgorithmId::EccP256 => "ES256,",
|
||||||
|
KeyAlgorithmId::EccP384 => "ES384",
|
||||||
|
KeyAlgorithmId::EccP521 => "ES512",
|
||||||
|
KeyAlgorithmId::MlKem512
|
||||||
|
| KeyAlgorithmId::MlKem768
|
||||||
|
| KeyAlgorithmId::MlKem1024 => "__UNKNOWN__",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for KeyAlgorithmId {
|
||||||
|
fn from_str(s: &str) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match s {
|
||||||
|
"rsa1024" => Some(KeyAlgorithmId::Rsa1024),
|
||||||
|
"rsa2048" => Some(KeyAlgorithmId::Rsa2048),
|
||||||
|
"rsa3072" => Some(KeyAlgorithmId::Rsa3072),
|
||||||
|
"rsa4096" => Some(KeyAlgorithmId::Rsa4096),
|
||||||
|
"p256" => Some(KeyAlgorithmId::EccP256),
|
||||||
|
"p384" => Some(KeyAlgorithmId::EccP384),
|
||||||
|
"p521" => Some(KeyAlgorithmId::EccP521),
|
||||||
|
"mlkem512" => Some(KeyAlgorithmId::MlKem512),
|
||||||
|
"mlkem768" => Some(KeyAlgorithmId::MlKem768),
|
||||||
|
"mlkem1024" => Some(KeyAlgorithmId::MlKem1024),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStr for KeyAlgorithmId {
|
||||||
|
fn to_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
KeyAlgorithmId::Rsa1024 => "rsa1024",
|
||||||
|
KeyAlgorithmId::Rsa2048 => "rsa2048",
|
||||||
|
KeyAlgorithmId::Rsa3072 => "rsa3072",
|
||||||
|
KeyAlgorithmId::Rsa4096 => "rsa4096",
|
||||||
|
KeyAlgorithmId::EccP256 => "p256",
|
||||||
|
KeyAlgorithmId::EccP384 => "p384",
|
||||||
|
KeyAlgorithmId::EccP521 => "p521",
|
||||||
|
KeyAlgorithmId::MlKem512 => "mlkem512",
|
||||||
|
KeyAlgorithmId::MlKem768 => "mlkem768",
|
||||||
|
KeyAlgorithmId::MlKem1024 => "mlkem1024",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum KeyUsage {
|
pub enum KeyUsage {
|
||||||
@@ -43,7 +247,18 @@ impl KeyUsage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(clippy::to_string_trait_impl)]
|
||||||
|
impl ToString for KeyUsage {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
KeyUsage::Any => "*",
|
||||||
|
KeyUsage::Singing => "signing",
|
||||||
|
KeyUsage::KeyAgreement => "key_agreement",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SecureEnclaveKey {
|
pub struct SecureEnclaveKey {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
@@ -51,21 +266,41 @@ pub struct SecureEnclaveKey {
|
|||||||
pub private_key: String,
|
pub private_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct YubikeyPivKey {
|
||||||
|
pub key_name: String,
|
||||||
|
pub algorithm: KeyAlgorithmId,
|
||||||
|
pub slot: SlotId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct YubikeyHmacEncSoftKey {
|
||||||
|
pub key_name: String,
|
||||||
|
pub algorithm: KeyAlgorithmId,
|
||||||
|
pub hmac_enc_private_key: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ExternalCommandKey {
|
||||||
|
pub external_command: String,
|
||||||
|
pub algorithm: KeyAlgorithmId,
|
||||||
|
pub parameter: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
|
pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
|
||||||
let regex = Regex::new(r##"^key://([a-zA-Z\-\._]*):(\w+)/(\w+):(\w+)?:(.*)$"##).unwrap();
|
let regex = Regex::new(r##"^key://([0-9a-zA-Z\-\._]*):(\w+)/(\w+):((?:\w+)?):(.*)$"##).unwrap();
|
||||||
let captures = match regex.captures(key_uri) {
|
let captures = match regex.captures(key_uri) {
|
||||||
None => return simple_error!("Invalid key uri: {}", key_uri),
|
None => return simple_error!("Invalid key uri: {}", key_uri),
|
||||||
Some(captures) => captures,
|
Some(captures) => captures,
|
||||||
};
|
};
|
||||||
let host = captures.get(1).unwrap().as_str();
|
let host_or_name = captures.get(1).unwrap().as_str();
|
||||||
let module = captures.get(2).unwrap().as_str();
|
let module = captures.get(2).unwrap().as_str();
|
||||||
let algorithm = captures.get(3).unwrap().as_str();
|
let algorithm = captures.get(3).unwrap().as_str();
|
||||||
let usage = captures.get(4).unwrap().as_str();
|
let usage = captures.get(4).unwrap().as_str();
|
||||||
let left_part = captures.get(5).unwrap().as_str();
|
let left_part = captures.get(5).unwrap().as_str();
|
||||||
|
|
||||||
if "se" != module {
|
match module {
|
||||||
return simple_error!("Key uri's module must be se.");
|
"se" => {
|
||||||
}
|
|
||||||
if "p256" != algorithm {
|
if "p256" != algorithm {
|
||||||
return simple_error!("Key uri's algorithm must be p256.");
|
return simple_error!("Key uri's algorithm must be p256.");
|
||||||
}
|
}
|
||||||
@@ -75,40 +310,136 @@ pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
|
|||||||
}
|
}
|
||||||
Some(key_usage) => key_usage,
|
Some(key_usage) => key_usage,
|
||||||
};
|
};
|
||||||
|
let parsed_key_uri = KeyUri::SecureEnclave(SecureEnclaveKey {
|
||||||
let parsed_key_uri = KeyUri::SecureEnclaveKey(SecureEnclaveKey {
|
host: host_or_name.to_string(),
|
||||||
host: host.to_string(),
|
|
||||||
usage: key_usage,
|
usage: key_usage,
|
||||||
private_key: left_part.to_string(),
|
private_key: left_part.to_string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
||||||
Ok(parsed_key_uri)
|
Ok(parsed_key_uri)
|
||||||
|
}
|
||||||
|
"piv" => {
|
||||||
|
if !usage.is_empty() {
|
||||||
|
return simple_error!("Key uri's usage must be empty.");
|
||||||
|
}
|
||||||
|
let algorithm = opt_value_result!(
|
||||||
|
KeyAlgorithmId::from_str(algorithm),
|
||||||
|
"Invalid algorithm id: {}",
|
||||||
|
algorithm
|
||||||
|
);
|
||||||
|
let slot = opt_value_result!(
|
||||||
|
SlotId::from_str(left_part),
|
||||||
|
"Invalid slot id: {}",
|
||||||
|
left_part
|
||||||
|
);
|
||||||
|
let parsed_key_uri = KeyUri::YubikeyPiv(YubikeyPivKey {
|
||||||
|
key_name: host_or_name.to_string(),
|
||||||
|
algorithm,
|
||||||
|
slot,
|
||||||
|
});
|
||||||
|
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
||||||
|
Ok(parsed_key_uri)
|
||||||
|
}
|
||||||
|
"soft" => {
|
||||||
|
if !usage.is_empty() {
|
||||||
|
return simple_error!("Key uri's usage must be empty.");
|
||||||
|
}
|
||||||
|
let algorithm = opt_value_result!(
|
||||||
|
KeyAlgorithmId::from_str(algorithm),
|
||||||
|
"Invalid algorithm id: {}",
|
||||||
|
algorithm
|
||||||
|
);
|
||||||
|
let hmac_enc_private_key = left_part.to_string();
|
||||||
|
let parsed_key_uri = KeyUri::YubikeyHmacEncSoft(YubikeyHmacEncSoftKey {
|
||||||
|
key_name: host_or_name.to_string(),
|
||||||
|
algorithm,
|
||||||
|
hmac_enc_private_key,
|
||||||
|
});
|
||||||
|
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
||||||
|
Ok(parsed_key_uri)
|
||||||
|
}
|
||||||
|
"external_command" => {
|
||||||
|
if !usage.is_empty() {
|
||||||
|
return simple_error!("Key uri's usage must be empty.");
|
||||||
|
}
|
||||||
|
let external_command = opt_result!(
|
||||||
|
percent_encoding::percent_decode_str(host_or_name).decode_utf8(),
|
||||||
|
"Decode external command failed: {}"
|
||||||
|
);
|
||||||
|
let algorithm = opt_value_result!(
|
||||||
|
KeyAlgorithmId::from_str(algorithm),
|
||||||
|
"Invalid algorithm id: {}",
|
||||||
|
algorithm
|
||||||
|
);
|
||||||
|
let parameter = left_part.to_string();
|
||||||
|
let parsed_key_uri = KeyUri::ExternalCommand(ExternalCommandKey {
|
||||||
|
external_command: external_command.to_string(),
|
||||||
|
algorithm,
|
||||||
|
parameter,
|
||||||
|
});
|
||||||
|
debugging!("Parsed key uri: {:?}", parsed_key_uri);
|
||||||
|
Ok(parsed_key_uri)
|
||||||
|
}
|
||||||
|
_ => simple_error!("Key uri's module must be se."),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_key_uri_01() {
|
fn test_parse_key_uri_01() {
|
||||||
let se_key_uri =
|
let se_key_uri =
|
||||||
parse_key_uri("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)").unwrap();
|
parse_key_uri("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
"key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)",
|
||||||
|
se_key_uri.to_string()
|
||||||
|
);
|
||||||
match se_key_uri {
|
match se_key_uri {
|
||||||
KeyUri::SecureEnclaveKey(se_key_uri) => {
|
KeyUri::SecureEnclave(se_key_uri) => {
|
||||||
assert_eq!("hatter-mac-pro", se_key_uri.host);
|
assert_eq!("hatter-mac-pro", se_key_uri.host);
|
||||||
assert_eq!(KeyUsage::Singing, se_key_uri.usage);
|
assert_eq!(KeyUsage::Singing, se_key_uri.usage);
|
||||||
assert_eq!("BASE64(dataRepresentation)", se_key_uri.private_key);
|
assert_eq!("BASE64(dataRepresentation)", se_key_uri.private_key);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Key uri not parsed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_key_uri_02() {
|
fn test_parse_key_uri_02() {
|
||||||
let se_key_uri =
|
let se_key_uri =
|
||||||
parse_key_uri("key://hatter-mac-pro:se/p256:key_agreement:BASE64(dataRepresentation)")
|
parse_key_uri("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
"key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)",
|
||||||
|
se_key_uri.to_string()
|
||||||
|
);
|
||||||
match se_key_uri {
|
match se_key_uri {
|
||||||
KeyUri::SecureEnclaveKey(se_key_uri) => {
|
KeyUri::SecureEnclave(se_key_uri) => {
|
||||||
assert_eq!("hatter-mac-pro", se_key_uri.host);
|
assert_eq!("hatter-mac-m1", se_key_uri.host);
|
||||||
assert_eq!(KeyUsage::KeyAgreement, se_key_uri.usage);
|
assert_eq!(KeyUsage::KeyAgreement, se_key_uri.usage);
|
||||||
assert_eq!("BASE64(dataRepresentation)", se_key_uri.private_key);
|
assert_eq!("BASE64(dataRepresentation)", se_key_uri.private_key);
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Key uri not parsed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_key_uri_03() {
|
||||||
|
let se_key_uri = parse_key_uri("key://yubikey-5n:piv/p256::9a").unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
"key://yubikey-5n:piv/p256::authentication",
|
||||||
|
se_key_uri.to_string()
|
||||||
|
);
|
||||||
|
match se_key_uri {
|
||||||
|
KeyUri::YubikeyPiv(piv_key_uri) => {
|
||||||
|
assert_eq!("yubikey-5n", piv_key_uri.key_name);
|
||||||
|
assert_eq!(KeyAlgorithmId::EccP256, piv_key_uri.algorithm);
|
||||||
|
assert_eq!(SlotId::Authentication, piv_key_uri.slot);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
panic!("Key uri not parsed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
177
src/main.rs
177
src/main.rs
@@ -6,58 +6,73 @@ use rust_util::util_clap::{Command, CommandError};
|
|||||||
|
|
||||||
mod argsutil;
|
mod argsutil;
|
||||||
mod cmd_chall;
|
mod cmd_chall;
|
||||||
mod cmd_challconfig;
|
mod cmd_chall_config;
|
||||||
mod cmd_ecverify;
|
mod cmd_convert_jwk_to_pem;
|
||||||
|
mod cmd_convert_pem_to_jwk;
|
||||||
|
mod cmd_ec_verify;
|
||||||
|
mod cmd_external_ecdh;
|
||||||
|
mod cmd_external_public_key;
|
||||||
|
mod cmd_external_sign;
|
||||||
|
mod cmd_external_spec;
|
||||||
|
mod cmd_file_sign;
|
||||||
|
mod cmd_file_verify;
|
||||||
|
mod cmd_hmac_decrypt;
|
||||||
|
mod cmd_hmac_encrypt;
|
||||||
mod cmd_hmac_sha1;
|
mod cmd_hmac_sha1;
|
||||||
mod cmd_hmacencrypt;
|
mod cmd_keypair_generate;
|
||||||
mod cmd_hmacdecrypt;
|
mod cmd_keypair_keychain_export;
|
||||||
|
mod cmd_keypair_keychain_import;
|
||||||
mod cmd_list;
|
mod cmd_list;
|
||||||
|
mod cmd_parseecdsasignature;
|
||||||
#[cfg(feature = "with-sequoia-openpgp")]
|
#[cfg(feature = "with-sequoia-openpgp")]
|
||||||
mod cmd_pgp;
|
mod cmd_pgp;
|
||||||
mod cmd_pgpageaddress;
|
mod cmd_pgp_age_address;
|
||||||
mod cmd_pgpcardadmin;
|
mod cmd_pgp_card_admin;
|
||||||
mod cmd_pgpcarddecrypt;
|
mod cmd_pgp_card_decrypt;
|
||||||
mod cmd_pgpcardlist;
|
mod cmd_pgp_card_list;
|
||||||
#[cfg(feature = "with-sequoia-openpgp")]
|
#[cfg(feature = "with-sequoia-openpgp")]
|
||||||
mod cmd_pgpcardmake;
|
mod cmd_pgp_card_make;
|
||||||
mod cmd_pgpcardsign;
|
mod cmd_pgp_card_sign;
|
||||||
mod cmd_piv;
|
mod cmd_piv;
|
||||||
mod cmd_pivdecrypt;
|
mod cmd_piv_decrypt;
|
||||||
mod cmd_pivecdh;
|
mod cmd_piv_ecdh;
|
||||||
mod cmd_pivecsign;
|
mod cmd_piv_ecsign;
|
||||||
mod cmd_pivgenerate;
|
mod cmd_piv_generate;
|
||||||
mod cmd_pivmeta;
|
mod cmd_piv_meta;
|
||||||
mod cmd_pivrsasign;
|
mod cmd_piv_rsasign;
|
||||||
mod cmd_pivsummary;
|
mod cmd_piv_summary;
|
||||||
mod cmd_pivverify;
|
mod cmd_piv_verify;
|
||||||
mod cmd_rsadecrypt;
|
mod cmd_rsa_decrypt;
|
||||||
mod cmd_rsaencrypt;
|
mod cmd_rsa_encrypt;
|
||||||
mod cmd_rsaverify;
|
mod cmd_rsa_verify;
|
||||||
mod cmd_se;
|
mod cmd_se;
|
||||||
mod cmd_se_ecdh;
|
mod cmd_se_ecdh;
|
||||||
mod cmd_se_ecsign;
|
mod cmd_se_ecsign;
|
||||||
mod cmd_se_generate;
|
mod cmd_se_generate;
|
||||||
mod cmd_se_recover;
|
mod cmd_se_recover;
|
||||||
mod cmd_signfile;
|
mod cmd_sign_jwt;
|
||||||
mod cmd_signjwt;
|
mod cmd_sign_jwt_piv;
|
||||||
mod cmd_signjwtsoft;
|
mod cmd_sign_jwt_se;
|
||||||
mod cmd_sshagent;
|
mod cmd_sign_jwt_soft;
|
||||||
mod cmd_sshparse;
|
mod cmd_ssh_agent;
|
||||||
mod cmd_sshparsesign;
|
mod cmd_ssh_agent_gpg;
|
||||||
mod cmd_sshpivcert;
|
mod cmd_ssh_parse;
|
||||||
mod cmd_sshpivsign;
|
mod cmd_ssh_parse_sign;
|
||||||
mod cmd_sshpubkey;
|
mod cmd_ssh_piv_cert;
|
||||||
mod cmd_u2fregister;
|
mod cmd_ssh_piv_sign;
|
||||||
mod cmd_u2fsign;
|
mod cmd_ssh_pub_key;
|
||||||
mod cmd_verifyfile;
|
mod cmd_u2f_register;
|
||||||
mod cmd_parseecdsasignature;
|
mod cmd_u2f_sign;
|
||||||
mod cmd_generatekeypair;
|
mod cmdutil;
|
||||||
mod digest;
|
mod digestutil;
|
||||||
mod ecdhutil;
|
mod ecdhutil;
|
||||||
mod ecdsautil;
|
mod ecdsautil;
|
||||||
mod fido;
|
mod ecutil;
|
||||||
|
mod fidoutil;
|
||||||
mod hmacutil;
|
mod hmacutil;
|
||||||
|
mod keychain;
|
||||||
mod keyutil;
|
mod keyutil;
|
||||||
|
mod pbeutil;
|
||||||
mod pgpcardutil;
|
mod pgpcardutil;
|
||||||
mod pinutil;
|
mod pinutil;
|
||||||
mod pivutil;
|
mod pivutil;
|
||||||
@@ -67,6 +82,14 @@ mod seutil;
|
|||||||
mod signfile;
|
mod signfile;
|
||||||
mod sshutil;
|
mod sshutil;
|
||||||
mod util;
|
mod util;
|
||||||
|
mod yubikeyutil;
|
||||||
|
mod cmd_yubikey;
|
||||||
|
mod mlkemutil;
|
||||||
|
|
||||||
|
use zeroizing_alloc::ZeroAlloc;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: ZeroAlloc<std::alloc::System> = ZeroAlloc(std::alloc::System);
|
||||||
|
|
||||||
pub struct DefaultCommandImpl;
|
pub struct DefaultCommandImpl;
|
||||||
|
|
||||||
@@ -97,50 +120,62 @@ fn inner_main() -> CommandError {
|
|||||||
Box::new(cmd_list::CommandImpl),
|
Box::new(cmd_list::CommandImpl),
|
||||||
Box::new(cmd_chall::CommandImpl),
|
Box::new(cmd_chall::CommandImpl),
|
||||||
Box::new(cmd_hmac_sha1::CommandImpl),
|
Box::new(cmd_hmac_sha1::CommandImpl),
|
||||||
Box::new(cmd_hmacencrypt::CommandImpl),
|
Box::new(cmd_hmac_encrypt::CommandImpl),
|
||||||
Box::new(cmd_hmacdecrypt::CommandImpl),
|
Box::new(cmd_hmac_decrypt::CommandImpl),
|
||||||
Box::new(cmd_challconfig::CommandImpl),
|
Box::new(cmd_chall_config::CommandImpl),
|
||||||
Box::new(cmd_rsaencrypt::CommandImpl),
|
Box::new(cmd_rsa_encrypt::CommandImpl),
|
||||||
Box::new(cmd_rsadecrypt::CommandImpl),
|
Box::new(cmd_rsa_decrypt::CommandImpl),
|
||||||
Box::new(cmd_rsaverify::CommandImpl),
|
Box::new(cmd_rsa_verify::CommandImpl),
|
||||||
#[cfg(feature = "with-sequoia-openpgp")]
|
#[cfg(feature = "with-sequoia-openpgp")]
|
||||||
Box::new(cmd_pgp::CommandImpl),
|
Box::new(cmd_pgp::CommandImpl),
|
||||||
Box::new(cmd_pgpcardadmin::CommandImpl),
|
Box::new(cmd_pgp_card_admin::CommandImpl),
|
||||||
Box::new(cmd_pgpcardlist::CommandImpl),
|
Box::new(cmd_pgp_card_list::CommandImpl),
|
||||||
Box::new(cmd_pgpcardsign::CommandImpl),
|
Box::new(cmd_pgp_card_sign::CommandImpl),
|
||||||
Box::new(cmd_pgpcarddecrypt::CommandImpl),
|
Box::new(cmd_pgp_card_decrypt::CommandImpl),
|
||||||
#[cfg(feature = "with-sequoia-openpgp")]
|
#[cfg(feature = "with-sequoia-openpgp")]
|
||||||
Box::new(cmd_pgpcardmake::CommandImpl),
|
Box::new(cmd_pgp_card_make::CommandImpl),
|
||||||
Box::new(cmd_piv::CommandImpl),
|
Box::new(cmd_piv::CommandImpl),
|
||||||
Box::new(cmd_pivsummary::CommandImpl),
|
Box::new(cmd_piv_summary::CommandImpl),
|
||||||
Box::new(cmd_pivmeta::CommandImpl),
|
Box::new(cmd_piv_meta::CommandImpl),
|
||||||
Box::new(cmd_pivverify::CommandImpl),
|
Box::new(cmd_piv_verify::CommandImpl),
|
||||||
Box::new(cmd_pivrsasign::CommandImpl),
|
Box::new(cmd_piv_rsasign::CommandImpl),
|
||||||
Box::new(cmd_pivecdh::CommandImpl),
|
Box::new(cmd_piv_ecdh::CommandImpl),
|
||||||
Box::new(cmd_pivecsign::CommandImpl),
|
Box::new(cmd_piv_ecsign::CommandImpl),
|
||||||
Box::new(cmd_pivdecrypt::CommandImpl),
|
Box::new(cmd_piv_decrypt::CommandImpl),
|
||||||
Box::new(cmd_pivgenerate::CommandImpl),
|
Box::new(cmd_piv_generate::CommandImpl),
|
||||||
Box::new(cmd_u2fregister::CommandImpl),
|
Box::new(cmd_u2f_register::CommandImpl),
|
||||||
Box::new(cmd_u2fsign::CommandImpl),
|
Box::new(cmd_u2f_sign::CommandImpl),
|
||||||
Box::new(cmd_sshagent::CommandImpl),
|
Box::new(cmd_ssh_agent::CommandImpl),
|
||||||
Box::new(cmd_sshparsesign::CommandImpl),
|
Box::new(cmd_ssh_agent_gpg::CommandImpl),
|
||||||
Box::new(cmd_sshpivsign::CommandImpl),
|
Box::new(cmd_ssh_parse_sign::CommandImpl),
|
||||||
Box::new(cmd_sshpivcert::CommandImpl),
|
Box::new(cmd_ssh_piv_sign::CommandImpl),
|
||||||
Box::new(cmd_sshpubkey::CommandImpl),
|
Box::new(cmd_ssh_piv_cert::CommandImpl),
|
||||||
Box::new(cmd_sshparse::CommandImpl),
|
Box::new(cmd_ssh_pub_key::CommandImpl),
|
||||||
Box::new(cmd_pgpageaddress::CommandImpl),
|
Box::new(cmd_ssh_parse::CommandImpl),
|
||||||
Box::new(cmd_signjwt::CommandImpl),
|
Box::new(cmd_pgp_age_address::CommandImpl),
|
||||||
Box::new(cmd_signjwtsoft::CommandImpl),
|
Box::new(cmd_sign_jwt_piv::CommandImpl),
|
||||||
Box::new(cmd_signfile::CommandImpl),
|
Box::new(cmd_sign_jwt_soft::CommandImpl),
|
||||||
Box::new(cmd_verifyfile::CommandImpl),
|
Box::new(cmd_sign_jwt_se::CommandImpl),
|
||||||
|
Box::new(cmd_sign_jwt::CommandImpl),
|
||||||
|
Box::new(cmd_file_sign::CommandImpl),
|
||||||
|
Box::new(cmd_file_verify::CommandImpl),
|
||||||
Box::new(cmd_se::CommandImpl),
|
Box::new(cmd_se::CommandImpl),
|
||||||
Box::new(cmd_se_generate::CommandImpl),
|
Box::new(cmd_se_generate::CommandImpl),
|
||||||
Box::new(cmd_se_recover::CommandImpl),
|
Box::new(cmd_se_recover::CommandImpl),
|
||||||
Box::new(cmd_se_ecsign::CommandImpl),
|
Box::new(cmd_se_ecsign::CommandImpl),
|
||||||
Box::new(cmd_se_ecdh::CommandImpl),
|
Box::new(cmd_se_ecdh::CommandImpl),
|
||||||
Box::new(cmd_ecverify::CommandImpl),
|
Box::new(cmd_ec_verify::CommandImpl),
|
||||||
Box::new(cmd_parseecdsasignature::CommandImpl),
|
Box::new(cmd_parseecdsasignature::CommandImpl),
|
||||||
Box::new(cmd_generatekeypair::CommandImpl),
|
Box::new(cmd_keypair_generate::CommandImpl),
|
||||||
|
Box::new(cmd_keypair_keychain_import::CommandImpl),
|
||||||
|
Box::new(cmd_keypair_keychain_export::CommandImpl),
|
||||||
|
Box::new(cmd_convert_pem_to_jwk::CommandImpl),
|
||||||
|
Box::new(cmd_convert_jwk_to_pem::CommandImpl),
|
||||||
|
Box::new(cmd_external_spec::CommandImpl),
|
||||||
|
Box::new(cmd_external_public_key::CommandImpl),
|
||||||
|
Box::new(cmd_external_sign::CommandImpl),
|
||||||
|
Box::new(cmd_external_ecdh::CommandImpl),
|
||||||
|
Box::new(cmd_yubikey::CommandImpl),
|
||||||
];
|
];
|
||||||
|
|
||||||
#[allow(clippy::vec_init_then_push)]
|
#[allow(clippy::vec_init_then_push)]
|
||||||
|
|||||||
241
src/mlkemutil.rs
Normal file
241
src/mlkemutil.rs
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
use crate::util::{base64_encode, to_pem};
|
||||||
|
use ml_kem::kem::{Decapsulate, Encapsulate};
|
||||||
|
use ml_kem::{EncodedSizeUser, KemCore, MlKem1024, MlKem512, MlKem768};
|
||||||
|
use rust_util::XResult;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use crate::pivutil::ToStr;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum MlKemLen {
|
||||||
|
Len512,
|
||||||
|
Len768,
|
||||||
|
Len1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStr for MlKemLen {
|
||||||
|
fn to_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
MlKemLen::Len512 => "mlkem512",
|
||||||
|
MlKemLen::Len768 => "mlkem768",
|
||||||
|
MlKemLen::Len1024 => "mlkem1024",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_mlkem_keypair(len: usize) -> XResult<(String, String, String, Vec<u8>, String)> {
|
||||||
|
let (dk_private, ek_public) = match len {
|
||||||
|
512 => generate_ml_kem_512(),
|
||||||
|
768 => generate_ml_kem_768(),
|
||||||
|
1024 => generate_ml_kem_1024(),
|
||||||
|
_ => return simple_error!("Invalid ML-KEM={}", len),
|
||||||
|
};
|
||||||
|
let secret_key_der_base64 = base64_encode(&dk_private);
|
||||||
|
let secret_key_pem = to_pem(&format!("ML-KEM-{} PRIVATE KEY", len), &dk_private);
|
||||||
|
let public_key_pem = to_pem(&format!("ML-KEM-{} PUBLIC KEY", len), &ek_public);
|
||||||
|
let public_key_der = ek_public;
|
||||||
|
let jwk_ec_key = "".to_string();
|
||||||
|
Ok((
|
||||||
|
secret_key_der_base64,
|
||||||
|
secret_key_pem,
|
||||||
|
public_key_pem,
|
||||||
|
public_key_der,
|
||||||
|
jwk_ec_key,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_parse_decapsulate_key_private_then_decapsulate(
|
||||||
|
key_bytes: &[u8],
|
||||||
|
ciphertext_bytes: &[u8],
|
||||||
|
) -> XResult<(MlKemLen, Vec<u8>)> {
|
||||||
|
if let Ok(shared_secret) =
|
||||||
|
parse_decapsulate_key_512_private_then_decapsulate(key_bytes, ciphertext_bytes)
|
||||||
|
{
|
||||||
|
return Ok((MlKemLen::Len512, shared_secret.to_vec()));
|
||||||
|
}
|
||||||
|
if let Ok(shared_secret) =
|
||||||
|
parse_decapsulate_key_768_private_then_decapsulate(key_bytes, ciphertext_bytes)
|
||||||
|
{
|
||||||
|
return Ok((MlKemLen::Len768, shared_secret.to_vec()));
|
||||||
|
}
|
||||||
|
if let Ok(shared_secret) =
|
||||||
|
parse_decapsulate_key_1024_private_then_decapsulate(key_bytes, ciphertext_bytes)
|
||||||
|
{
|
||||||
|
return Ok((MlKemLen::Len1024, shared_secret.to_vec()));
|
||||||
|
}
|
||||||
|
simple_error!("Invalid decapsulation key, only allow MK-KEM-512, ML-KEM-768, ML-KEM-1024")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_parse_decapsulate_key_private_get_encapsulate(
|
||||||
|
key_bytes: &[u8],
|
||||||
|
) -> XResult<(MlKemLen, Vec<u8>)> {
|
||||||
|
if let Ok(encapsulate_key) = parse_decapsulate_key_512_private_get_encapsulate(key_bytes) {
|
||||||
|
return Ok((MlKemLen::Len512, encapsulate_key));
|
||||||
|
}
|
||||||
|
if let Ok(encapsulate_key) = parse_decapsulate_key_768_private_get_encapsulate(key_bytes) {
|
||||||
|
return Ok((MlKemLen::Len768, encapsulate_key));
|
||||||
|
}
|
||||||
|
if let Ok(encapsulate_key) = parse_decapsulate_key_1024_private_get_encapsulate(key_bytes) {
|
||||||
|
return Ok((MlKemLen::Len1024, encapsulate_key));
|
||||||
|
}
|
||||||
|
simple_error!("Invalid decapsulation key, only allow MK-KEM-512, ML-KEM-768, ML-KEM-1024")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_ml_kem_512() -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let (dk_private, ek_public) = <MlKem512 as KemCore>::generate(&mut rng);
|
||||||
|
(
|
||||||
|
dk_private.as_bytes().0.to_vec(),
|
||||||
|
ek_public.as_bytes().0.to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_ml_kem_768() -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let (dk_private, ek_public) = <MlKem768 as KemCore>::generate(&mut rng);
|
||||||
|
(
|
||||||
|
dk_private.as_bytes().0.to_vec(),
|
||||||
|
ek_public.as_bytes().0.to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_ml_kem_1024() -> (Vec<u8>, Vec<u8>) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let (dk_private, ek_public) = <MlKem1024 as KemCore>::generate(&mut rng);
|
||||||
|
(
|
||||||
|
dk_private.as_bytes().0.to_vec(),
|
||||||
|
ek_public.as_bytes().0.to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_parse_encapsulation_key_public_then_encapsulate(bytes: &[u8]) -> XResult<(MlKemLen, Vec<u8>, Vec<u8>)> {
|
||||||
|
if let Ok((ciphertext, shared_key)) = parse_encapsulation_key_512_public_then_encapsulate(bytes) {
|
||||||
|
return Ok((MlKemLen::Len512, ciphertext, shared_key));
|
||||||
|
}
|
||||||
|
if let Ok((ciphertext, shared_key)) = parse_encapsulation_key_768_public_then_encapsulate(bytes) {
|
||||||
|
return Ok((MlKemLen::Len768, ciphertext, shared_key));
|
||||||
|
}
|
||||||
|
if let Ok((ciphertext, shared_key)) = parse_encapsulation_key_1024_public_then_encapsulate(bytes) {
|
||||||
|
return Ok((MlKemLen::Len1024, ciphertext, shared_key));
|
||||||
|
}
|
||||||
|
simple_error!("Invalid encapsulation key, only allow MK-KEM-512, ML-KEM-768, ML-KEM-1024")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_encapsulation_key_512_public_then_encapsulate(
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let ek = <MlKem512 as KemCore>::EncapsulationKey::from_bytes(&opt_result!(
|
||||||
|
bytes.try_into(),
|
||||||
|
"Parse encapsulation key 512 failed: {}"
|
||||||
|
));
|
||||||
|
let (ciphertext, shared_key) = opt_result!(
|
||||||
|
ek.encapsulate(&mut rand::thread_rng()),
|
||||||
|
"Encapsulation key 512 encapsulate failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok((ciphertext.0.to_vec(), shared_key.0.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_encapsulation_key_768_public_then_encapsulate(
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let ek = <MlKem768 as KemCore>::EncapsulationKey::from_bytes(&opt_result!(
|
||||||
|
bytes.try_into(),
|
||||||
|
"Parse encapsulation key 768 failed: {}"
|
||||||
|
));
|
||||||
|
let (ciphertext, shared_key) = opt_result!(
|
||||||
|
ek.encapsulate(&mut rand::thread_rng()),
|
||||||
|
"Encapsulation key 768 encapsulate failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok((ciphertext.0.to_vec(), shared_key.0.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_encapsulation_key_1024_public_then_encapsulate(
|
||||||
|
bytes: &[u8],
|
||||||
|
) -> XResult<(Vec<u8>, Vec<u8>)> {
|
||||||
|
let ek = <MlKem1024 as KemCore>::EncapsulationKey::from_bytes(&opt_result!(
|
||||||
|
bytes.try_into(),
|
||||||
|
"Parse encapsulation key 1024 failed: {}"
|
||||||
|
));
|
||||||
|
let (ciphertext, shared_key) = opt_result!(
|
||||||
|
ek.encapsulate(&mut rand::thread_rng()),
|
||||||
|
"Encapsulation key 1024 encapsulate failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok((ciphertext.0.to_vec(), shared_key.0.to_vec()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decapsulate_key_512_private_then_decapsulate(
|
||||||
|
key_bytes: &[u8],
|
||||||
|
ciphertext_bytes: &[u8],
|
||||||
|
) -> XResult<Vec<u8>> {
|
||||||
|
let dk = <MlKem512 as KemCore>::DecapsulationKey::from_bytes(&opt_result!(
|
||||||
|
key_bytes.try_into(),
|
||||||
|
"Parse decapsulation key 512 failed: {}"
|
||||||
|
));
|
||||||
|
let shared_key = opt_result!(
|
||||||
|
dk.decapsulate(opt_result!(
|
||||||
|
ciphertext_bytes.try_into(),
|
||||||
|
"Parse encoded ciphertext 512 failed: {}"
|
||||||
|
)),
|
||||||
|
"Decapsulation key 512 decapsulate failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok(shared_key.0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decapsulate_key_768_private_then_decapsulate(
|
||||||
|
key_bytes: &[u8],
|
||||||
|
ciphertext_bytes: &[u8],
|
||||||
|
) -> XResult<Vec<u8>> {
|
||||||
|
let dk = <MlKem768 as KemCore>::DecapsulationKey::from_bytes(&opt_result!(
|
||||||
|
key_bytes.try_into(),
|
||||||
|
"Parse decapsulation key 768 failed: {}"
|
||||||
|
));
|
||||||
|
let shared_key = opt_result!(
|
||||||
|
dk.decapsulate(opt_result!(
|
||||||
|
ciphertext_bytes.try_into(),
|
||||||
|
"Parse encoded ciphertext 768 failed: {}"
|
||||||
|
)),
|
||||||
|
"Decapsulation key 768 decapsulate failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok(shared_key.0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decapsulate_key_1024_private_then_decapsulate(
|
||||||
|
key_bytes: &[u8],
|
||||||
|
ciphertext_bytes: &[u8],
|
||||||
|
) -> XResult<Vec<u8>> {
|
||||||
|
let dk = <MlKem1024 as KemCore>::DecapsulationKey::from_bytes(&opt_result!(
|
||||||
|
key_bytes.try_into(),
|
||||||
|
"Parse decapsulation key 1024 failed: {}"
|
||||||
|
));
|
||||||
|
let shared_key = opt_result!(
|
||||||
|
dk.decapsulate(opt_result!(
|
||||||
|
ciphertext_bytes.try_into(),
|
||||||
|
"Parse encoded ciphertext 1024 failed: {}"
|
||||||
|
)),
|
||||||
|
"Decapsulation key 1024 decapsulate failed: {:?}"
|
||||||
|
);
|
||||||
|
Ok(shared_key.0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decapsulate_key_512_private_get_encapsulate(key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let dk = <MlKem512 as KemCore>::DecapsulationKey::from_bytes(&opt_result!(
|
||||||
|
key_bytes.try_into(),
|
||||||
|
"Parse decapsulation key 512 failed: {}"
|
||||||
|
));
|
||||||
|
Ok(dk.encapsulation_key().as_bytes().0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decapsulate_key_768_private_get_encapsulate(key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let dk = <MlKem768 as KemCore>::DecapsulationKey::from_bytes(&opt_result!(
|
||||||
|
key_bytes.try_into(),
|
||||||
|
"Parse decapsulation key 768 failed: {}"
|
||||||
|
));
|
||||||
|
Ok(dk.encapsulation_key().as_bytes().0.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_decapsulate_key_1024_private_get_encapsulate(key_bytes: &[u8]) -> XResult<Vec<u8>> {
|
||||||
|
let dk = <MlKem1024 as KemCore>::DecapsulationKey::from_bytes(&opt_result!(
|
||||||
|
key_bytes.try_into(),
|
||||||
|
"Parse decapsulation key 1024 failed: {}"
|
||||||
|
));
|
||||||
|
Ok(dk.encapsulation_key().as_bytes().0.to_vec())
|
||||||
|
}
|
||||||
119
src/pbeutil.rs
Normal file
119
src/pbeutil.rs
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
use crate::digestutil::{copy_sha256, sha256_bytes};
|
||||||
|
use crate::pinutil;
|
||||||
|
use crate::util::{base64_decode, base64_encode, base64_encode_url_safe_no_pad};
|
||||||
|
use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
|
||||||
|
use rand::random;
|
||||||
|
use rust_util::XResult;
|
||||||
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
|
const PBE_ENC_PREFIX: &str = "pbe_enc:";
|
||||||
|
|
||||||
|
pub fn simple_pbe_encrypt_with_prompt_from_string(iteration: u32, plaintext: &str, passowrd: &mut Option<String>, password_double_check: bool) -> XResult<String> {
|
||||||
|
simple_pbe_encrypt_with_prompt(iteration, plaintext.as_bytes(), passowrd, password_double_check)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_decrypt_with_prompt_to_string(pin_opt: &mut Option<String>, ciphertext: &str) -> XResult<String> {
|
||||||
|
let plaintext = simple_pbe_decrypt_with_prompt(pin_opt, ciphertext)?;
|
||||||
|
Ok(String::from_utf8(plaintext)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_encrypt_with_prompt(iteration: u32, plaintext: &[u8], password_opt: &mut Option<String>, password_double_check: bool) -> XResult<String> {
|
||||||
|
let mut pin = match password_opt {
|
||||||
|
None => {
|
||||||
|
let pin1 = opt_value_result!(pinutil::get_pin(None), "Simple PBE password required");
|
||||||
|
if password_double_check {
|
||||||
|
let mut pin2 = opt_value_result!(pinutil::get_pin(None), "Simple PBE password required");
|
||||||
|
if pin1 != pin2 {
|
||||||
|
return simple_error!("Two PINs mismatch");
|
||||||
|
}
|
||||||
|
pin2.zeroize();
|
||||||
|
}
|
||||||
|
*password_opt = Some(pin1.clone());
|
||||||
|
pin1
|
||||||
|
}
|
||||||
|
Some(pin) => pin.clone(),
|
||||||
|
};
|
||||||
|
let encrypt_result = simple_pbe_encrypt(&pin, iteration, plaintext);
|
||||||
|
pin.zeroize();
|
||||||
|
encrypt_result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_decrypt_with_prompt(pin_opt: &mut Option<String>, ciphertext: &str) -> XResult<Vec<u8>> {
|
||||||
|
let mut pin = opt_value_result!(pinutil::get_pin(pin_opt.clone().as_deref()), "Simple PBE password required");
|
||||||
|
pin_opt.zeroize();
|
||||||
|
*pin_opt = Some(pin.clone());
|
||||||
|
let decrypt_result = simple_pbe_decrypt(&pin, ciphertext);
|
||||||
|
pin.zeroize();
|
||||||
|
decrypt_result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_encrypt(password: &str, iteration: u32, plaintext: &[u8]) -> XResult<String> {
|
||||||
|
let pbe_salt: [u8; 16] = random();
|
||||||
|
let key = simple_pbe_kdf(password, &pbe_salt, iteration)?;
|
||||||
|
let aes_gcm_nonce: [u8; 16] = random();
|
||||||
|
|
||||||
|
let mut encryptor = Aes256GcmStreamEncryptor::new(key, &aes_gcm_nonce);
|
||||||
|
let mut ciphertext = encryptor.update(plaintext);
|
||||||
|
let (final_part, tag) = encryptor.finalize();
|
||||||
|
ciphertext.extend_from_slice(&final_part);
|
||||||
|
ciphertext.extend_from_slice(&tag);
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"{}{}:{}:{}:{}",
|
||||||
|
PBE_ENC_PREFIX,
|
||||||
|
iteration,
|
||||||
|
base64_encode_url_safe_no_pad(pbe_salt),
|
||||||
|
base64_encode_url_safe_no_pad(aes_gcm_nonce),
|
||||||
|
base64_encode(&ciphertext)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_decrypt(password: &str, ciphertext: &str) -> XResult<Vec<u8>> {
|
||||||
|
if !is_simple_pbe_encrypted(ciphertext) {
|
||||||
|
return simple_error!("Invalid ciphertext: {}", ciphertext);
|
||||||
|
}
|
||||||
|
let parts = ciphertext.split(":").collect::<Vec<_>>();
|
||||||
|
let iteration: u32 = parts[1].parse()?;
|
||||||
|
let pbe_salt = crate::hmacutil::try_decode_hmac_val(parts[2])?;
|
||||||
|
let aes_gcm_nonce = crate::hmacutil::try_decode_hmac_val(parts[3])?;
|
||||||
|
let ciphertext = base64_decode(parts[4])?;
|
||||||
|
|
||||||
|
let key = simple_pbe_kdf(password, &pbe_salt, iteration)?;
|
||||||
|
|
||||||
|
let mut decryptor = Aes256GcmStreamDecryptor::new(key, &aes_gcm_nonce);
|
||||||
|
let mut plaintext = decryptor.update(&ciphertext);
|
||||||
|
let final_part = decryptor.finalize()?;
|
||||||
|
plaintext.extend_from_slice(&final_part);
|
||||||
|
|
||||||
|
Ok(plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_simple_pbe_encrypted(ciphertext: &str) -> bool {
|
||||||
|
ciphertext.starts_with(PBE_ENC_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_pbe_kdf(password: &str, pbe_salt: &[u8], iteration: u32) -> XResult<[u8; 32]> {
|
||||||
|
let mut init_data = password.as_bytes().to_vec();
|
||||||
|
init_data.extend_from_slice(pbe_salt);
|
||||||
|
let mut loop_hash = sha256_bytes(&init_data);
|
||||||
|
for i in 0..iteration {
|
||||||
|
let i_to_bytes = i.to_be_bytes();
|
||||||
|
loop_hash[..4].copy_from_slice(&i_to_bytes);
|
||||||
|
loop_hash = sha256_bytes(&loop_hash);
|
||||||
|
}
|
||||||
|
let key = copy_sha256(&sha256_bytes(&loop_hash))?;
|
||||||
|
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_simple_pbe_kdf() {
|
||||||
|
assert_eq!("cea7eb6424132aa4d6b1880c3cb570df17c41d766826e144088fd16b334c80c5",
|
||||||
|
hex::encode(simple_pbe_kdf("hello_world", b"salt", 1).unwrap()));
|
||||||
|
assert_eq!("e3ed4a0a2f451180cfa4a0f8f3e1181d8863fc192d161e4c1480e3612135ca27",
|
||||||
|
hex::encode(simple_pbe_kdf("hello_world", b"salt", 2).unwrap()));
|
||||||
|
assert_eq!("e552900ad3f14a96629c8056bd7bc4b2431250ef4a47e78856626f45807cd87e",
|
||||||
|
hex::encode(simple_pbe_kdf("hello_world 2", b"salt", 2).unwrap()));
|
||||||
|
assert_eq!("37e71484f00033c99db444c77553364b31614d5145e38aad5bd5caa6676d40f9",
|
||||||
|
hex::encode(simple_pbe_kdf("hello_world", b"salt 2", 2).unwrap()));
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
|
use jwt::AlgorithmType;
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
use spki::{ObjectIdentifier, SubjectPublicKeyInfoOwned};
|
use spki::{ObjectIdentifier, SubjectPublicKeyInfoOwned};
|
||||||
use spki::der::{Decode, Encode};
|
use spki::der::{Decode, Encode};
|
||||||
@@ -56,6 +57,32 @@ pub trait ToStr {
|
|||||||
fn to_str(&self) -> &str;
|
fn to_str(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait FromStr {
|
||||||
|
fn from_str(s: &str) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStr for AlgorithmType {
|
||||||
|
fn to_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
AlgorithmType::Hs256 => "HS256",
|
||||||
|
AlgorithmType::Hs384 => "HS384",
|
||||||
|
AlgorithmType::Hs512 => "HS512",
|
||||||
|
AlgorithmType::Rs256 => "RS256",
|
||||||
|
AlgorithmType::Rs384 => "RS384",
|
||||||
|
AlgorithmType::Rs512 => "RS512",
|
||||||
|
AlgorithmType::Es256 => "ES256",
|
||||||
|
AlgorithmType::Es384 => "ES384",
|
||||||
|
AlgorithmType::Es512 => "ES512",
|
||||||
|
AlgorithmType::Ps256 => "PS256",
|
||||||
|
AlgorithmType::Ps384 => "PS384",
|
||||||
|
AlgorithmType::Ps512 => "PS512",
|
||||||
|
AlgorithmType::None => "NONE",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToStr for PinPolicy {
|
impl ToStr for PinPolicy {
|
||||||
fn to_str(&self) -> &str {
|
fn to_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
@@ -78,6 +105,21 @@ impl ToStr for TouchPolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for AlgorithmId {
|
||||||
|
fn from_str(s: &str) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
match s {
|
||||||
|
"rsa1024" => Some(AlgorithmId::Rsa1024),
|
||||||
|
"rsa2048" => Some(AlgorithmId::Rsa2048),
|
||||||
|
"p256" => Some(AlgorithmId::EccP256),
|
||||||
|
"p384" => Some(AlgorithmId::EccP384),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToStr for AlgorithmId {
|
impl ToStr for AlgorithmId {
|
||||||
fn to_str(&self) -> &str {
|
fn to_str(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
@@ -169,6 +211,7 @@ pub fn get_slot_id(slot: &str) -> XResult<SlotId> {
|
|||||||
"9c" | "sign" | "signature" => SlotId::Signature,
|
"9c" | "sign" | "signature" => SlotId::Signature,
|
||||||
"9d" | "keym" | "keymanagement" => SlotId::KeyManagement,
|
"9d" | "keym" | "keymanagement" => SlotId::KeyManagement,
|
||||||
"9e" | "card" | "cardauthentication" => SlotId::CardAuthentication,
|
"9e" | "card" | "cardauthentication" => SlotId::CardAuthentication,
|
||||||
|
"f9" | "attest" | "attestation" => SlotId::Attestation,
|
||||||
"r1" | "82" => SlotId::Retired(RetiredSlotId::R1),
|
"r1" | "82" => SlotId::Retired(RetiredSlotId::R1),
|
||||||
"r2" | "83" => SlotId::Retired(RetiredSlotId::R2),
|
"r2" | "83" => SlotId::Retired(RetiredSlotId::R2),
|
||||||
"r3" | "84" => SlotId::Retired(RetiredSlotId::R3),
|
"r3" | "84" => SlotId::Retired(RetiredSlotId::R3),
|
||||||
@@ -193,6 +236,54 @@ pub fn get_slot_id(slot: &str) -> XResult<SlotId> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for SlotId {
|
||||||
|
fn from_str(s: &str) -> Option<Self>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
get_slot_id(s).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToStr for SlotId {
|
||||||
|
fn to_str(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
SlotId::Authentication => "authentication",
|
||||||
|
SlotId::Signature => "signature",
|
||||||
|
SlotId::KeyManagement => "keymanagement",
|
||||||
|
SlotId::CardAuthentication => "cardauthentication",
|
||||||
|
SlotId::Retired(retried) => match retried {
|
||||||
|
RetiredSlotId::R1 => "r1",
|
||||||
|
RetiredSlotId::R2 => "r2",
|
||||||
|
RetiredSlotId::R3 => "r3",
|
||||||
|
RetiredSlotId::R4 => "r4",
|
||||||
|
RetiredSlotId::R5 => "r5",
|
||||||
|
RetiredSlotId::R6 => "r6",
|
||||||
|
RetiredSlotId::R7 => "r7",
|
||||||
|
RetiredSlotId::R8 => "r8",
|
||||||
|
RetiredSlotId::R9 => "r9",
|
||||||
|
RetiredSlotId::R10 => "r10",
|
||||||
|
RetiredSlotId::R11 => "r11",
|
||||||
|
RetiredSlotId::R12 => "r12",
|
||||||
|
RetiredSlotId::R13 => "r13",
|
||||||
|
RetiredSlotId::R14 => "r14",
|
||||||
|
RetiredSlotId::R15 => "r15",
|
||||||
|
RetiredSlotId::R16 => "r16",
|
||||||
|
RetiredSlotId::R17 => "r17",
|
||||||
|
RetiredSlotId::R18 => "r18",
|
||||||
|
RetiredSlotId::R19 => "r19",
|
||||||
|
RetiredSlotId::R20 => "r20",
|
||||||
|
}
|
||||||
|
SlotId::Attestation => "attestation",
|
||||||
|
SlotId::Management(management) => match management {
|
||||||
|
ManagementSlotId::Pin => "pin",
|
||||||
|
ManagementSlotId::Puk => "puk",
|
||||||
|
ManagementSlotId::Management => "management",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn check_read_pin(yk: &mut YubiKey, slot_id: SlotId, sub_arg_matches: &ArgMatches) -> Option<String> {
|
pub fn check_read_pin(yk: &mut YubiKey, slot_id: SlotId, sub_arg_matches: &ArgMatches) -> Option<String> {
|
||||||
if never_use_pin(yk, slot_id) {
|
if never_use_pin(yk, slot_id) {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use rust_util::XResult;
|
|||||||
use sequoia_openpgp::crypto::mpi::PublicKey;
|
use sequoia_openpgp::crypto::mpi::PublicKey;
|
||||||
use x509_parser::x509::AlgorithmIdentifier;
|
use x509_parser::x509::AlgorithmIdentifier;
|
||||||
|
|
||||||
use crate::digest::sha256_bytes;
|
use crate::digestutil::sha256_bytes;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum PkiAlgorithm {
|
pub enum PkiAlgorithm {
|
||||||
|
|||||||
@@ -1,8 +1,65 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use ecdsa::elliptic_curve::rand_core::OsRng;
|
||||||
use openssl::bn::{BigNum, BigNumContext};
|
use openssl::bn::{BigNum, BigNumContext};
|
||||||
use openssl::pkey::PKey;
|
use openssl::pkey::PKey;
|
||||||
use openssl::rsa::{Padding, Rsa};
|
use openssl::rsa::{Padding, Rsa};
|
||||||
|
use rsa::{Pkcs1v15Sign, RsaPrivateKey, RsaPublicKey};
|
||||||
use rust_util::{util_msg, XResult};
|
use rust_util::{util_msg, XResult};
|
||||||
use rust_util::util_msg::MessageType;
|
use rust_util::util_msg::MessageType;
|
||||||
|
use spki::DecodePublicKey;
|
||||||
|
use rsa::pkcs1::DecodeRsaPublicKey;
|
||||||
|
use rsa::traits::PublicKeyParts;
|
||||||
|
use spki::EncodePublicKey;
|
||||||
|
use rsa::pkcs1::LineEnding;
|
||||||
|
use rsa::pkcs8::EncodePrivateKey;
|
||||||
|
use sha2::{Sha256, Sha384, Sha512};
|
||||||
|
use crate::digestutil;
|
||||||
|
use crate::util::{base64_decode, base64_encode};
|
||||||
|
|
||||||
|
pub enum RsaSignAlgorithm {
|
||||||
|
Rs256,
|
||||||
|
Rs384,
|
||||||
|
Rs512,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RsaSignAlgorithm {
|
||||||
|
pub fn from_str(alg: &str) -> Option<RsaSignAlgorithm> {
|
||||||
|
match alg {
|
||||||
|
"RS256" => Some(RsaSignAlgorithm::Rs256),
|
||||||
|
"RS384" => Some(RsaSignAlgorithm::Rs384),
|
||||||
|
"RS512" => Some(RsaSignAlgorithm::Rs512),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign(rsa_private_key: &RsaPrivateKey, rsa_sign_algorithm: RsaSignAlgorithm, message: &[u8], is_raw: bool) -> XResult<Vec<u8>> {
|
||||||
|
match rsa_sign_algorithm {
|
||||||
|
RsaSignAlgorithm::Rs256 => {
|
||||||
|
let raw_in = iff!(is_raw, digestutil::sha256_bytes(message), message.to_vec());
|
||||||
|
Ok(rsa_private_key.sign(Pkcs1v15Sign::new::<Sha256>(), &raw_in)?)
|
||||||
|
}
|
||||||
|
RsaSignAlgorithm::Rs384 => {
|
||||||
|
let raw_in = iff!(is_raw, digestutil::sha384_bytes(message), message.to_vec());
|
||||||
|
Ok(rsa_private_key.sign(Pkcs1v15Sign::new::<Sha384>(), &raw_in)?)
|
||||||
|
}
|
||||||
|
RsaSignAlgorithm::Rs512 => {
|
||||||
|
let raw_in = iff!(is_raw, digestutil::sha512_bytes(message), message.to_vec());
|
||||||
|
Ok(rsa_private_key.sign(Pkcs1v15Sign::new::<Sha512>(), &raw_in)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_rsa_keypair(bit_size: usize) -> XResult<(String, String, String, Vec<u8>, String)> {
|
||||||
|
let rsa_private_key = opt_result!(RsaPrivateKey::new(&mut OsRng, bit_size), "Generate RSA private key failed: {}");
|
||||||
|
let rsa_public_key = rsa_private_key.to_public_key();
|
||||||
|
let secret_key_der_base64 = base64_encode(rsa_private_key.to_pkcs8_der()?.as_bytes());
|
||||||
|
let secret_key_pem = rsa_private_key.to_pkcs8_pem(LineEnding::LF)?.to_string();
|
||||||
|
let public_key_pem = rsa_public_key.to_public_key_pem(LineEnding::LF)?;
|
||||||
|
let public_key_der = rsa_public_key.to_public_key_der()?.to_vec();
|
||||||
|
let jwk_ec_key = rsa_public_key_to_jwk(&rsa_public_key)?;
|
||||||
|
Ok((secret_key_der_base64, secret_key_pem, public_key_pem, public_key_der, jwk_ec_key))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RsaCrt {
|
pub struct RsaCrt {
|
||||||
@@ -151,3 +208,43 @@ fn pkcs1_padding_for_sign(bs: &[u8], bit_len: usize) -> XResult<Vec<u8>> {
|
|||||||
output.extend_from_slice(bs);
|
output.extend_from_slice(bs);
|
||||||
Ok(output)
|
Ok(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn convert_rsa_to_jwk(public_key: &str) -> XResult<String> {
|
||||||
|
let rsa_public_key = try_parse_rsa(public_key)?;
|
||||||
|
rsa_public_key_to_jwk(&rsa_public_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rsa_public_key_to_jwk(rsa_public_key: &RsaPublicKey) -> XResult<String> {
|
||||||
|
let e_bytes = rsa_public_key.e().to_bytes_be();
|
||||||
|
let n_bytes = rsa_public_key.n().to_bytes_be();
|
||||||
|
|
||||||
|
let mut jwk = HashMap::new();
|
||||||
|
jwk.insert("kty", "RSA".to_string());
|
||||||
|
jwk.insert("n", base64_encode(&n_bytes));
|
||||||
|
jwk.insert("e", base64_encode(&e_bytes));
|
||||||
|
|
||||||
|
Ok(serde_json::to_string(&jwk).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_parse_rsa(public_key: &str) -> XResult<RsaPublicKey> {
|
||||||
|
debugging!("Try parse RSA public key PEM.");
|
||||||
|
// parse RSA public key PEM not works? why?
|
||||||
|
if let Ok(rsa_public_key) = RsaPublicKey::from_public_key_pem(public_key) {
|
||||||
|
return Ok(rsa_public_key);
|
||||||
|
}
|
||||||
|
debugging!("Try parse RSA PKCS#1 public key PEM.");
|
||||||
|
if let Ok(rsa_public_key) = RsaPublicKey::from_pkcs1_pem(public_key) {
|
||||||
|
return Ok(rsa_public_key);
|
||||||
|
}
|
||||||
|
if let Ok(public_key_der) = base64_decode(public_key) {
|
||||||
|
debugging!("Try parse RSA public key DER.");
|
||||||
|
if let Ok(rsa_public_key) = RsaPublicKey::from_public_key_der(&public_key_der) {
|
||||||
|
return Ok(rsa_public_key);
|
||||||
|
}
|
||||||
|
debugging!("Try parse RSA PKCS#1 public key DER.");
|
||||||
|
if let Ok(rsa_public_key) = RsaPublicKey::from_pkcs1_der(&public_key_der) {
|
||||||
|
return Ok(rsa_public_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
simple_error!("Invalid RSA public key.")
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,49 @@
|
|||||||
use base64::engine::general_purpose::STANDARD;
|
use crate::util::{base64_decode, base64_encode};
|
||||||
use base64::Engine;
|
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
use swift_secure_enclave_tool_rs::KeyPurpose;
|
use se_tool::KeyPurpose;
|
||||||
|
use swift_secure_enclave_tool_rs as se_tool;
|
||||||
|
use swift_secure_enclave_tool_rs::{ControlFlag, DigestType, KeyMlKem};
|
||||||
|
|
||||||
pub fn is_support_se() -> bool {
|
pub fn is_support_se() -> bool {
|
||||||
swift_secure_enclave_tool_rs::is_secure_enclave_supported().unwrap_or(false)
|
se_tool::is_secure_enclave_supported().unwrap_or_else(|e| {
|
||||||
|
failure!("Invoke command swift-secure-enclave-tool failed: {}", e);
|
||||||
|
false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_secure_enclave_p256_keypair(sign: bool) -> XResult<(Vec<u8>, Vec<u8>, String)> {
|
pub fn check_se_supported() -> XResult<()> {
|
||||||
let key_material = if sign {
|
if !is_support_se() {
|
||||||
swift_secure_enclave_tool_rs::generate_ecdsa_keypair(KeyPurpose::Signing, true)?
|
simple_error!("Secure Enclave is NOT supported.")
|
||||||
} else {
|
} else {
|
||||||
swift_secure_enclave_tool_rs::generate_ecdsa_keypair(KeyPurpose::KeyAgreement, true)?
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_secure_enclave_p256_keypair(
|
||||||
|
sign: bool,
|
||||||
|
control_flag: ControlFlag,
|
||||||
|
) -> XResult<(Vec<u8>, Vec<u8>, String)> {
|
||||||
|
let key_material = if sign {
|
||||||
|
se_tool::generate_keypair(KeyPurpose::Signing, control_flag)?
|
||||||
|
} else {
|
||||||
|
se_tool::generate_keypair(KeyPurpose::KeyAgreement, control_flag)?
|
||||||
};
|
};
|
||||||
Ok((
|
Ok((
|
||||||
key_material.public_key_point,
|
key_material.public_key_point,
|
||||||
key_material.public_key_der,
|
key_material.public_key_der,
|
||||||
STANDARD.encode(&key_material.private_key_representation),
|
base64_encode(&key_material.private_key_representation),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_secure_enclave_mlkem_keypair(
|
||||||
|
key_ml_kem: KeyMlKem,
|
||||||
|
control_flag: ControlFlag,
|
||||||
|
) -> XResult<(Vec<u8>, Vec<u8>, String)> {
|
||||||
|
let key_material = se_tool::generate_mlkem_keypair(key_ml_kem, control_flag)?;
|
||||||
|
Ok((
|
||||||
|
key_material.public_key_point,
|
||||||
|
key_material.public_key_der,
|
||||||
|
base64_encode(&key_material.private_key_representation),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,22 +51,16 @@ pub fn recover_secure_enclave_p256_public_key(
|
|||||||
private_key: &str,
|
private_key: &str,
|
||||||
sign: bool,
|
sign: bool,
|
||||||
) -> XResult<(Vec<u8>, Vec<u8>, String)> {
|
) -> XResult<(Vec<u8>, Vec<u8>, String)> {
|
||||||
let private_key_representation = STANDARD.decode(private_key)?;
|
let private_key_representation = base64_decode(private_key)?;
|
||||||
let key_material = if sign {
|
let key_material = if sign {
|
||||||
swift_secure_enclave_tool_rs::recover_ecdsa_keypair(
|
se_tool::recover_keypair(KeyPurpose::Signing, &private_key_representation)
|
||||||
KeyPurpose::Signing,
|
|
||||||
&private_key_representation,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
swift_secure_enclave_tool_rs::recover_ecdsa_keypair(
|
se_tool::recover_keypair(KeyPurpose::KeyAgreement, &private_key_representation)
|
||||||
KeyPurpose::KeyAgreement,
|
|
||||||
&private_key_representation,
|
|
||||||
)
|
|
||||||
}?;
|
}?;
|
||||||
Ok((
|
Ok((
|
||||||
key_material.public_key_point,
|
key_material.public_key_point,
|
||||||
key_material.public_key_der,
|
key_material.public_key_der,
|
||||||
STANDARD.encode(&key_material.private_key_representation),
|
base64_encode(&key_material.private_key_representation),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,17 +68,14 @@ pub fn secure_enclave_p256_dh(
|
|||||||
private_key: &str,
|
private_key: &str,
|
||||||
ephemeral_public_key_bytes: &[u8],
|
ephemeral_public_key_bytes: &[u8],
|
||||||
) -> XResult<Vec<u8>> {
|
) -> XResult<Vec<u8>> {
|
||||||
let private_key_representation = STANDARD.decode(private_key)?;
|
let private_key_representation = base64_decode(private_key)?;
|
||||||
let shared_secret = swift_secure_enclave_tool_rs::private_key_ecdh(
|
let shared_secret =
|
||||||
&private_key_representation,
|
se_tool::private_key_ecdh(&private_key_representation, ephemeral_public_key_bytes)?;
|
||||||
ephemeral_public_key_bytes,
|
|
||||||
)?;
|
|
||||||
Ok(shared_secret)
|
Ok(shared_secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn secure_enclave_p256_sign(private_key: &str, content: &[u8]) -> XResult<Vec<u8>> {
|
pub fn secure_enclave_p256_sign(private_key: &str, content: &[u8], digest_type: DigestType) -> XResult<Vec<u8>> {
|
||||||
let private_key_representation = STANDARD.decode(private_key)?;
|
let private_key_representation = base64_decode(private_key)?;
|
||||||
let signature =
|
let signature = se_tool::private_key_sign_digested(&private_key_representation, content, digest_type)?;
|
||||||
swift_secure_enclave_tool_rs::private_key_ecdsa_sign(&private_key_representation, content)?;
|
|
||||||
Ok(signature)
|
Ok(signature)
|
||||||
}
|
}
|
||||||
33
src/util.rs
33
src/util.rs
@@ -1,9 +1,10 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use base64::{DecodeError, Engine};
|
|
||||||
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD};
|
||||||
|
use base64::{DecodeError, Engine};
|
||||||
use rust_util::XResult;
|
use rust_util::XResult;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
pub fn base64_encode<T: AsRef<[u8]>>(input: T) -> String {
|
pub fn base64_encode<T: AsRef<[u8]>>(input: T) -> String {
|
||||||
STANDARD.encode(input)
|
STANDARD.encode(input)
|
||||||
@@ -21,6 +22,21 @@ pub fn base64_uri_decode<T: AsRef<[u8]>>(input: T) -> Result<Vec<u8>, DecodeErro
|
|||||||
URL_SAFE_NO_PAD.decode(input)
|
URL_SAFE_NO_PAD.decode(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_pem(header: &str, bytes: &[u8]) -> String {
|
||||||
|
let mut buf = String::new();
|
||||||
|
buf.push_str(&format!("-----BEGIN {}-----\n", header));
|
||||||
|
let bas64ed = base64_encode(bytes);
|
||||||
|
let len = bas64ed.len();
|
||||||
|
for (i, c) in bas64ed.chars().enumerate() {
|
||||||
|
buf.push(c);
|
||||||
|
if i > 0 && i < len && i % 64 == 0 {
|
||||||
|
buf.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.push_str(&format!("\n-----END {}-----\n", header));
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_decode(input: &str) -> XResult<Vec<u8>> {
|
pub fn try_decode(input: &str) -> XResult<Vec<u8>> {
|
||||||
match hex::decode(input) {
|
match hex::decode(input) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
@@ -30,7 +46,7 @@ pub fn try_decode(input: &str) -> XResult<Vec<u8>> {
|
|||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(e) => simple_error!("decode hex or base64 error: {}", e),
|
Err(e) => simple_error!("decode hex or base64 error: {}", e),
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +61,17 @@ pub fn read_file_or_stdin(file: &str) -> XResult<Vec<u8>> {
|
|||||||
if file == "-" {
|
if file == "-" {
|
||||||
read_stdin()
|
read_stdin()
|
||||||
} else {
|
} else {
|
||||||
Ok(opt_result!(fs::read(file), "Read file: {} failed: {}", file))
|
Ok(opt_result!(
|
||||||
|
fs::read(file),
|
||||||
|
"Read file: {} failed: {}",
|
||||||
|
file
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn print_pretty_json<T>(value: &T)
|
||||||
|
where
|
||||||
|
T: ?Sized + Serialize,
|
||||||
|
{
|
||||||
|
println!("{}", serde_json::to_string_pretty(value).unwrap());
|
||||||
|
}
|
||||||
|
|||||||
58
src/yubikeyutil.rs
Normal file
58
src/yubikeyutil.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use crate::pivutil::slot_equals;
|
||||||
|
use clap::ArgMatches;
|
||||||
|
use rust_util::XResult;
|
||||||
|
use yubikey::piv::SlotId;
|
||||||
|
use yubikey::{Key, Serial, YubiKey};
|
||||||
|
|
||||||
|
pub fn open_yubikey_with_args(sub_arg_matches: &ArgMatches) -> XResult<YubiKey> {
|
||||||
|
let serial_opt = sub_arg_matches.value_of("serial");
|
||||||
|
open_yubikey_with_serial(&serial_opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_yubikey_with_serial(serial_opt: &Option<&str>) -> XResult<YubiKey> {
|
||||||
|
match serial_opt {
|
||||||
|
None => open_yubikey(),
|
||||||
|
Some(serial) => {
|
||||||
|
let serial_no: u32 = opt_result!(serial.parse(), "{}");
|
||||||
|
Ok(opt_result!(
|
||||||
|
YubiKey::open_by_serial(Serial(serial_no)),
|
||||||
|
"YubiKey with serial: {} not found: {}",
|
||||||
|
serial
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_yubikey() -> XResult<YubiKey> {
|
||||||
|
Ok(opt_result!(YubiKey::open(), "YubiKey not found: {}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_and_find_key(slot_id: &SlotId, sub_arg_matches: &ArgMatches) -> XResult<Option<Key>> {
|
||||||
|
let mut yk = open_yubikey_with_args(sub_arg_matches)?;
|
||||||
|
find_key(&mut yk, slot_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_key(yk: &mut YubiKey, slot_id: &SlotId) -> XResult<Option<Key>> {
|
||||||
|
match Key::list(yk) {
|
||||||
|
Err(e) => warning!("List keys failed: {}", e),
|
||||||
|
Ok(keys) => return Ok(filter_key(keys, slot_id)),
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_key_or_error(yk: &mut YubiKey, slot_id: &SlotId) -> XResult<Option<Key>> {
|
||||||
|
match Key::list(yk) {
|
||||||
|
Err(e) => simple_error!("List keys failed: {}", e),
|
||||||
|
Ok(keys) => Ok(filter_key(keys, slot_id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_key(keys: Vec<Key>, slot_id: &SlotId) -> Option<Key> {
|
||||||
|
for k in keys {
|
||||||
|
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
|
||||||
|
if slot_equals(slot_id, &slot_str) {
|
||||||
|
return Some(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user