Skip to content

Commit

Permalink
jose-jwk: add support for p521 (#62)
Browse files Browse the repository at this point in the history
Adds support for the NIST P-521 elliptic curve using the `p521` crate, analogous
to the existing support for `p256` and `p384`
  • Loading branch information
JoHaHu authored Nov 27, 2023
1 parent 3e5ca6f commit 11bd6e8
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 16 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target/
**/*.rs.bk
.idea/
20 changes: 16 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion jose-jwk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ rust-version = "1.65"

[features]
default = ["crypto"]
crypto = ["p256", "p384", "rsa"]
crypto = ["p256", "p384", "p521", "rsa"]

[dependencies]
jose-b64 = { version = "0.1", default-features = false, features = ["secret"], path = "../jose-b64" }
Expand All @@ -29,6 +29,7 @@ zeroize = { version = "1.7.0", default-features = false, features = ["alloc"] }
# optional dependencies
p256 = { version = "0.13.2", default-features = false, optional = true, features = ["arithmetic"] }
p384 = { version = "0.13.0", default-features = false, optional = true, features = ["arithmetic"] }
p521 = { version = "0.13.3", default-features = false, optional = true, features = ["arithmetic"]}
rsa = { version = "0.9", default-features = false, optional = true }
url = { version = "2.4.1", default-features = false, optional = true, features = ["serde"] }

Expand Down
44 changes: 42 additions & 2 deletions jose-jwk/src/crypto/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub enum Key {
/// A P-384 key.
#[cfg(feature = "p384")]
P384(super::Kind<p384::PublicKey, p384::SecretKey>),

/// A P-521 key
#[cfg(feature = "p521")]
P521(super::Kind<p521::PublicKey, p521::SecretKey>),
}

impl KeyInfo for Key {
Expand All @@ -49,6 +53,9 @@ impl KeyInfo for Key {

#[cfg(feature = "p384")]
Self::P384(k) => k.strength(),

#[cfg(feature = "p521")]
Self::P521(k) => k.strength(),
}
}

Expand All @@ -64,6 +71,9 @@ impl KeyInfo for Key {

#[cfg(feature = "p384")]
Self::P384(k) => k.is_supported(algo),

#[cfg(feature = "p521")]
Self::P521(k) => k.is_supported(algo),
}
}
}
Expand Down Expand Up @@ -137,6 +147,27 @@ impl From<p384::SecretKey> for Key {
}
}

#[cfg(feature = "p521")]
impl From<super::Kind<p521::PublicKey, p521::SecretKey>> for Key {
fn from(value: super::Kind<p521::PublicKey, p521::SecretKey>) -> Self {
Self::P521(value)
}
}

#[cfg(feature = "p521")]
impl From<p521::PublicKey> for Key {
fn from(value: p521::PublicKey) -> Self {
Self::P521(super::Kind::Public(value))
}
}

#[cfg(feature = "p521")]
impl From<p521::SecretKey> for Key {
fn from(value: p521::SecretKey) -> Self {
Self::P521(super::Kind::Secret(value))
}
}

impl From<&crate::Oct> for Key {
fn from(value: &crate::Oct) -> Self {
Self::Oct(value.k.to_vec().into_boxed_slice().into())
Expand All @@ -152,7 +183,7 @@ impl TryFrom<&crate::Rsa> for Key {
}
}

#[cfg(any(feature = "p256", feature = "p384"))]
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
impl TryFrom<&crate::Ec> for Key {
type Error = super::Error;

Expand All @@ -164,6 +195,9 @@ impl TryFrom<&crate::Ec> for Key {
#[cfg(feature = "p384")]
crate::EcCurves::P384 => Ok(Self::P384(value.try_into()?)),

#[cfg(feature = "p521")]
crate::EcCurves::P521 => Ok(Self::P521(value.try_into()?)),

_ => Err(super::Error::Unsupported),
}
}
Expand All @@ -179,7 +213,7 @@ impl TryFrom<&crate::Key> for Key {
#[cfg(feature = "rsa")]
crate::Key::Rsa(rsa) => rsa.try_into(),

#[cfg(any(feature = "p256", feature = "p384"))]
#[cfg(any(feature = "p256", feature = "p384", feature = "p521"))]
crate::Key::Ec(ec) => ec.try_into(),

_ => Err(super::Error::Unsupported),
Expand Down Expand Up @@ -211,6 +245,12 @@ impl From<&Key> for crate::Key {
super::Kind::Public(public) => Self::Ec(public.into()),
super::Kind::Secret(secret) => Self::Ec(secret.into()),
},

#[cfg(feature = "p521")]
Key::P521(kind) => match kind {
super::Kind::Public(public) => Self::Ec(public.into()),
super::Kind::Secret(secret) => Self::Ec(secret.into()),
},
}
}
}
23 changes: 23 additions & 0 deletions jose-jwk/src/crypto/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,26 @@ impl TryFrom<&crate::Ec> for Kind<p384::PublicKey, p384::SecretKey> {
}
}
}

#[cfg(feature = "p521")]
impl From<&Kind<p521::PublicKey, p521::SecretKey>> for crate::Ec {
fn from(value: &Kind<p521::PublicKey, p521::SecretKey>) -> Self {
match value {
Kind::Public(key) => key.into(),
Kind::Secret(key) => key.into(),
}
}
}

#[cfg(feature = "p521")]
impl TryFrom<&crate::Ec> for Kind<p521::PublicKey, p521::SecretKey> {
type Error = super::Error;

fn try_from(value: &crate::Ec) -> Result<Self, Self::Error> {
if value.d.is_none() {
Ok(Kind::Public(value.try_into()?))
} else {
Ok(Kind::Secret(value.try_into()?))
}
}
}
1 change: 1 addition & 0 deletions jose-jwk/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod keyinfo;
mod kind;
mod p256;
mod p384;
mod p521;
mod rsa;

pub use key::Key;
Expand Down
124 changes: 124 additions & 0 deletions jose-jwk/src/crypto/p521.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-FileCopyrightText: 2022 Profian Inc. <[email protected]>
// SPDX-License-Identifier: Apache-2.0 OR MIT

#![cfg(feature = "p521")]

use p521::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint};
use p521::{EncodedPoint, FieldBytes, PublicKey, SecretKey};

use jose_jwa::{Algorithm, Algorithm::Signing, Signing::*};

use super::Error;
use super::KeyInfo;
use crate::{Ec, EcCurves};

impl KeyInfo for PublicKey {
fn strength(&self) -> usize {
32
}

fn is_supported(&self, algo: &Algorithm) -> bool {
matches!(algo, Signing(Es512))
}
}

impl KeyInfo for SecretKey {
fn strength(&self) -> usize {
32
}

fn is_supported(&self, algo: &Algorithm) -> bool {
matches!(algo, Signing(Es512))
}
}

impl From<&PublicKey> for Ec {
fn from(pk: &PublicKey) -> Self {
let ep = pk.to_encoded_point(false);

Self {
crv: EcCurves::P521,
x: ep.x().expect("unreachable").to_vec().into(),
y: ep.y().expect("unreachable").to_vec().into(),
d: None,
}
}
}

impl From<PublicKey> for Ec {
fn from(sk: PublicKey) -> Self {
(&sk).into()
}
}

impl TryFrom<&Ec> for PublicKey {
type Error = Error;

fn try_from(value: &Ec) -> Result<Self, Self::Error> {
if value.crv != EcCurves::P521 {
return Err(Error::AlgMismatch);
}

let mut x = FieldBytes::default();
if value.x.len() != x.len() {
return Err(Error::Invalid);
}

let mut y = FieldBytes::default();
if value.y.len() != y.len() {
return Err(Error::Invalid);
}

x.copy_from_slice(&value.x);
y.copy_from_slice(&value.y);

let ep = EncodedPoint::from_affine_coordinates(&x, &y, false);
Option::from(Self::from_encoded_point(&ep)).ok_or(Error::Invalid)
}
}

impl TryFrom<Ec> for PublicKey {
type Error = Error;

fn try_from(value: Ec) -> Result<Self, Self::Error> {
(&value).try_into()
}
}

impl From<&SecretKey> for Ec {
fn from(sk: &SecretKey) -> Self {
let mut key: Self = sk.public_key().into();
key.d = Some(sk.to_bytes().to_vec().into());
key
}
}

impl From<SecretKey> for Ec {
fn from(sk: SecretKey) -> Self {
(&sk).into()
}
}

impl TryFrom<&Ec> for SecretKey {
type Error = Error;

fn try_from(value: &Ec) -> Result<Self, Self::Error> {
if value.crv != EcCurves::P521 {
return Err(Error::AlgMismatch);
}

if let Some(d) = value.d.as_ref() {
return Self::from_slice(d).map_err(|_| Error::Invalid);
}

Err(Error::NotPrivate)
}
}

impl TryFrom<Ec> for SecretKey {
type Error = Error;

fn try_from(value: Ec) -> Result<Self, Self::Error> {
(&value).try_into()
}
}
18 changes: 9 additions & 9 deletions jose-jws/src/head.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,41 +62,41 @@ impl Default for Protected {
/// The JWS Unprotected Header
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Unprotected {
/// RFC 7517 Section 4.1.1
/// RFC 7515 Section 4.1.1
#[serde(skip_serializing_if = "Option::is_none", default)]
pub alg: Option<Signing>,

/// RFC 7517 Section 4.1.2
/// RFC 7515 Section 4.1.2
#[cfg(feature = "url")]
#[serde(skip_serializing_if = "Option::is_none", default)]
pub jku: Option<url::Url>,

/// RFC 7517 Section 4.1.3
/// RFC 7515 Section 4.1.3
#[serde(skip_serializing_if = "Option::is_none", default)]
pub jwk: Option<Jwk>,

/// RFC 7517 Section 4.1.4
/// RFC 7515 Section 4.1.4
#[serde(skip_serializing_if = "Option::is_none", default)]
pub kid: Option<String>,

/// RFC 7517 Section 4.1.5
/// RFC 7515 Section 4.1.5
#[serde(skip_serializing_if = "Option::is_none", default)]
#[cfg(feature = "url")]
pub x5u: Option<url::Url>,

/// RFC 7517 Section 4.1.6
/// RFC 7515 Section 4.1.6
#[serde(skip_serializing_if = "Option::is_none", default)]
pub x5c: Option<Vec<Bytes<Box<[u8]>, Base64>>>, // base64, not base64url

/// RFC 7517 Section 4.1.7-8
/// RFC 7515 Section 4.1.7-8
#[serde(flatten)]
pub x5t: Thumbprint,

/// RFC 7517 Section 4.1.9
/// RFC 7515 Section 4.1.9
#[serde(skip_serializing_if = "Option::is_none", default)]
pub typ: Option<String>,

/// RFC 7517 Section 4.1.10
/// RFC 7515 Section 4.1.10
#[serde(skip_serializing_if = "Option::is_none", default)]
pub cty: Option<String>,
}

0 comments on commit 11bd6e8

Please sign in to comment.