mirror of
https://github.com/BitskiCo/jwk-rs
synced 2024-11-21 19:49:20 +00:00
Use stable Rust
This commit is contained in:
parent
8014d18bf0
commit
9592cad01c
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: nightly
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
components: rustfmt, clippy
|
components: rustfmt, clippy
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ jobs:
|
|||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: nightly
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- name: Build (release)
|
- name: Build (release)
|
||||||
@ -58,7 +58,7 @@ jobs:
|
|||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: nightly
|
toolchain: stable
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- uses: actions-rs/tarpaulin@v0.1
|
- uses: actions-rs/tarpaulin@v0.1
|
||||||
|
36
Cargo.toml
36
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "jsonwebkey"
|
name = "jsonwebkey"
|
||||||
version = "0.2.0"
|
version = "0.3.0"
|
||||||
authors = ["Nick Hynes <nhynes@nhynes.com>"]
|
authors = ["Nick Hynes <nhynes@nhynes.com>"]
|
||||||
description = "JSON Web Key (JWK) (de)serialization, generation, and conversion."
|
description = "JSON Web Key (JWK) (de)serialization, generation, and conversion."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@ -9,24 +9,28 @@ license = "MIT"
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.12"
|
base64 = "0.12"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
jsonwebtoken = { version = "7.2", optional = true }
|
generic-array = "0.14"
|
||||||
num-bigint = { version = "0.2", optional = true }
|
jsonwebtoken = { version = "7.2", optional = true }
|
||||||
p256 = { version = "0.3", optional = true }
|
num-bigint = { version = "0.2", optional = true }
|
||||||
rand = { version = "0.7", optional = true }
|
p256 = { version = "0.3", optional = true }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
rand = { version = "0.7", optional = true }
|
||||||
serde_json = "1.0"
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
syn = { version = "1.0", features = ["full"] } # required to parse const generics
|
serde_json = "1.0"
|
||||||
thiserror = "1.0"
|
syn = { version = "1.0", features = ["full"] } # required to parse const generics
|
||||||
yasna = { version = "0.3", optional = true, features = ["num-bigint"] }
|
thiserror = "1.0"
|
||||||
zeroize = { version = "1.1", features = ["zeroize_derive"] }
|
yasna = { version = "0.3", optional = true, features = ["num-bigint"] }
|
||||||
|
zeroize = { version = "1.1", features = ["zeroize_derive"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
pkcs-convert = ["num-bigint", "yasna"]
|
pkcs-convert = ["num-bigint", "yasna"]
|
||||||
jwt-convert = ["pkcs-convert", "jsonwebtoken"]
|
jwt-convert = ["pkcs-convert", "jsonwebtoken"]
|
||||||
generate = ["p256", "rand"]
|
generate = ["p256", "rand"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
jsonwebtoken = "7.2"
|
jsonwebtoken = "7.2"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
*[JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517#section-4.3) (de)serialization, generation, and conversion.*
|
*[JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517#section-4.3) (de)serialization, generation, and conversion.*
|
||||||
|
|
||||||
Note: requires rustc nightly >= 1.45 for conveniences around fixed-size arrays.
|
|
||||||
|
|
||||||
**Goals**
|
**Goals**
|
||||||
|
|
||||||
tl;dr: get keys into a format that can be used by other crates; be as safe as possible while doing so.
|
tl;dr: get keys into a format that can be used by other crates; be as safe as possible while doing so.
|
||||||
|
@ -1,61 +1,55 @@
|
|||||||
use std::{array::FixedSizeArray, fmt};
|
use derive_more::{AsRef, Deref};
|
||||||
|
use generic_array::{ArrayLength, GenericArray};
|
||||||
use derive_more::{AsRef, Deref, From};
|
use serde::de::{self, Deserialize, Deserializer};
|
||||||
use serde::{
|
|
||||||
de::{self, Deserialize, Deserializer},
|
|
||||||
ser::{Serialize, Serializer},
|
|
||||||
};
|
|
||||||
use zeroize::{Zeroize, Zeroizing};
|
use zeroize::{Zeroize, Zeroizing};
|
||||||
|
|
||||||
use crate::utils::{deserialize_base64, serialize_base64};
|
|
||||||
|
|
||||||
/// A zeroizing-on-drop container for a `[u8; N]` that deserializes from base64.
|
/// A zeroizing-on-drop container for a `[u8; N]` that deserializes from base64.
|
||||||
#[derive(Clone, Zeroize, Deref, AsRef, From)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deref, AsRef)]
|
||||||
#[zeroize(drop)]
|
#[serde(transparent)]
|
||||||
pub struct ByteArray<const N: usize>(pub [u8; N]);
|
pub struct ByteArray<N: ArrayLength<u8>>(
|
||||||
|
#[serde(serialize_with = "crate::utils::serde_base64::serialize")] pub GenericArray<u8, N>,
|
||||||
|
);
|
||||||
|
|
||||||
impl<const N: usize> fmt::Debug for ByteArray<N> {
|
impl<N: ArrayLength<u8>, T: Into<GenericArray<u8, N>>> From<T> for ByteArray<N> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn from(arr: T) -> Self {
|
||||||
if cfg!(debug_assertions) {
|
Self(arr.into())
|
||||||
write!(f, "{}", base64::encode(self.0.as_slice()))
|
|
||||||
} else {
|
|
||||||
write!(f, "ByteArray<{}>", N)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> PartialEq for ByteArray<N> {
|
impl<N: ArrayLength<u8>> Drop for ByteArray<N> {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn drop(&mut self) {
|
||||||
self.0.as_slice() == other.0.as_slice()
|
Zeroize::zeroize(self.0.as_mut_slice())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Eq for ByteArray<N> {}
|
impl<N: ArrayLength<u8>> ByteArray<N> {
|
||||||
|
/// An unwrapping version of `try_from_slice`.
|
||||||
|
pub fn from_slice(bytes: impl AsRef<[u8]>) -> Self {
|
||||||
|
Self::try_from_slice(bytes).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
impl<const N: usize> ByteArray<N> {
|
|
||||||
pub fn try_from_slice(bytes: impl AsRef<[u8]>) -> Result<Self, String> {
|
pub fn try_from_slice(bytes: impl AsRef<[u8]>) -> Result<Self, String> {
|
||||||
let mut arr = Self([0u8; N]);
|
|
||||||
let bytes = bytes.as_ref();
|
let bytes = bytes.as_ref();
|
||||||
if bytes.len() != N {
|
if bytes.len() != N::USIZE {
|
||||||
Err(format!("expected {} bytes but got {}", N, bytes.len()))
|
Err(format!(
|
||||||
|
"expected {} bytes but got {}",
|
||||||
|
N::USIZE,
|
||||||
|
bytes.len()
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
arr.0.copy_from_slice(bytes);
|
Ok(Self(GenericArray::clone_from_slice(bytes)))
|
||||||
Ok(arr)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Serialize for ByteArray<N> {
|
impl<'de, N: ArrayLength<u8>> Deserialize<'de> for ByteArray<N> {
|
||||||
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
|
||||||
serialize_base64(self.0.as_slice(), s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de, const N: usize> Deserialize<'de> for ByteArray<N> {
|
|
||||||
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||||
let bytes = Zeroizing::new(deserialize_base64(d)?);
|
let bytes = Zeroizing::new(crate::utils::serde_base64::deserialize(d)?);
|
||||||
Self::try_from_slice(&*bytes).map_err(|_| {
|
Self::try_from_slice(&*bytes).map_err(|_| {
|
||||||
de::Error::invalid_length(bytes.len(), &format!("{} base64-encoded bytes", N).as_str())
|
de::Error::invalid_length(
|
||||||
|
bytes.len(),
|
||||||
|
&format!("{} base64-encoded bytes", N::USIZE).as_str(),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,6 +58,8 @@ impl<'de, const N: usize> Deserialize<'de> for ByteArray<N> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use generic_array::typenum::*;
|
||||||
|
|
||||||
static BYTES: &[u8] = &[1, 2, 3, 4, 5, 6, 7];
|
static BYTES: &[u8] = &[1, 2, 3, 4, 5, 6, 7];
|
||||||
static BASE64_JSON: &str = "\"AQIDBAUGBw==\"";
|
static BASE64_JSON: &str = "\"AQIDBAUGBw==\"";
|
||||||
|
|
||||||
@ -73,26 +69,26 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde_byte_array_good() {
|
fn test_serde_byte_array_good() {
|
||||||
let arr = ByteArray::<7>::try_from_slice(BYTES).unwrap();
|
let arr = ByteArray::<U7>::try_from_slice(BYTES).unwrap();
|
||||||
let b64 = serde_json::to_string(&arr).unwrap();
|
let b64 = serde_json::to_string(&arr).unwrap();
|
||||||
assert_eq!(b64, BASE64_JSON);
|
assert_eq!(b64, BASE64_JSON);
|
||||||
let bytes: ByteArray<7> = serde_json::from_str(&b64).unwrap();
|
let bytes: ByteArray<U7> = serde_json::from_str(&b64).unwrap();
|
||||||
assert_eq!(bytes.as_ref(), BYTES);
|
assert_eq!(bytes.as_slice(), BYTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde_deserialize_byte_array_invalid() {
|
fn test_serde_deserialize_byte_array_invalid() {
|
||||||
let mut de = serde_json::Deserializer::from_str("\"Z\"");
|
let mut de = serde_json::Deserializer::from_str("\"Z\"");
|
||||||
ByteArray::<0>::deserialize(&mut de).unwrap_err();
|
ByteArray::<U0>::deserialize(&mut de).unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde_base64_deserialize_array_long() {
|
fn test_serde_base64_deserialize_array_long() {
|
||||||
ByteArray::<6>::deserialize(&mut get_de()).unwrap_err();
|
ByteArray::<U6>::deserialize(&mut get_de()).unwrap_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serde_base64_deserialize_array_short() {
|
fn test_serde_base64_deserialize_array_short() {
|
||||||
ByteArray::<8>::deserialize(&mut get_de()).unwrap_err();
|
ByteArray::<U8>::deserialize(&mut get_de()).unwrap_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,13 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use derive_more::{AsRef, Deref, From};
|
use derive_more::{AsRef, Deref, From};
|
||||||
use serde::{
|
|
||||||
de::{Deserialize, Deserializer},
|
|
||||||
ser::{Serialize, Serializer},
|
|
||||||
};
|
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
use crate::utils::{deserialize_base64, serialize_base64};
|
|
||||||
|
|
||||||
/// A zeroizing-on-drop container for a `Vec<u8>` that deserializes from base64.
|
/// A zeroizing-on-drop container for a `Vec<u8>` that deserializes from base64.
|
||||||
#[derive(Clone, PartialEq, Eq, Zeroize, Deref, AsRef, From)]
|
#[derive(Clone, PartialEq, Eq, Zeroize, Serialize, Deserialize, Deref, AsRef, From)]
|
||||||
#[zeroize(drop)]
|
#[zeroize(drop)]
|
||||||
pub struct ByteVec(pub Vec<u8>);
|
#[serde(transparent)]
|
||||||
|
pub struct ByteVec(#[serde(with = "crate::utils::serde_base64")] pub Vec<u8>);
|
||||||
|
|
||||||
impl fmt::Debug for ByteVec {
|
impl fmt::Debug for ByteVec {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
@ -24,18 +19,6 @@ impl fmt::Debug for ByteVec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Serialize for ByteVec {
|
|
||||||
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
|
||||||
serialize_base64(&self.0, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ByteVec {
|
|
||||||
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
|
||||||
Ok(Self(deserialize_base64(d)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
36
src/lib.rs
36
src/lib.rs
@ -1,5 +1,5 @@
|
|||||||
#![allow(incomplete_features)]
|
#![deny(rust_2018_idioms, unreachable_pub)]
|
||||||
#![feature(box_syntax, const_generics, fixed_size_array)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
//! *[JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517#section-4.3) (de)serialization, generation, and conversion.*
|
//! *[JSON Web Key (JWK)](https://tools.ietf.org/html/rfc7517#section-4.3) (de)serialization, generation, and conversion.*
|
||||||
//!
|
//!
|
||||||
@ -58,6 +58,9 @@
|
|||||||
//! This pulls in the [p256](https://crates.io/crates/p256) and [rand](https://crates.io/crates/rand) crates.
|
//! This pulls in the [p256](https://crates.io/crates/p256) and [rand](https://crates.io/crates/rand) crates.
|
||||||
//! * `jsonwebtoken` - enables conversions to types in the [jsonwebtoken](https://crates.io/crates/jsonwebtoken) crate.
|
//! * `jsonwebtoken` - enables conversions to types in the [jsonwebtoken](https://crates.io/crates/jsonwebtoken) crate.
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde;
|
||||||
|
|
||||||
mod byte_array;
|
mod byte_array;
|
||||||
mod byte_vec;
|
mod byte_vec;
|
||||||
mod key_ops;
|
mod key_ops;
|
||||||
@ -67,6 +70,7 @@ mod utils;
|
|||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use generic_array::typenum::U32;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use byte_array::ByteArray;
|
pub use byte_array::ByteArray;
|
||||||
@ -94,7 +98,7 @@ pub struct JsonWebKey {
|
|||||||
impl JsonWebKey {
|
impl JsonWebKey {
|
||||||
pub fn new(key: Key) -> Self {
|
pub fn new(key: Key) -> Self {
|
||||||
Self {
|
Self {
|
||||||
key: box key,
|
key: Box::new(key),
|
||||||
key_use: None,
|
key_use: None,
|
||||||
key_ops: KeyOps::empty(),
|
key_ops: KeyOps::empty(),
|
||||||
key_id: None,
|
key_id: None,
|
||||||
@ -143,7 +147,7 @@ impl std::str::FromStr for JsonWebKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for JsonWebKey {
|
impl std::fmt::Display for JsonWebKey {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
if f.alternate() {
|
if f.alternate() {
|
||||||
write!(f, "{}", serde_json::to_string_pretty(self).unwrap())
|
write!(f, "{}", serde_json::to_string_pretty(self).unwrap())
|
||||||
} else {
|
} else {
|
||||||
@ -179,7 +183,7 @@ pub enum Key {
|
|||||||
impl Key {
|
impl Key {
|
||||||
/// Returns true iff this key only contains private components (i.e. a private asymmetric
|
/// Returns true iff this key only contains private components (i.e. a private asymmetric
|
||||||
/// key or a symmetric key).
|
/// key or a symmetric key).
|
||||||
fn is_private(&self) -> bool {
|
pub fn is_private(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Symmetric { .. }
|
Self::Symmetric { .. }
|
||||||
| Self::EC {
|
| Self::EC {
|
||||||
@ -194,7 +198,7 @@ impl Key {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the public part of this key (symmetric keys have no public parts).
|
/// Returns the public part of this key (symmetric keys have no public parts).
|
||||||
pub fn to_public(&self) -> Option<Cow<Self>> {
|
pub fn to_public(&self) -> Option<Cow<'_, Self>> {
|
||||||
if !self.is_private() {
|
if !self.is_private() {
|
||||||
return Some(Cow::Borrowed(self));
|
return Some(Cow::Borrowed(self));
|
||||||
}
|
}
|
||||||
@ -236,7 +240,7 @@ impl Key {
|
|||||||
let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
|
let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
|
||||||
let oids = &[Some(&ec_public_oid), Some(&prime256v1_oid)];
|
let oids = &[Some(&ec_public_oid), Some(&prime256v1_oid)];
|
||||||
|
|
||||||
let write_public = |writer: DERWriter| {
|
let write_public = |writer: DERWriter<'_>| {
|
||||||
let public_bytes: Vec<u8> = [0x04 /* uncompressed */]
|
let public_bytes: Vec<u8> = [0x04 /* uncompressed */]
|
||||||
.iter()
|
.iter()
|
||||||
.chain(x.iter())
|
.chain(x.iter())
|
||||||
@ -248,7 +252,7 @@ impl Key {
|
|||||||
|
|
||||||
match d {
|
match d {
|
||||||
Some(private_point) => {
|
Some(private_point) => {
|
||||||
pkcs8::write_private(oids, |writer: &mut DERWriterSeq| {
|
pkcs8::write_private(oids, |writer: &mut DERWriterSeq<'_>| {
|
||||||
writer.next().write_i8(1); // version
|
writer.next().write_i8(1); // version
|
||||||
writer.next().write_bytes(&**private_point);
|
writer.next().write_bytes(&**private_point);
|
||||||
// The following tagged value is optional. OpenSSL produces it,
|
// The following tagged value is optional. OpenSSL produces it,
|
||||||
@ -268,17 +272,17 @@ impl Key {
|
|||||||
1, 2, 840, 113549, 1, 1, 1, // rsaEncryption
|
1, 2, 840, 113549, 1, 1, 1, // rsaEncryption
|
||||||
]);
|
]);
|
||||||
let oids = &[Some(&rsa_encryption_oid), None];
|
let oids = &[Some(&rsa_encryption_oid), None];
|
||||||
let write_bytevec = |writer: DERWriter, vec: &ByteVec| {
|
let write_bytevec = |writer: DERWriter<'_>, vec: &ByteVec| {
|
||||||
let bigint = BigUint::from_bytes_be(vec.as_slice());
|
let bigint = BigUint::from_bytes_be(vec.as_slice());
|
||||||
writer.write_biguint(&bigint);
|
writer.write_biguint(&bigint);
|
||||||
};
|
};
|
||||||
|
|
||||||
let write_public = |writer: &mut DERWriterSeq| {
|
let write_public = |writer: &mut DERWriterSeq<'_>| {
|
||||||
write_bytevec(writer.next(), &public.n);
|
write_bytevec(writer.next(), &public.n);
|
||||||
writer.next().write_u32(PUBLIC_EXPONENT);
|
writer.next().write_u32(PUBLIC_EXPONENT);
|
||||||
};
|
};
|
||||||
|
|
||||||
let write_private = |writer: &mut DERWriterSeq, private: &RsaPrivate| {
|
let write_private = |writer: &mut DERWriterSeq<'_>, private: &RsaPrivate| {
|
||||||
// https://tools.ietf.org/html/rfc3447#appendix-A.1.2
|
// https://tools.ietf.org/html/rfc3447#appendix-A.1.2
|
||||||
writer.next().write_i8(0); // version (two-prime)
|
writer.next().write_i8(0); // version (two-prime)
|
||||||
write_public(writer);
|
write_public(writer);
|
||||||
@ -389,8 +393,8 @@ impl Key {
|
|||||||
Self::EC {
|
Self::EC {
|
||||||
curve: Curve::P256 {
|
curve: Curve::P256 {
|
||||||
d: Some(sk_scalar.to_bytes().into()),
|
d: Some(sk_scalar.to_bytes().into()),
|
||||||
x: ByteArray::try_from_slice(x_bytes).unwrap(),
|
x: ByteArray::from_slice(x_bytes),
|
||||||
y: ByteArray::try_from_slice(y_bytes).unwrap(),
|
y: ByteArray::from_slice(y_bytes),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,11 +408,11 @@ pub enum Curve {
|
|||||||
P256 {
|
P256 {
|
||||||
/// The private scalar.
|
/// The private scalar.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
d: Option<ByteArray<32>>,
|
d: Option<ByteArray<U32>>,
|
||||||
/// The curve point x coordinate.
|
/// The curve point x coordinate.
|
||||||
x: ByteArray<32>,
|
x: ByteArray<U32>,
|
||||||
/// The curve point y coordinate.
|
/// The curve point y coordinate.
|
||||||
y: ByteArray<32>,
|
y: ByteArray<U32>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
53
src/tests.rs
53
src/tests.rs
@ -2,6 +2,8 @@ use super::*;
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::byte_array::ByteArray;
|
||||||
|
|
||||||
// Generated using https://mkjwk.org
|
// Generated using https://mkjwk.org
|
||||||
static P256_JWK_FIXTURE: &str = r#"{
|
static P256_JWK_FIXTURE: &str = r#"{
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
@ -40,29 +42,24 @@ fn deserialize_es256() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
jwk,
|
jwk,
|
||||||
JsonWebKey {
|
JsonWebKey {
|
||||||
key: box Key::EC {
|
key: Box::new(Key::EC {
|
||||||
// The parameters were decoded using a 10-liner Rust script.
|
// The parameters were decoded using a 10-liner Rust script.
|
||||||
curve: Curve::P256 {
|
curve: Curve::P256 {
|
||||||
d: Some(
|
d: Some(ByteArray::from_slice(&[
|
||||||
[
|
102, 130, 144, 246, 62, 29, 132, 128, 101, 49, 21, 107, 191, 228, 6, 240,
|
||||||
102, 130, 144, 246, 62, 29, 132, 128, 101, 49, 21, 107, 191, 228, 6,
|
255, 211, 246, 203, 173, 191, 127, 253, 229, 232, 168, 244, 203, 105, 128,
|
||||||
240, 255, 211, 246, 203, 173, 191, 127, 253, 229, 232, 168, 244, 203,
|
168
|
||||||
105, 128, 168
|
])),
|
||||||
]
|
x: ByteArray::from_slice(&[
|
||||||
.into()
|
|
||||||
),
|
|
||||||
x: [
|
|
||||||
64, 227, 7, 154, 255, 122, 181, 89, 73, 191, 235, 141, 170, 154, 231, 13,
|
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
|
34, 136, 143, 144, 34, 45, 53, 202, 70, 137, 151, 98, 118, 175, 208, 221
|
||||||
]
|
]),
|
||||||
.into(),
|
y: ByteArray::from_slice(&[
|
||||||
y: [
|
|
||||||
78, 54, 25, 160, 121, 220, 181, 171, 68, 19, 163, 66, 172, 169, 151, 65,
|
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
|
210, 73, 62, 115, 115, 100, 69, 252, 156, 25, 153, 117, 237, 192, 99, 137
|
||||||
]
|
])
|
||||||
.into(),
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
algorithm: Some(Algorithm::ES256),
|
algorithm: Some(Algorithm::ES256),
|
||||||
key_id: Some("a key".into()),
|
key_id: Some("a key".into()),
|
||||||
key_ops: KeyOps::empty(),
|
key_ops: KeyOps::empty(),
|
||||||
@ -74,13 +71,13 @@ fn deserialize_es256() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serialize_es256() {
|
fn serialize_es256() {
|
||||||
let jwk = JsonWebKey {
|
let jwk = JsonWebKey {
|
||||||
key: box Key::EC {
|
key: Box::new(Key::EC {
|
||||||
curve: Curve::P256 {
|
curve: Curve::P256 {
|
||||||
d: None,
|
d: None,
|
||||||
x: [1u8; 32].into(),
|
x: ByteArray::from_slice(&[1u8; 32]),
|
||||||
y: [2u8; 32].into(),
|
y: ByteArray::from_slice(&[2u8; 32]),
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
key_id: None,
|
key_id: None,
|
||||||
algorithm: None,
|
algorithm: None,
|
||||||
key_ops: KeyOps::empty(),
|
key_ops: KeyOps::empty(),
|
||||||
@ -130,10 +127,10 @@ fn deserialize_hs256() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
jwk,
|
jwk,
|
||||||
JsonWebKey {
|
JsonWebKey {
|
||||||
key: box Key::Symmetric {
|
key: Box::new(Key::Symmetric {
|
||||||
// The parameters were decoded using a 10-liner Rust script.
|
// The parameters were decoded using a 10-liner Rust script.
|
||||||
key: vec![180, 3, 141, 233].into(),
|
key: vec![180, 3, 141, 233].into(),
|
||||||
},
|
}),
|
||||||
algorithm: Some(Algorithm::HS256),
|
algorithm: Some(Algorithm::HS256),
|
||||||
key_id: None,
|
key_id: None,
|
||||||
key_ops: KeyOps::SIGN | KeyOps::VERIFY,
|
key_ops: KeyOps::SIGN | KeyOps::VERIFY,
|
||||||
@ -145,9 +142,9 @@ fn deserialize_hs256() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serialize_hs256() {
|
fn serialize_hs256() {
|
||||||
let jwk = JsonWebKey {
|
let jwk = JsonWebKey {
|
||||||
key: box Key::Symmetric {
|
key: Box::new(Key::Symmetric {
|
||||||
key: vec![42; 16].into(),
|
key: vec![42; 16].into(),
|
||||||
},
|
}),
|
||||||
key_id: None,
|
key_id: None,
|
||||||
algorithm: None,
|
algorithm: None,
|
||||||
key_ops: KeyOps::empty(),
|
key_ops: KeyOps::empty(),
|
||||||
@ -165,7 +162,7 @@ fn deserialize_rs256() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
jwk,
|
jwk,
|
||||||
JsonWebKey {
|
JsonWebKey {
|
||||||
key: box Key::RSA {
|
key: Box::new(Key::RSA {
|
||||||
public: RsaPublic {
|
public: RsaPublic {
|
||||||
e: PublicExponent,
|
e: PublicExponent,
|
||||||
n: vec![
|
n: vec![
|
||||||
@ -223,7 +220,7 @@ fn deserialize_rs256() {
|
|||||||
.into()
|
.into()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
}),
|
||||||
algorithm: None,
|
algorithm: None,
|
||||||
key_id: None,
|
key_id: None,
|
||||||
key_ops: KeyOps::WRAP_KEY,
|
key_ops: KeyOps::WRAP_KEY,
|
||||||
@ -235,7 +232,7 @@ fn deserialize_rs256() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn serialize_rs256() {
|
fn serialize_rs256() {
|
||||||
let jwk = JsonWebKey {
|
let jwk = JsonWebKey {
|
||||||
key: box Key::RSA {
|
key: Box::new(Key::RSA {
|
||||||
public: RsaPublic {
|
public: RsaPublic {
|
||||||
e: PublicExponent,
|
e: PublicExponent,
|
||||||
n: vec![105, 183, 62].into(),
|
n: vec![105, 183, 62].into(),
|
||||||
@ -248,7 +245,7 @@ fn serialize_rs256() {
|
|||||||
dq: None,
|
dq: None,
|
||||||
qi: None,
|
qi: None,
|
||||||
}),
|
}),
|
||||||
},
|
}),
|
||||||
key_id: None,
|
key_id: None,
|
||||||
algorithm: None,
|
algorithm: None,
|
||||||
key_ops: KeyOps::empty(),
|
key_ops: KeyOps::empty(),
|
||||||
|
43
src/utils.rs
43
src/utils.rs
@ -16,29 +16,36 @@ fn base64_decode(b64: impl AsRef<[u8]>) -> Result<Vec<u8>, base64::DecodeError>
|
|||||||
base64::decode_config(b64, base64_config())
|
base64::decode_config(b64, base64_config())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn serialize_base64<S: Serializer>(bytes: impl AsRef<[u8]>, s: S) -> Result<S::Ok, S::Error> {
|
pub(crate) mod serde_base64 {
|
||||||
base64_encode(bytes).serialize(s)
|
use super::*;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deserialize_base64<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
|
pub(crate) fn serialize<S: Serializer>(
|
||||||
let base64_str = Zeroizing::new(String::deserialize(d)?);
|
bytes: impl AsRef<[u8]>,
|
||||||
base64_decode(&*base64_str).map_err(|e| {
|
s: S,
|
||||||
#[cfg(debug_assertions)]
|
) -> Result<S::Ok, S::Error> {
|
||||||
let err_msg = e.to_string().to_lowercase();
|
base64_encode(bytes).serialize(s)
|
||||||
#[cfg(not(debug_assertions))]
|
}
|
||||||
let err_msg = "invalid base64";
|
|
||||||
de::Error::custom(err_msg.strip_suffix(".").unwrap_or(&err_msg))
|
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| {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let err_msg = e.to_string().to_lowercase();
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
let err_msg = "invalid base64";
|
||||||
|
de::Error::custom(err_msg.strip_suffix(".").unwrap_or(&err_msg))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "pkcs-convert")]
|
#[cfg(feature = "pkcs-convert")]
|
||||||
pub mod pkcs8 {
|
pub(crate) mod pkcs8 {
|
||||||
use yasna::{
|
use yasna::{
|
||||||
models::{ObjectIdentifier, TaggedDerValue},
|
models::{ObjectIdentifier, TaggedDerValue},
|
||||||
DERWriter, DERWriterSeq,
|
DERWriter, DERWriterSeq,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn write_oids(writer: &mut DERWriterSeq, oids: &[Option<&ObjectIdentifier>]) {
|
fn write_oids(writer: &mut DERWriterSeq<'_>, oids: &[Option<&ObjectIdentifier>]) {
|
||||||
for oid in oids {
|
for oid in oids {
|
||||||
match oid {
|
match oid {
|
||||||
Some(oid) => writer.next().write_oid(oid),
|
Some(oid) => writer.next().write_oid(oid),
|
||||||
@ -47,9 +54,9 @@ pub mod pkcs8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_private(
|
pub(crate) fn write_private(
|
||||||
oids: &[Option<&ObjectIdentifier>],
|
oids: &[Option<&ObjectIdentifier>],
|
||||||
body_writer: impl FnOnce(&mut DERWriterSeq),
|
body_writer: impl FnOnce(&mut DERWriterSeq<'_>),
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
yasna::construct_der(|writer| {
|
yasna::construct_der(|writer| {
|
||||||
writer.write_sequence(|writer| {
|
writer.write_sequence(|writer| {
|
||||||
@ -66,9 +73,9 @@ pub mod pkcs8 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_public(
|
pub(crate) fn write_public(
|
||||||
oids: &[Option<&ObjectIdentifier>],
|
oids: &[Option<&ObjectIdentifier>],
|
||||||
body_writer: impl FnOnce(DERWriter),
|
body_writer: impl FnOnce(DERWriter<'_>),
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
yasna::construct_der(|writer| {
|
yasna::construct_der(|writer| {
|
||||||
writer.write_sequence(|writer| {
|
writer.write_sequence(|writer| {
|
||||||
|
Loading…
Reference in New Issue
Block a user