@@ -25,7 +25,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.56.0
|
||||
toolchain: 1.60.0
|
||||
override: true
|
||||
- name: Install build dependencies
|
||||
run: sudo apt install ${{ matrix.build_deps }}
|
||||
@@ -46,14 +46,14 @@ jobs:
|
||||
args: --verbose
|
||||
|
||||
clippy:
|
||||
name: Clippy (1.56.0)
|
||||
name: Clippy (1.60.0)
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.56.0
|
||||
toolchain: 1.60.0
|
||||
components: clippy
|
||||
override: true
|
||||
- name: Install build dependencies
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Run clippy
|
||||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
name: Clippy (1.56.0)
|
||||
name: Clippy (1.60.0)
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features --all-targets -- -D warnings
|
||||
|
||||
@@ -117,7 +117,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.56.0
|
||||
toolchain: 1.60.0
|
||||
override: true
|
||||
- name: Install build dependencies
|
||||
run: sudo apt install libpcsclite-dev
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.56.0
|
||||
toolchain: 1.60.0
|
||||
components: rustfmt
|
||||
override: true
|
||||
- name: Check formatting
|
||||
|
||||
@@ -7,6 +7,8 @@ and this project adheres to Rust's notion of
|
||||
to 0.3.0 are beta releases.
|
||||
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
- MSRV is now 1.60.0.
|
||||
|
||||
## [0.3.2] - 2023-01-01
|
||||
### Changed
|
||||
|
||||
Generated
+693
-398
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -9,7 +9,7 @@ keywords = ["age", "cli", "encryption", "yubikey"]
|
||||
categories = ["command-line-utilities", "cryptography"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.56" # MSRV
|
||||
rust-version = "1.60" # MSRV
|
||||
|
||||
[package.metadata.deb]
|
||||
extended-description = """\
|
||||
@@ -22,24 +22,24 @@ assets = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
age-core = "0.8"
|
||||
age-plugin = "0.3"
|
||||
base64 = "0.13"
|
||||
bech32 = "0.8"
|
||||
age-core = "0.9"
|
||||
age-plugin = "0.4"
|
||||
base64 = "0.20"
|
||||
bech32 = "0.9"
|
||||
console = { version = "0.15", default-features = false }
|
||||
dialoguer = { version = "0.9", default-features = false, features = ["password"] }
|
||||
env_logger = "0.9"
|
||||
dialoguer = { version = "0.10", default-features = false, features = ["password"] }
|
||||
env_logger = "0.10"
|
||||
gumdrop = "0.8"
|
||||
hex = "0.4"
|
||||
log = "0.4"
|
||||
p256 = { version = "0.9", features = ["ecdh"] }
|
||||
p256 = { version = "0.11", features = ["ecdh"] }
|
||||
pcsc = "2.4"
|
||||
rand = "0.8"
|
||||
sha2 = "0.9"
|
||||
sha2 = "0.10"
|
||||
which = "4.1"
|
||||
x509 = "0.2"
|
||||
x509-parser = "0.12"
|
||||
yubikey = { version = "0.5", features = ["untested"] }
|
||||
x509-parser = "0.14"
|
||||
yubikey = { version = "0.7", features = ["untested"] }
|
||||
|
||||
# Translations
|
||||
i18n-embed = { version = "0.13", features = ["desktop-requester", "fluent-system"] }
|
||||
@@ -48,7 +48,7 @@ lazy_static = "1"
|
||||
rust-embed = "6"
|
||||
|
||||
# GnuPG coexistence
|
||||
sysinfo = ">=0.26, <0.26.4"
|
||||
sysinfo = "0.27"
|
||||
|
||||
[dev-dependencies]
|
||||
flate2 = "1"
|
||||
|
||||
@@ -9,7 +9,7 @@ which enables files to be encrypted to age identities stored on YubiKeys.
|
||||
On Windows, Linux, and macOS, you can use the
|
||||
[pre-built binaries](https://github.com/str4d/age-plugin-yubikey/releases).
|
||||
|
||||
If your system has Rust 1.56+ installed (either via `rustup` or a system
|
||||
If your system has Rust 1.60+ installed (either via `rustup` or a system
|
||||
package), you can build directly from source:
|
||||
|
||||
```
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1.56.0
|
||||
1.60.0
|
||||
|
||||
+33
-10
@@ -1,10 +1,14 @@
|
||||
use age_core::{
|
||||
format::{FileKey, Stanza},
|
||||
primitives::{aead_encrypt, hkdf},
|
||||
primitives::aead_encrypt,
|
||||
secrecy::ExposeSecret,
|
||||
};
|
||||
use p256::{ecdh::EphemeralSecret, elliptic_curve::sec1::ToEncodedPoint};
|
||||
use p256::{
|
||||
ecdh::EphemeralSecret,
|
||||
elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint},
|
||||
};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::{p256::Recipient, STANZA_TAG};
|
||||
|
||||
@@ -14,6 +18,14 @@ const TAG_BYTES: usize = 4;
|
||||
const EPK_BYTES: usize = 33;
|
||||
const ENCRYPTED_FILE_KEY_BYTES: usize = 32;
|
||||
|
||||
const STANDARD_NO_PAD: &base64::engine::fast_portable::FastPortable = {
|
||||
use base64::{
|
||||
alphabet::STANDARD,
|
||||
engine::fast_portable::{FastPortable, NO_PAD},
|
||||
};
|
||||
&FastPortable::from(&STANDARD, NO_PAD)
|
||||
};
|
||||
|
||||
/// The ephemeral key bytes in a piv-p256 stanza.
|
||||
///
|
||||
/// The bytes contain a compressed SEC-1 encoding of a valid point.
|
||||
@@ -23,7 +35,11 @@ pub(crate) struct EphemeralKeyBytes(p256::EncodedPoint);
|
||||
impl EphemeralKeyBytes {
|
||||
fn from_bytes(bytes: [u8; EPK_BYTES]) -> Option<Self> {
|
||||
let encoded = p256::EncodedPoint::from_bytes(&bytes).ok()?;
|
||||
if encoded.is_compressed() && encoded.decompress().is_some() {
|
||||
if encoded.is_compressed()
|
||||
&& p256::PublicKey::from_encoded_point(&encoded)
|
||||
.is_some()
|
||||
.into()
|
||||
{
|
||||
Some(EphemeralKeyBytes(encoded))
|
||||
} else {
|
||||
None
|
||||
@@ -39,9 +55,9 @@ impl EphemeralKeyBytes {
|
||||
}
|
||||
|
||||
pub(crate) fn decompress(&self) -> p256::EncodedPoint {
|
||||
self.0
|
||||
.decompress()
|
||||
.expect("EphemeralKeyBytes is a valid compressed encoding by construction")
|
||||
// EphemeralKeyBytes is a valid compressed encoding by construction.
|
||||
let p = p256::PublicKey::from_encoded_point(&self.0).unwrap();
|
||||
p.to_encoded_point(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +73,8 @@ impl From<RecipientLine> for Stanza {
|
||||
Stanza {
|
||||
tag: STANZA_TAG.to_owned(),
|
||||
args: vec![
|
||||
base64::encode_config(&r.tag, base64::STANDARD_NO_PAD),
|
||||
base64::encode_config(r.epk_bytes.as_bytes(), base64::STANDARD_NO_PAD),
|
||||
base64::encode_engine(&r.tag, STANDARD_NO_PAD),
|
||||
base64::encode_engine(r.epk_bytes.as_bytes(), STANDARD_NO_PAD),
|
||||
],
|
||||
body: r.encrypted_file_key.to_vec(),
|
||||
}
|
||||
@@ -76,7 +92,7 @@ impl RecipientLine {
|
||||
return None;
|
||||
}
|
||||
|
||||
base64::decode_config_slice(arg, base64::STANDARD_NO_PAD, buf.as_mut())
|
||||
base64::decode_engine_slice(arg, buf.as_mut(), STANDARD_NO_PAD)
|
||||
.ok()
|
||||
.map(|_| buf)
|
||||
}
|
||||
@@ -111,7 +127,14 @@ impl RecipientLine {
|
||||
salt.extend_from_slice(epk_bytes.as_bytes());
|
||||
salt.extend_from_slice(pk.to_encoded().as_bytes());
|
||||
|
||||
let enc_key = hkdf(&salt, STANZA_KEY_LABEL, shared_secret.as_bytes());
|
||||
let enc_key = {
|
||||
let mut okm = [0; 32];
|
||||
shared_secret
|
||||
.extract::<Sha256>(Some(&salt))
|
||||
.expand(STANZA_KEY_LABEL, &mut okm)
|
||||
.expect("okm is the correct length");
|
||||
okm
|
||||
};
|
||||
|
||||
let encrypted_file_key = {
|
||||
let mut key = [0; ENCRYPTED_FILE_KEY_BYTES];
|
||||
|
||||
@@ -247,6 +247,7 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> {
|
||||
yubikey_serial = yubikey.serial().to_string(),
|
||||
default_pin = DEFAULT_PIN,
|
||||
))
|
||||
.report(true)
|
||||
.interact()?;
|
||||
yubikey.verify_pin(pin.as_bytes())?;
|
||||
|
||||
|
||||
+15
-2
@@ -286,7 +286,7 @@ fn list(flags: PluginFlags, all: bool) -> Result<(), Error> {
|
||||
all,
|
||||
|_, recipient, metadata| {
|
||||
println!("{}", metadata);
|
||||
println!("{}", recipient.to_string());
|
||||
println!("{}", recipient);
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -372,6 +372,7 @@ fn main() -> Result<(), Error> {
|
||||
.with_prompt(fl!("cli-setup-select-yk"))
|
||||
.items(&reader_names)
|
||||
.default(0)
|
||||
.report(true)
|
||||
.interact_opt()?
|
||||
{
|
||||
Some(yk) => readers_list[yk].open()?,
|
||||
@@ -393,7 +394,11 @@ fn main() -> Result<(), Error> {
|
||||
x509_parser::parse_x509_certificate(key.certificate().as_ref())
|
||||
.unwrap();
|
||||
let (name, _) = util::extract_name(&cert, true).unwrap();
|
||||
let created = cert.validity().not_before.to_rfc2822();
|
||||
let created = cert
|
||||
.validity()
|
||||
.not_before
|
||||
.to_rfc2822()
|
||||
.unwrap_or_else(|e| format!("Invalid date: {}", e));
|
||||
|
||||
format!("{}, created: {}", name, created)
|
||||
})
|
||||
@@ -426,6 +431,7 @@ fn main() -> Result<(), Error> {
|
||||
.with_prompt(fl!("cli-setup-select-slot"))
|
||||
.items(&slots)
|
||||
.default(0)
|
||||
.report(true)
|
||||
.interact_opt()?
|
||||
{
|
||||
Some(slot) => {
|
||||
@@ -443,6 +449,7 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
if Confirm::new()
|
||||
.with_prompt(fl!("cli-setup-use-existing", slot_index = slot_index))
|
||||
.report(true)
|
||||
.interact()?
|
||||
{
|
||||
let stub = key::Stub::new(yubikey.serial(), slot, &recipient);
|
||||
@@ -462,6 +469,7 @@ fn main() -> Result<(), Error> {
|
||||
flags.name.as_deref().unwrap_or("age identity TAG_HEX")
|
||||
))
|
||||
.allow_empty(true)
|
||||
.report(true)
|
||||
.interact_text()?;
|
||||
|
||||
let pin_policy = match Select::new()
|
||||
@@ -479,6 +487,7 @@ fn main() -> Result<(), Error> {
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.report(true)
|
||||
.interact_opt()?
|
||||
{
|
||||
Some(0) => PinPolicy::Always,
|
||||
@@ -503,6 +512,7 @@ fn main() -> Result<(), Error> {
|
||||
})
|
||||
.unwrap(),
|
||||
)
|
||||
.report(true)
|
||||
.interact_opt()?
|
||||
{
|
||||
Some(0) => TouchPolicy::Always,
|
||||
@@ -514,6 +524,7 @@ fn main() -> Result<(), Error> {
|
||||
|
||||
if Confirm::new()
|
||||
.with_prompt(fl!("cli-setup-generate-new", slot_index = slot_index))
|
||||
.report(true)
|
||||
.interact()?
|
||||
{
|
||||
eprintln!();
|
||||
@@ -541,6 +552,7 @@ fn main() -> Result<(), Error> {
|
||||
"age-yubikey-identity-{}.txt",
|
||||
hex::encode(stub.tag)
|
||||
))
|
||||
.report(true)
|
||||
.interact_text()?;
|
||||
|
||||
let mut file = match OpenOptions::new()
|
||||
@@ -552,6 +564,7 @@ fn main() -> Result<(), Error> {
|
||||
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
|
||||
if Confirm::new()
|
||||
.with_prompt(fl!("cli-setup-identity-file-exists"))
|
||||
.report(true)
|
||||
.interact()?
|
||||
{
|
||||
File::create(&file_name)?
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ impl Recipient {
|
||||
/// This accepts both compressed (as used by the plugin) and uncompressed (as used in
|
||||
/// the YubiKey certificate) encodings.
|
||||
fn from_encoded(encoded: &p256::EncodedPoint) -> Option<Self> {
|
||||
p256::PublicKey::from_encoded_point(encoded).map(Recipient)
|
||||
Option::from(p256::PublicKey::from_encoded_point(encoded)).map(Recipient)
|
||||
}
|
||||
|
||||
/// Returns the compressed SEC-1 encoding of this recipient.
|
||||
|
||||
+9
-2
@@ -122,7 +122,10 @@ impl Metadata {
|
||||
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
|
||||
let policies = |c: &X509Certificate| {
|
||||
c.tbs_certificate
|
||||
.find_extension(&Oid::from(POLICY_EXTENSION_OID).unwrap())
|
||||
.get_extension_unique(&Oid::from(POLICY_EXTENSION_OID).unwrap())
|
||||
// If the extension is duplicated, we assume it is invalid.
|
||||
.ok()
|
||||
.flatten()
|
||||
// If the encoded extension doesn't have 2 bytes, we assume it is invalid.
|
||||
.filter(|policy| policy.value.len() >= 2)
|
||||
.map(|policy| {
|
||||
@@ -170,7 +173,11 @@ impl Metadata {
|
||||
serial: yubikey.serial(),
|
||||
slot,
|
||||
name,
|
||||
created: cert.validity().not_before.to_rfc2822(),
|
||||
created: cert
|
||||
.validity()
|
||||
.not_before
|
||||
.to_rfc2822()
|
||||
.unwrap_or_else(|e| format!("Invalid date: {}", e)),
|
||||
pin_policy,
|
||||
touch_policy,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user