Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

updated base64, bitflags, generic-array, p256, and jsonwebtoken #16

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ license = "MIT"
edition = "2021"

[dependencies]
base64 = "0.13"
bitflags = "1.2"
generic-array = "0.14"
jsonwebtoken = { version = "8.0", optional = true }
base64 = "0.22"
bitflags = "2.6"
generic-array = "1.1"
jsonwebtoken = { version = "9.3", optional = true }
num-bigint = { version = "0.4", optional = true }
p256 = { version = "0.10", optional = true, features = ["arithmetic"] }
p256 = { version = "0.13", optional = true, features = ["arithmetic"] }
rand = { version = "0.8", optional = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand All @@ -31,7 +31,7 @@ generate = ["p256", "rand"]
thumbprint = ["sha2"]

[dev-dependencies]
jsonwebtoken = "8.0"
jsonwebtoken = "9.3"

[package.metadata.docs.rs]
all-features = true
20 changes: 11 additions & 9 deletions src/byte_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,42 @@ use zeroize::{Zeroize, Zeroizing};
/// A zeroizing-on-drop container for a `[u8; N]` that deserializes from base64.
#[derive(Clone, PartialEq, Eq, Serialize)]
#[serde(transparent)]
pub struct ByteArray<N: ArrayLength<u8>>(
pub struct ByteArray<N: ArrayLength>(
#[serde(serialize_with = "crate::utils::serde_base64::serialize")] GenericArray<u8, N>,
);

impl<N: ArrayLength<u8>> std::fmt::Debug for ByteArray<N> {
impl<N: ArrayLength> std::fmt::Debug for ByteArray<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&crate::utils::base64_encode(&self.0))
}
}

impl<N: ArrayLength<u8>, T: Into<GenericArray<u8, N>>> From<T> for ByteArray<N> {
impl<N: ArrayLength, T: Into<GenericArray<u8, N>>> From<T> for ByteArray<N> {
fn from(arr: T) -> Self {
Self(arr.into())
}
}

impl<N: ArrayLength<u8>> Drop for ByteArray<N> {
impl<N: ArrayLength> Drop for ByteArray<N> {
fn drop(&mut self) {
Zeroize::zeroize(self.0.as_mut_slice())
}
}

impl<N: ArrayLength<u8>> AsRef<[u8]> for ByteArray<N> {
impl<N: ArrayLength> AsRef<[u8]> for ByteArray<N> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

impl<N: ArrayLength<u8>> std::ops::Deref for ByteArray<N> {
impl<N: ArrayLength> std::ops::Deref for ByteArray<N> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<N: ArrayLength<u8>> ByteArray<N> {
impl<N: ArrayLength> ByteArray<N> {
/// An unwrapping version of `try_from_slice`.
pub fn from_slice(bytes: impl AsRef<[u8]>) -> Self {
Self::try_from_slice(bytes).unwrap()
Expand All @@ -58,12 +58,14 @@ impl<N: ArrayLength<u8>> ByteArray<N> {
bytes.len()
))
} else {
Ok(Self(GenericArray::clone_from_slice(bytes)))
let generic_array = GenericArray::try_from_slice(bytes)
.map_err(|_| format!("expected {} bytes but got {}", N::USIZE, bytes.len()))?;
Ok(Self(generic_array.clone()))
}
}
}

impl<'de, N: ArrayLength<u8>> Deserialize<'de> for ByteArray<N> {
impl<'de, N: ArrayLength> Deserialize<'de> for ByteArray<N> {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let bytes = Zeroizing::new(crate::utils::serde_base64::deserialize(d)?);
Self::try_from_slice(&*bytes).map_err(|_| {
Expand Down
41 changes: 41 additions & 0 deletions src/key_ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use serde::{
de::{self, Deserialize, Deserializer},
ser::{Serialize, SerializeSeq, Serializer},
};
use std::fmt::Debug;

macro_rules! impl_key_ops {
($(($key_op:ident, $const_name:ident, $i:literal)),+,) => {
Expand All @@ -12,6 +13,32 @@ macro_rules! impl_key_ops {
}
}

impl Debug for KeyOps {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_set();
$(
if self.contains(KeyOps::$const_name) {
debug.entry(&stringify!($key_op));
}
)+
debug.finish()
}
}

impl Clone for KeyOps {
fn clone(&self) -> Self {
KeyOps::from_bits_truncate(self.bits())
}
}

impl std::cmp::Eq for KeyOps {}

impl std::cmp::PartialEq for KeyOps {
fn eq(&self, other: &Self) -> bool {
self.bits() == other.bits()
}
}

impl Serialize for KeyOps {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut seq = s.serialize_seq(Some(self.bits().count_ones() as usize))?;
Expand Down Expand Up @@ -71,4 +98,18 @@ mod tests {
let json = serde_json::to_string(&ops).unwrap();
assert_eq!(json, r#"["sign","deriveBits"]"#)
}

#[test]
fn debug() {
let ops = KeyOps::SIGN | KeyOps::ENCRYPT | KeyOps::DERIVE_BITS;
let debug_str = format!("{:?}", ops);
assert_eq!(debug_str, r#"{"sign", "encrypt", "deriveBits"}"#);
}

#[test]
fn clone_eq() {
let ops = KeyOps::SIGN | KeyOps::VERIFY;
let cloned_ops = ops.clone();
assert_eq!(ops, cloned_ops);
}
}
15 changes: 9 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ impl JsonWebKey {
}

pub fn set_algorithm(&mut self, alg: Algorithm) -> Result<(), Error> {
Self::validate_algorithm(alg, &*self.key)?;
Self::validate_algorithm(alg, &self.key)?;
self.algorithm = Some(alg);
Ok(())
}
Expand Down Expand Up @@ -180,7 +180,7 @@ impl std::str::FromStr for JsonWebKey {
Some(alg) => alg,
None => return Ok(jwk),
};
Self::validate_algorithm(alg, &*jwk.key).map(|_| jwk)
Self::validate_algorithm(alg, &jwk.key).map(|_| jwk)
}
}

Expand Down Expand Up @@ -337,6 +337,7 @@ impl Key {
Some(private_point) => {
pkcs8::write_private(oids, |writer: &mut DERWriterSeq<'_>| {
writer.next().write_i8(1); // version
#[allow(clippy::explicit_auto_deref)]
writer.next().write_bytes(&**private_point);
// The following tagged value is optional. OpenSSL produces it,
// but many tools, including jwt.io and `jsonwebtoken`, don't like it,
Expand Down Expand Up @@ -411,8 +412,9 @@ impl Key {
/// If this key is asymmetric, encodes it as PKCS#8 with PEM armoring.
#[cfg(feature = "pkcs-convert")]
pub fn try_to_pem(&self) -> Result<String, ConversionError> {
use base64::Engine;
use std::fmt::Write;
let der_b64 = base64::encode(self.try_to_der()?);
let der_b64 = base64::engine::general_purpose::STANDARD.encode(self.try_to_der()?);
let key_ty = if self.is_private() {
"PRIVATE"
} else {
Expand Down Expand Up @@ -469,7 +471,7 @@ impl Key {

Self::EC {
curve: Curve::P256,
d: Some(sk_scalar.to_bytes().into()),
d: Some(ByteArray::from_slice(sk_scalar.to_bytes())),
x: ByteArray::from_slice(x_bytes),
y: ByteArray::from_slice(y_bytes),
}
Expand Down Expand Up @@ -525,7 +527,7 @@ impl<'de> Deserialize<'de> for PublicExponent {
if e == PUBLIC_EXPONENT_B64 || e == PUBLIC_EXPONENT_B64_PADDED {
Ok(Self)
} else {
Err(serde::de::Error::custom(&format!(
Err(serde::de::Error::custom(format!(
"public exponent must be {}",
PUBLIC_EXPONENT
)))
Expand Down Expand Up @@ -635,7 +637,8 @@ const _IMPL_JWT_CONVERSIONS: () = {
.unwrap()
}
Self::RSA { .. } => {
jwt::DecodingKey::from_rsa_pem(self.to_pem().as_bytes()).unwrap()
jwt::DecodingKey::from_rsa_pem(self.to_public().unwrap().to_pem().as_bytes())
.unwrap()
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ fn deserialize_es256() {
key: Box::new(Key::EC {
// The parameters were decoded using a 10-liner Rust script.
curve: Curve::P256,
d: Some(ByteArray::from_slice(&[
d: Some(ByteArray::from_slice([
102, 130, 144, 246, 62, 29, 132, 128, 101, 49, 21, 107, 191, 228, 6, 240, 255,
211, 246, 203, 173, 191, 127, 253, 229, 232, 168, 244, 203, 105, 128, 168
])),
x: ByteArray::from_slice(&[
x: ByteArray::from_slice([
64, 227, 7, 154, 255, 122, 181, 89, 73, 191, 235, 141, 170, 154, 231, 13, 34,
136, 143, 144, 34, 45, 53, 202, 70, 137, 151, 98, 118, 175, 208, 221
]),
y: ByteArray::from_slice(&[
y: ByteArray::from_slice([
78, 54, 25, 160, 121, 220, 181, 171, 68, 19, 163, 66, 172, 169, 151, 65, 210,
73, 62, 115, 115, 100, 69, 252, 156, 25, 153, 117, 237, 192, 99, 137
])
Expand All @@ -78,8 +78,8 @@ fn serialize_es256() {
key: Box::new(Key::EC {
curve: Curve::P256,
d: None,
x: ByteArray::from_slice(&[1u8; 32]),
y: ByteArray::from_slice(&[2u8; 32]),
x: ByteArray::from_slice([1u8; 32]),
y: ByteArray::from_slice([2u8; 32]),
}),
key_id: None,
algorithm: None,
Expand Down
9 changes: 5 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use base64::engine::Engine;
use serde::{
de::{self, Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
use zeroize::Zeroizing;

fn base64_config() -> base64::Config {
base64::Config::new(base64::CharacterSet::UrlSafe, false /* pad */)
fn base64_engine() -> base64::engine::GeneralPurpose {
base64::engine::general_purpose::URL_SAFE_NO_PAD
}

pub(crate) fn base64_encode(bytes: impl AsRef<[u8]>) -> String {
base64::encode_config(bytes, base64_config())
base64_engine().encode(bytes)
}

fn base64_decode(b64: impl AsRef<[u8]>) -> Result<Vec<u8>, base64::DecodeError> {
base64::decode_config(b64, base64_config())
base64_engine().decode(b64)
}

pub(crate) mod serde_base64 {
Expand All @@ -28,7 +29,7 @@

pub(crate) fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
let base64_str = Zeroizing::new(String::deserialize(d)?);
base64_decode(&*base64_str).map_err(|e| {

Check warning on line 32 in src/utils.rs

View workflow job for this annotation

GitHub Actions / build-release

unused variable: `e`
#[cfg(debug_assertions)]
let err_msg = e.to_string().to_lowercase();
#[cfg(not(debug_assertions))]
Expand Down
Loading