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-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
+2
View File
@@ -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
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"]
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"
+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
[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
View File
@@ -1 +1 @@
1.56.0
1.60.0
+33 -10
View File
@@ -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];
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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,
})