diff --git a/Cargo.lock b/Cargo.lock index e2e4113..7b259da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,9 +213,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "base64ct" diff --git a/Cargo.toml b/Cargo.toml index da96a8b..e4aa43c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ age-core = { version = "0.9.0", path = "age-core" } # Dependencies required by the age specification: # - Base64 from RFC 4648 -base64 = "0.13" +base64 = "0.21" # - ChaCha20-Poly1305 from RFC 7539 chacha20poly1305 = { version = "0.10", default-features = false, features = ["alloc"] } diff --git a/age-core/src/format.rs b/age-core/src/format.rs index 260a0ef..b374dfe 100644 --- a/age-core/src/format.rs +++ b/age-core/src/format.rs @@ -1,5 +1,6 @@ //! Core types and encoding operations used by the age file format. +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use rand::{ distributions::{Distribution, Uniform}, thread_rng, RngCore, @@ -60,7 +61,7 @@ impl<'a> AgeStanza<'a> { data[full_chunks.len() * 64..].copy_from_slice(partial_chunk); // The chunks are guaranteed to contain Base64 characters by construction. - base64::decode_config(&data, base64::STANDARD_NO_PAD).unwrap() + BASE64_STANDARD_NO_PAD.decode(&data).unwrap() } } @@ -324,6 +325,7 @@ pub mod read { /// Encoding operations for age types. pub mod write { + use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use cookie_factory::{ combinator::string, multi::separated_list, @@ -336,7 +338,7 @@ pub mod write { use super::STANZA_TAG; fn wrapped_encoded_data<'a, W: 'a + Write>(data: &[u8]) -> impl SerializeFn + 'a { - let encoded = base64::encode_config(data, base64::STANDARD_NO_PAD); + let encoded = BASE64_STANDARD_NO_PAD.encode(data); move |mut w: WriteContext| { let mut s = encoded.as_str(); @@ -377,6 +379,7 @@ pub mod write { #[cfg(test)] mod tests { + use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use nom::error::ErrorKind; use super::{read, write}; @@ -385,11 +388,9 @@ mod tests { fn parse_age_stanza() { let test_tag = "X25519"; let test_args = &["CJM36AHmTbdHSuOQL+NESqyVQE75f2e610iRdLPEN20"]; - let test_body = base64::decode_config( - "C3ZAeY64NXS4QFrksLm3EGz+uPRyI0eQsWw7LWbbYig", - base64::STANDARD_NO_PAD, - ) - .unwrap(); + let test_body = BASE64_STANDARD_NO_PAD + .decode("C3ZAeY64NXS4QFrksLm3EGz+uPRyI0eQsWw7LWbbYig") + .unwrap(); // The only body line is short, so we don't need a trailing empty line. let test_stanza = "-> X25519 CJM36AHmTbdHSuOQL+NESqyVQE75f2e610iRdLPEN20 @@ -433,11 +434,9 @@ C3ZAeY64NXS4QFrksLm3EGz+uPRyI0eQsWw7LWbbYig fn age_stanza_with_full_body() { let test_tag = "full-body"; let test_args = &["some", "arguments"]; - let test_body = base64::decode_config( - "xD7o4VEOu1t7KZQ1gDgq2FPzBEeSRqbnqvQEXdLRYy143BxR6oFxsUUJCRB0ErXA", - base64::STANDARD_NO_PAD, - ) - .unwrap(); + let test_body = BASE64_STANDARD_NO_PAD + .decode("xD7o4VEOu1t7KZQ1gDgq2FPzBEeSRqbnqvQEXdLRYy143BxR6oFxsUUJCRB0ErXA") + .unwrap(); // The body fills a complete line, so it requires a trailing empty line. let test_stanza = "-> full-body some arguments @@ -460,11 +459,9 @@ xD7o4VEOu1t7KZQ1gDgq2FPzBEeSRqbnqvQEXdLRYy143BxR6oFxsUUJCRB0ErXA fn age_stanza_with_legacy_full_body() { let test_tag = "full-body"; let test_args = &["some", "arguments"]; - let test_body = base64::decode_config( - "xD7o4VEOu1t7KZQ1gDgq2FPzBEeSRqbnqvQEXdLRYy143BxR6oFxsUUJCRB0ErXA", - base64::STANDARD_NO_PAD, - ) - .unwrap(); + let test_body = BASE64_STANDARD_NO_PAD + .decode("xD7o4VEOu1t7KZQ1gDgq2FPzBEeSRqbnqvQEXdLRYy143BxR6oFxsUUJCRB0ErXA") + .unwrap(); // The body fills a complete line, but lacks a trailing empty line. let test_stanza = "-> full-body some arguments diff --git a/age-plugin/src/identity.rs b/age-plugin/src/identity.rs index 2e206d5..314e8a2 100644 --- a/age-plugin/src/identity.rs +++ b/age-plugin/src/identity.rs @@ -5,6 +5,7 @@ use age_core::{ plugin::{self, BidirSend, Connection}, secrecy::{ExposeSecret, SecretString}, }; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use bech32::FromBase32; use std::collections::HashMap; use std::io; @@ -71,7 +72,7 @@ impl<'a, 'b, R: io::Read, W: io::Write> Callbacks for BidirCallbacks<'a, let metadata: Vec<_> = Some(yes_string) .into_iter() .chain(no_string) - .map(|s| base64::encode_config(s, base64::STANDARD_NO_PAD)) + .map(|s| BASE64_STANDARD_NO_PAD.encode(s)) .collect(); let metadata: Vec<_> = metadata.iter().map(|s| s.as_str()).collect(); diff --git a/age-plugin/src/recipient.rs b/age-plugin/src/recipient.rs index 0a7bbee..6f55704 100644 --- a/age-plugin/src/recipient.rs +++ b/age-plugin/src/recipient.rs @@ -5,6 +5,7 @@ use age_core::{ plugin::{self, BidirSend, Connection}, secrecy::SecretString, }; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use bech32::FromBase32; use std::io; @@ -70,7 +71,7 @@ impl<'a, 'b, R: io::Read, W: io::Write> Callbacks for BidirCallbacks<'a, let metadata: Vec<_> = Some(yes_string) .into_iter() .chain(no_string) - .map(|s| base64::encode_config(s, base64::STANDARD_NO_PAD)) + .map(|s| BASE64_STANDARD_NO_PAD.encode(s)) .collect(); let metadata: Vec<_> = metadata.iter().map(|s| s.as_str()).collect(); diff --git a/age/CHANGELOG.md b/age/CHANGELOG.md index 3677d17..1618e9f 100644 --- a/age/CHANGELOG.md +++ b/age/CHANGELOG.md @@ -14,6 +14,7 @@ to 1.0.0 are beta releases. ### Changed - MSRV is now 1.65.0. +- Migrated to `base64 0.21`. ## [0.9.2] - 2023-06-12 ### Added diff --git a/age/src/format.rs b/age/src/format.rs index 404b4d7..43064f3 100644 --- a/age/src/format.rs +++ b/age/src/format.rs @@ -238,7 +238,9 @@ mod read { preceded( pair(tag(MAC_TAG), tag(b" ")), terminated( - map_opt(take(ENCODED_MAC_LENGTH), |tag| base64_arg(&tag, [0; 32])), + map_opt(take(ENCODED_MAC_LENGTH), |tag| { + base64_arg::<_, 32, 33>(&tag) + }), newline, ), ), diff --git a/age/src/plugin.rs b/age/src/plugin.rs index ab52254..0f8ca37 100644 --- a/age/src/plugin.rs +++ b/age/src/plugin.rs @@ -6,6 +6,7 @@ use age_core::{ plugin::{Connection, Reply, Response, IDENTITY_V1, RECIPIENT_V1}, secrecy::ExposeSecret, }; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use bech32::Variant; use i18n_embed_fl::fl; @@ -233,7 +234,7 @@ fn handle_confirm( .args .iter() .take(2) - .map(|s| base64::decode_config(s, base64::STANDARD_NO_PAD)); + .map(|s| BASE64_STANDARD_NO_PAD.decode(s)); let (yes_string, no_string) = match (strings.next(), strings.next()) { (None, _) => { errors.push(PluginError::Other { diff --git a/age/src/primitives/armor.rs b/age/src/primitives/armor.rs index 2f61467..b3a3852 100644 --- a/age/src/primitives/armor.rs +++ b/age/src/primitives/armor.rs @@ -1,5 +1,6 @@ //! I/O helper structs for the age ASCII armor format. +use base64::{prelude::BASE64_STANDARD, Engine}; use pin_project::pin_project; use std::cmp; use std::error; @@ -318,8 +319,9 @@ impl ArmoredWriter { .. } => { let byte_buf = byte_buf.unwrap(); - let encoded = - base64::encode_config_slice(&byte_buf, base64::STANDARD, &mut encoded_buf[..]); + let encoded = BASE64_STANDARD + .encode_slice(&byte_buf, &mut encoded_buf[..]) + .expect("byte_buf.len() <= BASE64_CHUNK_SIZE_BYTES"); inner.write_all(&encoded_buf[..encoded])?; inner.finish() } @@ -361,11 +363,9 @@ impl Write for ArmoredWriter { break; } else { assert_eq!( - base64::encode_config_slice( - &byte_buf, - base64::STANDARD, - &mut encoded_buf[..], - ), + BASE64_STANDARD + .encode_slice(&byte_buf, &mut encoded_buf[..]) + .expect("byte_buf.len() <= BASE64_CHUNK_SIZE_BYTES"), BASE64_CHUNK_SIZE_COLUMNS ); inner.write_all(&encoded_buf[..])?; @@ -461,11 +461,9 @@ impl AsyncWrite for ArmoredWriter { // line must be written in poll_close(). if !buf.is_empty() { assert_eq!( - base64::encode_config_slice( - &byte_buf, - base64::STANDARD, - &mut encoded_buf[..], - ), + BASE64_STANDARD + .encode_slice(&byte_buf, &mut encoded_buf[..],) + .expect("byte_buf.len() <= BASE64_CHUNK_SIZE_BYTES"), ARMORED_COLUMNS_PER_LINE ); *encoded_line = Some(EncodedBytes { @@ -509,8 +507,9 @@ impl AsyncWrite for ArmoredWriter { if let Some(byte_buf) = byte_buf { // Finish the armored format with a partial line (if necessary) and the end // marker. - let encoded = - base64::encode_config_slice(&byte_buf, base64::STANDARD, &mut encoded_buf[..]); + let encoded = BASE64_STANDARD + .encode_slice(&byte_buf, &mut encoded_buf[..]) + .expect("byte_buf.len() <= BASE64_CHUNK_SIZE_BYTES"); *encoded_line = Some(EncodedBytes { offset: 0, end: encoded, @@ -533,7 +532,7 @@ impl AsyncWrite for ArmoredWriter { #[derive(Debug)] pub enum ArmoredReadError { /// An error occurred while parsing Base64. - Base64(base64::DecodeError), + Base64(base64::DecodeSliceError), /// The begin marker for the armor is invalid. InvalidBeginMarker, /// Invalid UTF-8 characters were encountered between the begin and end marker. @@ -787,11 +786,9 @@ impl ArmoredReader { // Decode the line self.byte_start = 0; - self.byte_end = - base64::decode_config_slice(line.as_bytes(), base64::STANDARD, self.byte_buf.as_mut()) - .map_err(|e| { - io::Error::new(io::ErrorKind::InvalidData, ArmoredReadError::Base64(e)) - })?; + self.byte_end = BASE64_STANDARD + .decode_slice(line.as_bytes(), self.byte_buf.as_mut()) + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, ArmoredReadError::Base64(e)))?; // Finished with this buffered line! self.line_buf.clear(); diff --git a/age/src/scrypt.rs b/age/src/scrypt.rs index aacd98a..f2416ee 100644 --- a/age/src/scrypt.rs +++ b/age/src/scrypt.rs @@ -3,6 +3,7 @@ use age_core::{ primitives::{aead_decrypt, aead_encrypt}, secrecy::{ExposeSecret, SecretString}, }; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use rand::{rngs::OsRng, RngCore}; use std::time::Duration; use zeroize::Zeroize; @@ -94,7 +95,7 @@ impl crate::Recipient for Recipient { scrypt(&inner_salt, log_n, self.passphrase.expose_secret()).expect("log_n < 64"); let encrypted_file_key = aead_encrypt(&enc_key, file_key.expose_secret()); - let encoded_salt = base64::encode_config(salt, base64::STANDARD_NO_PAD); + let encoded_salt = BASE64_STANDARD_NO_PAD.encode(salt); Ok(vec![Stanza { tag: SCRYPT_RECIPIENT_TAG.to_owned(), @@ -118,7 +119,10 @@ impl<'a> crate::Identity for Identity<'a> { // Enforce valid and canonical stanza format. // https://c2sp.org/age#scrypt-recipient-stanza let (salt, log_n) = match &stanza.args[..] { - [salt, log_n] => match (base64_arg(salt, [0; SALT_LEN]), decimal_digit_arg(log_n)) { + [salt, log_n] => match ( + base64_arg::<_, SALT_LEN, 18>(salt), + decimal_digit_arg(log_n), + ) { (Some(salt), Some(log_n)) => (salt, log_n), _ => return Some(Err(DecryptError::InvalidHeader)), }, diff --git a/age/src/ssh/identity.rs b/age/src/ssh/identity.rs index 4413153..34d92af 100644 --- a/age/src/ssh/identity.rs +++ b/age/src/ssh/identity.rs @@ -3,6 +3,7 @@ use age_core::{ primitives::{aead_decrypt, hkdf}, secrecy::{ExposeSecret, Secret}, }; +use base64::prelude::BASE64_STANDARD; use i18n_embed_fl::fl; use nom::{ branch::alt, @@ -47,7 +48,7 @@ impl UnencryptedKey { pub(crate) fn unwrap_stanza(&self, stanza: &Stanza) -> Option> { match (self, stanza.tag.as_str()) { (UnencryptedKey::SshRsa(ssh_key, sk), SSH_RSA_RECIPIENT_TAG) => { - let tag = base64_arg(stanza.args.get(0)?, [0; TAG_LEN_BYTES])?; + let tag = base64_arg::<_, TAG_LEN_BYTES, 6>(stanza.args.get(0)?)?; if ssh_tag(ssh_key) != tag { return None; } @@ -72,7 +73,7 @@ impl UnencryptedKey { ) } (UnencryptedKey::SshEd25519(ssh_key, privkey), SSH_ED25519_RECIPIENT_TAG) => { - let tag = base64_arg(stanza.args.get(0)?, [0; TAG_LEN_BYTES])?; + let tag = base64_arg::<_, TAG_LEN_BYTES, 6>(stanza.args.get(0)?)?; if ssh_tag(ssh_key) != tag { return None; } @@ -81,7 +82,8 @@ impl UnencryptedKey { } let epk = - base64_arg(stanza.args.get(1)?, [0; crate::x25519::EPK_LEN_BYTES])?.into(); + base64_arg::<_, { crate::x25519::EPK_LEN_BYTES }, 33>(stanza.args.get(1)?)? + .into(); let sk: StaticSecret = { let mut sk = [0; 32]; @@ -316,7 +318,7 @@ fn rsa_privkey(input: &str) -> IResult<&str, Identity> { map_opt( pair( opt(terminated(rsa_pem_encryption_header, line_ending)), - wrapped_str_while_encoded(base64::STANDARD), + wrapped_str_while_encoded(BASE64_STANDARD), ), |(enc_header, privkey)| { if enc_header.is_some() { @@ -345,7 +347,7 @@ fn openssh_privkey(input: &str) -> IResult<&str, Identity> { preceded( pair(tag("-----BEGIN OPENSSH PRIVATE KEY-----"), line_ending), terminated( - map_opt(wrapped_str_while_encoded(base64::STANDARD), |privkey| { + map_opt(wrapped_str_while_encoded(BASE64_STANDARD), |privkey| { read_ssh::openssh_privkey(&privkey).ok().map(|(_, key)| key) }), pair(line_ending, tag("-----END OPENSSH PRIVATE KEY-----")), diff --git a/age/src/ssh/recipient.rs b/age/src/ssh/recipient.rs index 6964251..6b82cad 100644 --- a/age/src/ssh/recipient.rs +++ b/age/src/ssh/recipient.rs @@ -3,6 +3,10 @@ use age_core::{ primitives::{aead_encrypt, hkdf}, secrecy::ExposeSecret, }; +use base64::{ + prelude::{BASE64_STANDARD, BASE64_STANDARD_NO_PAD}, + Engine, +}; use curve25519_dalek::edwards::EdwardsPoint; use nom::{ branch::alt, @@ -74,10 +78,20 @@ impl fmt::Display for Recipient { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Recipient::SshRsa(ssh_key, _) => { - write!(f, "{} {}", SSH_RSA_KEY_PREFIX, base64::encode(ssh_key)) + write!( + f, + "{} {}", + SSH_RSA_KEY_PREFIX, + BASE64_STANDARD.encode(ssh_key) + ) } Recipient::SshEd25519(ssh_key, _) => { - write!(f, "{} {}", SSH_ED25519_KEY_PREFIX, base64::encode(ssh_key)) + write!( + f, + "{} {}", + SSH_ED25519_KEY_PREFIX, + BASE64_STANDARD.encode(ssh_key) + ) } } } @@ -127,7 +141,7 @@ impl crate::Recipient for Recipient { ) .expect("pubkey is valid and file key is not too long"); - let encoded_tag = base64::encode_config(ssh_tag(ssh_key), base64::STANDARD_NO_PAD); + let encoded_tag = BASE64_STANDARD_NO_PAD.encode(ssh_tag(ssh_key)); Ok(vec![Stanza { tag: SSH_RSA_RECIPIENT_TAG.to_owned(), @@ -158,8 +172,8 @@ impl crate::Recipient for Recipient { ); let encrypted_file_key = aead_encrypt(&enc_key, file_key.expose_secret()); - let encoded_tag = base64::encode_config(ssh_tag(ssh_key), base64::STANDARD_NO_PAD); - let encoded_epk = base64::encode_config(epk.as_bytes(), base64::STANDARD_NO_PAD); + let encoded_tag = BASE64_STANDARD_NO_PAD.encode(ssh_tag(ssh_key)); + let encoded_epk = BASE64_STANDARD_NO_PAD.encode(epk.as_bytes()); Ok(vec![Stanza { tag: SSH_ED25519_RECIPIENT_TAG.to_owned(), @@ -175,7 +189,7 @@ fn ssh_rsa_pubkey(input: &str) -> IResult<&str, ParsedRecipient> { preceded( pair(tag(SSH_RSA_KEY_PREFIX), tag(" ")), map_opt( - str_while_encoded(base64::STANDARD_NO_PAD), + str_while_encoded(BASE64_STANDARD_NO_PAD), |ssh_key| match read_ssh::rsa_pubkey(&ssh_key) { Ok((_, pk)) => Some(ParsedRecipient::Supported(Recipient::SshRsa(ssh_key, pk))), Err(_) => None, @@ -188,7 +202,7 @@ fn ssh_ed25519_pubkey(input: &str) -> IResult<&str, ParsedRecipient> { preceded( pair(tag(SSH_ED25519_KEY_PREFIX), tag(" ")), map_opt( - encoded_str(51, base64::STANDARD_NO_PAD), + encoded_str(51, BASE64_STANDARD_NO_PAD), |ssh_key| match read_ssh::ed25519_pubkey(&ssh_key) { Ok((_, pk)) => Some(ParsedRecipient::Supported(Recipient::SshEd25519( ssh_key, pk, @@ -206,7 +220,7 @@ fn ssh_ignore_pubkey(input: &str) -> IResult<&str, ParsedRecipient> { separated_pair( is_not(" "), tag(" "), - str_while_encoded(base64::STANDARD_NO_PAD), + str_while_encoded(BASE64_STANDARD_NO_PAD), ), |(key_type, ssh_key)| { read_ssh::string_tag(key_type)(&ssh_key) diff --git a/age/src/util.rs b/age/src/util.rs index dd97296..ed8ec28 100644 --- a/age/src/util.rs +++ b/age/src/util.rs @@ -18,6 +18,7 @@ pub(crate) fn parse_bech32(s: &str) -> Option<(String, Vec)> { pub(crate) mod read { use std::str::FromStr; + use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use nom::{character::complete::digit1, combinator::verify, ParseTo}; #[cfg(feature = "ssh")] @@ -32,7 +33,7 @@ pub(crate) mod read { #[cfg_attr(docsrs, doc(cfg(feature = "ssh")))] pub(crate) fn encoded_str( count: usize, - config: base64::Config, + engine: impl base64::Engine, ) -> impl Fn(&str) -> IResult<&str, Vec> { use nom::bytes::streaming::take; @@ -41,7 +42,7 @@ pub(crate) mod read { move |input: &str| { let (i, data) = take(encoded_count)(input)?; - match base64::decode_config(data, config) { + match engine.decode(data) { Ok(decoded) => Ok((i, decoded)), Err(_) => Err(nom::Err::Failure(make_error(input, ErrorKind::Eof))), } @@ -51,7 +52,7 @@ pub(crate) mod read { #[cfg(feature = "ssh")] #[cfg_attr(docsrs, doc(cfg(feature = "ssh")))] pub(crate) fn str_while_encoded( - config: base64::Config, + engine: impl base64::Engine, ) -> impl Fn(&str) -> IResult<&str, Vec> { use nom::bytes::complete::take_while1; @@ -61,9 +62,9 @@ pub(crate) mod read { let c = c as u8; // Substitute the character in twice after AA, so that padding // characters will also be detected as a valid if allowed. - base64::decode_config_slice([65, 65, c, c], config, &mut [0, 0, 0]).is_ok() + engine.decode_slice([65, 65, c, c], &mut [0, 0, 0]).is_ok() }), - |data| base64::decode_config(data, config), + |data| engine.decode(data), )(input) } } @@ -71,7 +72,7 @@ pub(crate) mod read { #[cfg(feature = "ssh")] #[cfg_attr(docsrs, doc(cfg(feature = "ssh")))] pub(crate) fn wrapped_str_while_encoded( - config: base64::Config, + engine: impl Engine, ) -> impl Fn(&str) -> IResult<&str, Vec> { use nom::{bytes::streaming::take_while1, character::streaming::line_ending}; @@ -83,25 +84,28 @@ pub(crate) mod read { let c = c as u8; // Substitute the character in twice after AA, so that padding // characters will also be detected as a valid if allowed. - base64::decode_config_slice([65, 65, c, c], config, &mut [0, 0, 0]).is_ok() + engine.decode_slice([65, 65, c, c], &mut [0, 0, 0]).is_ok() }), ), |chunks| { let data = chunks.join(""); - base64::decode_config(&data, config) + engine.decode(&data) }, )(input) } } - pub(crate) fn base64_arg, B: AsMut<[u8]>>(arg: &A, mut buf: B) -> Option { - if arg.as_ref().len() != ((4 * buf.as_mut().len()) + 2) / 3 { + pub(crate) fn base64_arg, const N: usize, const B: usize>( + arg: &A, + ) -> Option<[u8; N]> { + if N > B { return None; } - match base64::decode_config_slice(arg, base64::STANDARD_NO_PAD, buf.as_mut()) { - Ok(_) => Some(buf), - Err(_) => None, + let mut buf = [0; B]; + match BASE64_STANDARD_NO_PAD.decode_slice(arg, buf.as_mut()) { + Ok(n) if n == N => Some(buf[..N].try_into().unwrap()), + _ => None, } } @@ -114,11 +118,12 @@ pub(crate) mod read { } pub(crate) mod write { + use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use cookie_factory::{combinator::string, SerializeFn}; use std::io::Write; pub(crate) fn encoded_data(data: &[u8]) -> impl SerializeFn { - let encoded = base64::encode_config(data, base64::STANDARD_NO_PAD); + let encoded = BASE64_STANDARD_NO_PAD.encode(data); string(encoded) } } diff --git a/age/src/x25519.rs b/age/src/x25519.rs index 3006078..8333206 100644 --- a/age/src/x25519.rs +++ b/age/src/x25519.rs @@ -5,6 +5,7 @@ use age_core::{ primitives::{aead_decrypt, aead_encrypt, hkdf}, secrecy::{ExposeSecret, SecretString}, }; +use base64::{prelude::BASE64_STANDARD_NO_PAD, Engine}; use bech32::{ToBase32, Variant}; use rand_7::rngs::OsRng; use std::fmt; @@ -91,7 +92,7 @@ impl crate::Identity for Identity { // Enforce valid and canonical stanza format. // https://c2sp.org/age#x25519-recipient-stanza let ephemeral_share = match &stanza.args[..] { - [arg] => match base64_arg(arg, [0; EPK_LEN_BYTES]) { + [arg] => match base64_arg::<_, EPK_LEN_BYTES, 33>(arg) { Some(ephemeral_share) => ephemeral_share, None => return Some(Err(DecryptError::InvalidHeader)), }, @@ -211,7 +212,7 @@ impl crate::Recipient for Recipient { let enc_key = hkdf(&salt, X25519_RECIPIENT_KEY_LABEL, shared_secret.as_bytes()); let encrypted_file_key = aead_encrypt(&enc_key, file_key.expose_secret()); - let encoded_epk = base64::encode_config(epk.as_bytes(), base64::STANDARD_NO_PAD); + let encoded_epk = BASE64_STANDARD_NO_PAD.encode(epk.as_bytes()); Ok(vec![Stanza { tag: X25519_RECIPIENT_TAG.to_owned(), diff --git a/fuzz-afl/Cargo.lock b/fuzz-afl/Cargo.lock index fef71c2..3b4271c 100644 --- a/fuzz-afl/Cargo.lock +++ b/fuzz-afl/Cargo.lock @@ -107,9 +107,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bech32" diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 9e8ad18..bb1e108 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -82,9 +82,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.13.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bech32" diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 28eb693..eed9659 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -71,10 +71,6 @@ criteria = "safe-to-deploy" version = "0.7.3" criteria = "safe-to-run" -[[exemptions.base64]] -version = "0.13.1" -criteria = "safe-to-deploy" - [[exemptions.base64ct]] version = "1.6.0" criteria = "safe-to-deploy" diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 0c523e8..a165eea 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -196,6 +196,12 @@ Linux-specific constructs and does not constitute any major changes to the crate. """ +[[audits.bytecode-alliance.audits.base64]] +who = "Pat Hickey " +criteria = "safe-to-deploy" +version = "0.21.0" +notes = "This crate has no dependencies, no build.rs, and contains no unsafe code." + [[audits.bytecode-alliance.audits.block-buffer]] who = "Benjamin Bouvier " criteria = "safe-to-deploy" @@ -409,6 +415,16 @@ who = "Tim Geoghegan " criteria = "safe-to-deploy" delta = "0.10.1 -> 0.10.2" +[[audits.isrg.audits.base64]] +who = "Tim Geoghegan " +criteria = "safe-to-deploy" +delta = "0.21.0 -> 0.21.1" + +[[audits.isrg.audits.base64]] +who = "Brandon Pitman " +criteria = "safe-to-deploy" +delta = "0.21.1 -> 0.21.2" + [[audits.isrg.audits.block-buffer]] who = "David Cook " criteria = "safe-to-deploy"