Merge pull request #88 from str4d/msrv-1.60

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