mirror of
https://github.com/BitskiCo/jwk-rs
synced 2024-11-24 21:09:22 +00:00
Refactor PKCS#8 conversion
This commit is contained in:
parent
8b1d543598
commit
8aaf3d71c7
@ -12,13 +12,17 @@ edition = "2018"
|
|||||||
base64 = "0.12"
|
base64 = "0.12"
|
||||||
bitflags = "1.2"
|
bitflags = "1.2"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
|
num-bigint = { version = "0.2", optional = true }
|
||||||
paste = "0.1"
|
paste = "0.1"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
syn = { version = "1.0", features = ["full"] } # required to parse const generics
|
syn = { version = "1.0", features = ["full"] } # required to parse const generics
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
yasna = { version = "0.3", optional = true }
|
yasna = { version = "0.3", optional = true, features = ["num-bigint"] }
|
||||||
zeroize = { version = "1.1", features = ["zeroize_derive"] }
|
zeroize = { version = "1.1", features = ["zeroize_derive"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
conversion = ["yasna"]
|
convert = ["num-bigint", "yasna"]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
jsonwebtoken = "7.2"
|
||||||
|
@ -35,3 +35,8 @@ fn main() {
|
|||||||
let token = jwt::encode(&jwt::Header::default(), &() /* claims */, encoding_key).unwrap();
|
let token = jwt::encode(&jwt::Header::default(), &() /* claims */, encoding_key).unwrap();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* `convert` - enables `Key::{to_der, to_pem}`.
|
||||||
|
This pulls in the [yasna](https://crates.io/crates/yasna) crate.
|
||||||
|
181
src/lib.rs
181
src/lib.rs
@ -8,6 +8,8 @@ mod key_ops;
|
|||||||
mod tests;
|
mod tests;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
use std::array::FixedSizeArray;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use zeroize::Zeroize;
|
use zeroize::Zeroize;
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ impl std::str::FromStr for JsonWebKey {
|
|||||||
(
|
(
|
||||||
ES256,
|
ES256,
|
||||||
EC {
|
EC {
|
||||||
params: Curve::P256 { .. },
|
curve: Curve::P256 { .. },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
| (RS256, RSA { .. })
|
| (RS256, RSA { .. })
|
||||||
@ -78,16 +80,20 @@ impl std::fmt::Display for JsonWebKey {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(tag = "kty")]
|
#[serde(tag = "kty")]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
|
/// An elliptic curve, as per [RFC 7518 §6.2](https://tools.ietf.org/html/rfc7518#section-6.2).
|
||||||
EC {
|
EC {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
params: Curve,
|
curve: Curve,
|
||||||
},
|
},
|
||||||
|
/// An elliptic curve, as per [RFC 7518 §6.3](https://tools.ietf.org/html/rfc7518#section-6.3).
|
||||||
|
/// See also: [RFC 3447](https://tools.ietf.org/html/rfc3447).
|
||||||
RSA {
|
RSA {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
public: RsaPublic,
|
public: RsaPublic,
|
||||||
#[serde(flatten, default, skip_serializing_if = "Option::is_none")]
|
#[serde(flatten, default, skip_serializing_if = "Option::is_none")]
|
||||||
private: Option<RsaPrivate>,
|
private: Option<RsaPrivate>,
|
||||||
},
|
},
|
||||||
|
/// A symmetric key, as per [RFC 7518 §6.4](https://tools.ietf.org/html/rfc7518#section-6.4).
|
||||||
#[serde(rename = "oct")]
|
#[serde(rename = "oct")]
|
||||||
Symmetric {
|
Symmetric {
|
||||||
#[serde(rename = "k")]
|
#[serde(rename = "k")]
|
||||||
@ -102,7 +108,7 @@ impl Key {
|
|||||||
match self {
|
match self {
|
||||||
Self::Symmetric { .. }
|
Self::Symmetric { .. }
|
||||||
| Self::EC {
|
| Self::EC {
|
||||||
params: Curve::P256 { d: Some(_), .. },
|
curve: Curve::P256 { d: Some(_), .. },
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
| Self::RSA {
|
| Self::RSA {
|
||||||
@ -125,9 +131,9 @@ impl Key {
|
|||||||
Some(match self {
|
Some(match self {
|
||||||
Self::Symmetric { .. } => return None,
|
Self::Symmetric { .. } => return None,
|
||||||
Self::EC {
|
Self::EC {
|
||||||
params: Curve::P256 { x, y, .. },
|
curve: Curve::P256 { x, y, .. },
|
||||||
} => Self::EC {
|
} => Self::EC {
|
||||||
params: Curve::P256 {
|
curve: Curve::P256 {
|
||||||
x: x.clone(),
|
x: x.clone(),
|
||||||
y: y.clone(),
|
y: y.clone(),
|
||||||
d: None,
|
d: None,
|
||||||
@ -141,22 +147,25 @@ impl Key {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// If this key is asymmetric, encodes it as PKCS#8.
|
/// If this key is asymmetric, encodes it as PKCS#8.
|
||||||
#[cfg(feature = "conversion")]
|
#[cfg(feature = "convert")]
|
||||||
pub fn to_der(&self) -> Option<Vec<u8>> {
|
pub fn to_der(&self) -> Result<Vec<u8>, PkcsConvertError> {
|
||||||
|
use num_bigint::BigUint;
|
||||||
use yasna::{models::ObjectIdentifier, DERWriter, DERWriterSeq, Tag};
|
use yasna::{models::ObjectIdentifier, DERWriter, DERWriterSeq, Tag};
|
||||||
|
|
||||||
|
use crate::utils::pkcs8;
|
||||||
|
|
||||||
if let Self::Symmetric { .. } = self {
|
if let Self::Symmetric { .. } = self {
|
||||||
return None;
|
return Err(PkcsConvertError::NotAsymmetric);
|
||||||
}
|
}
|
||||||
Some(yasna::construct_der(|writer| match self {
|
|
||||||
|
Ok(match self {
|
||||||
Self::EC {
|
Self::EC {
|
||||||
params: Curve::P256 { d, x, y },
|
curve: Curve::P256 { d, x, y },
|
||||||
} => {
|
} => {
|
||||||
let write_curve_oid = |writer: DERWriter| {
|
let ec_public_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1]);
|
||||||
writer.write_oid(&ObjectIdentifier::from_slice(&[
|
let prime256v1_oid = ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7]);
|
||||||
1, 2, 840, 10045, 3, 1, 7, // prime256v1
|
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()
|
||||||
@ -166,93 +175,77 @@ impl Key {
|
|||||||
.collect();
|
.collect();
|
||||||
writer.write_bitvec_bytes(&public_bytes, 8 * (32 * 2 + 1));
|
writer.write_bitvec_bytes(&public_bytes, 8 * (32 * 2 + 1));
|
||||||
};
|
};
|
||||||
writer.write_sequence(|writer| {
|
|
||||||
match d {
|
match d {
|
||||||
Some(private_point) => {
|
Some(private_point) => {
|
||||||
|
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.as_ref());
|
writer.next().write_bytes(private_point.as_slice());
|
||||||
writer.next().write_tagged(Tag::context(0), |writer| {
|
writer.next().write_tagged(Tag::context(0), |writer| {
|
||||||
write_curve_oid(writer);
|
writer.write_oid(&prime256v1_oid)
|
||||||
});
|
});
|
||||||
writer.next().write_tagged(Tag::context(1), |writer| {
|
writer.next().write_tagged(Tag::context(1), write_public);
|
||||||
write_public(writer);
|
})
|
||||||
});
|
}
|
||||||
}
|
None => pkcs8::write_public(oids, write_public),
|
||||||
None => {
|
}
|
||||||
writer.next().write_sequence(|writer| {
|
|
||||||
writer.next().write_oid(&ObjectIdentifier::from_slice(&[
|
|
||||||
1, 2, 840, 10045, 2, 1, // ecPublicKey
|
|
||||||
]));
|
|
||||||
write_curve_oid(writer.next());
|
|
||||||
});
|
|
||||||
write_public(writer.next());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
Self::RSA { public, private } => {
|
Self::RSA { public, private } => {
|
||||||
let write_alg_id = |writer: &mut DERWriterSeq| {
|
let rsa_encryption_oid = ObjectIdentifier::from_slice(&[
|
||||||
writer.next().write_oid(&ObjectIdentifier::from_slice(&[
|
1, 2, 840, 113549, 1, 1, 1, // rsaEncryption
|
||||||
1, 2, 840, 113549, 1, 1, 1, // rsaEncryption
|
]);
|
||||||
]));
|
let oids = &[Some(&rsa_encryption_oid), None];
|
||||||
writer.next().write_null(); // parameters
|
let write_bytevec = |writer: DERWriter, vec: &ByteVec| {
|
||||||
|
let bigint = BigUint::from_bytes_be(vec.as_slice());
|
||||||
|
writer.write_biguint(&bigint);
|
||||||
};
|
};
|
||||||
|
|
||||||
let write_public = |writer: &mut DERWriterSeq| {
|
let write_public = |writer: &mut DERWriterSeq| {
|
||||||
writer.next().write_bytes(&*public.n);
|
write_bytevec(writer.next(), &public.n);
|
||||||
writer.next().write_u32(PUBLIC_EXPONENT);
|
writer.next().write_u32(PUBLIC_EXPONENT);
|
||||||
};
|
};
|
||||||
writer.write_sequence(|writer| {
|
|
||||||
match private {
|
let write_private = |writer: &mut DERWriterSeq, private: &RsaPrivate| {
|
||||||
Some(private) => {
|
// https://tools.ietf.org/html/rfc3447#appendix-A.1.2
|
||||||
writer.next().write_i8(0); // version
|
writer.next().write_i8(0); // version (two-prime)
|
||||||
writer.next().write_sequence(|writer| {
|
write_public(writer);
|
||||||
write_alg_id(writer);
|
write_bytevec(writer.next(), &private.d);
|
||||||
});
|
macro_rules! write_opt_bytevecs {
|
||||||
writer
|
($($param:ident),+) => {{
|
||||||
.next()
|
$(write_bytevec(writer.next(), private.$param.as_ref().unwrap());)+
|
||||||
.write_tagged(yasna::tags::TAG_OCTETSTRING, |writer| {
|
}};
|
||||||
writer.write_sequence(|writer| {
|
|
||||||
writer.next().write_i8(0); // version
|
|
||||||
write_public(writer);
|
|
||||||
writer.next().write_bytes(&private.d);
|
|
||||||
if let Some(p) = &private.p {
|
|
||||||
writer.next().write_bytes(p);
|
|
||||||
}
|
|
||||||
if let Some(q) = &private.q {
|
|
||||||
writer.next().write_bytes(q);
|
|
||||||
}
|
|
||||||
if let Some(dp) = &private.dp {
|
|
||||||
writer.next().write_bytes(dp);
|
|
||||||
}
|
|
||||||
if let Some(dq) = &private.dq {
|
|
||||||
writer.next().write_bytes(dq);
|
|
||||||
}
|
|
||||||
if let Some(qi) = &private.qi {
|
|
||||||
writer.next().write_bytes(qi);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
None => {
|
write_opt_bytevecs!(p, q, dp, dq, qi);
|
||||||
write_alg_id(writer);
|
};
|
||||||
writer
|
|
||||||
.next()
|
match private {
|
||||||
.write_tagged(yasna::tags::TAG_BITSTRING, |writer| {
|
Some(
|
||||||
writer.write_sequence(|writer| {
|
private
|
||||||
write_public(writer);
|
@
|
||||||
})
|
RsaPrivate {
|
||||||
});
|
d: _,
|
||||||
}
|
p: Some(_),
|
||||||
}
|
q: Some(_),
|
||||||
});
|
dp: Some(_),
|
||||||
|
dq: Some(_),
|
||||||
|
qi: Some(_),
|
||||||
|
},
|
||||||
|
) => pkcs8::write_private(oids, |writer| write_private(writer, private)),
|
||||||
|
Some(_) => return Err(PkcsConvertError::MissingRsaParams),
|
||||||
|
None => pkcs8::write_public(oids, |writer| {
|
||||||
|
let body =
|
||||||
|
yasna::construct_der(|writer| writer.write_sequence(write_public));
|
||||||
|
writer.write_bitvec_bytes(&body, body.len() * 8);
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Self::Symmetric { .. } => unreachable!("checked above"),
|
Self::Symmetric { .. } => unreachable!("checked above"),
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If this key is asymmetric, encodes it as PKCS#8 with PEM armoring.
|
/// If this key is asymmetric, encodes it as PKCS#8 with PEM armoring.
|
||||||
#[cfg(feature = "conversion")]
|
#[cfg(feature = "convert")]
|
||||||
pub fn to_pem(&self) -> Option<String> {
|
pub fn to_pem(&self) -> Result<String, PkcsConvertError> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let der_b64 = base64::encode(self.to_der()?);
|
let der_b64 = base64::encode(self.to_der()?);
|
||||||
let key_ty = if self.is_private() {
|
let key_ty = if self.is_private() {
|
||||||
@ -272,7 +265,7 @@ impl Key {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
writeln!(&mut pem, "-----END {} KEY-----", key_ty).unwrap();
|
writeln!(&mut pem, "-----END {} KEY-----", key_ty).unwrap();
|
||||||
Some(pem)
|
Ok(pem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,8 +353,7 @@ pub enum JsonWebAlgorithm {
|
|||||||
ES256,
|
ES256,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Serde(#[from] serde_json::Error),
|
Serde(#[from] serde_json::Error),
|
||||||
@ -372,3 +364,12 @@ pub enum Error {
|
|||||||
#[error("mismatched algorithm for key type")]
|
#[error("mismatched algorithm for key type")]
|
||||||
MismatchedAlgorithm,
|
MismatchedAlgorithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum PkcsConvertError {
|
||||||
|
#[error("encoding RSA JWK as PKCS#8 requires specifing all of p, q, dp, dq, qi")]
|
||||||
|
MissingRsaParams,
|
||||||
|
|
||||||
|
#[error("a symmetric key can not be encoded using PKCS#8")]
|
||||||
|
NotAsymmetric,
|
||||||
|
}
|
||||||
|
119
src/tests.rs
119
src/tests.rs
@ -2,10 +2,8 @@ use super::*;
|
|||||||
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[test]
|
// Generated using https://mkjwk.org
|
||||||
fn deserialize_es256() {
|
static P256_JWK_FIXTURE: &str = r#"{
|
||||||
// Generated using https://mkjwk.org
|
|
||||||
let jwk_str = r#"{
|
|
||||||
"kty": "EC",
|
"kty": "EC",
|
||||||
"d": "ZoKQ9j4dhIBlMRVrv-QG8P_T9sutv3_95eio9MtpgKg",
|
"d": "ZoKQ9j4dhIBlMRVrv-QG8P_T9sutv3_95eio9MtpgKg",
|
||||||
"use": "enc",
|
"use": "enc",
|
||||||
@ -15,13 +13,29 @@ fn deserialize_es256() {
|
|||||||
"y": "TjYZoHnctatEE6NCrKmXQdJJPnNzZEX8nBmZde3AY4k",
|
"y": "TjYZoHnctatEE6NCrKmXQdJJPnNzZEX8nBmZde3AY4k",
|
||||||
"alg": "ES256"
|
"alg": "ES256"
|
||||||
}"#;
|
}"#;
|
||||||
let jwk = JsonWebKey::from_str(jwk_str).unwrap();
|
|
||||||
|
static RSA_JWK_FIXTURE: &str = r#"{
|
||||||
|
"p": "6AQ4yHef17an_i5LQPHNIxzpH65xWOSf_qCB7q-lXyM",
|
||||||
|
"kty": "RSA",
|
||||||
|
"q": "tSVfpefCsf1iWmAs1zYvxdEsUiv0VMEuQBtbTijj_OE",
|
||||||
|
"d": "Qdp8a8Df5TlMaaloXApNF_3eu8sLHNWbXdg70e5YVTAs0WUfaIf5c3n96RrDDAzmMEwgKnJ7A1NJ9Nlzz4Z0AQ",
|
||||||
|
"e": "AQAB",
|
||||||
|
"use": "enc",
|
||||||
|
"qi": "adhQHH8IGXFfLEMnZ5t_TeCp5zgSwQktJ2lmylxUG0M",
|
||||||
|
"dp": "qVnLiKeoSG_Olz17OGBGd4a2sqVFnrjh_51wuaQDdTk",
|
||||||
|
"dq": "GL_Ec6xYg2z1FRfyyGyU1lgf0BJFTZcfNI8ISIN5ssE",
|
||||||
|
"n": "pCzbcd9kjvg5rfGHdEMWnXo49zbB6FLQ-m0B0BvVp0aojVWYa0xujC-ZP7ZhxByPxyc2PazwFJJi9ivZ_ggRww"
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_es256() {
|
||||||
|
let jwk = JsonWebKey::from_str(P256_JWK_FIXTURE).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
jwk,
|
jwk,
|
||||||
JsonWebKey {
|
JsonWebKey {
|
||||||
key: box Key::EC {
|
key: box Key::EC {
|
||||||
// The parameters were decoded using a 10-liner Rust script.
|
// The parameters were decoded using a 10-liner Rust script.
|
||||||
params: Curve::P256 {
|
curve: Curve::P256 {
|
||||||
d: Some(
|
d: Some(
|
||||||
[
|
[
|
||||||
102, 130, 144, 246, 62, 29, 132, 128, 101, 49, 21, 107, 191, 228, 6,
|
102, 130, 144, 246, 62, 29, 132, 128, 101, 49, 21, 107, 191, 228, 6,
|
||||||
@ -54,7 +68,7 @@ fn deserialize_es256() {
|
|||||||
fn serialize_es256() {
|
fn serialize_es256() {
|
||||||
let jwk = JsonWebKey {
|
let jwk = JsonWebKey {
|
||||||
key: box Key::EC {
|
key: box Key::EC {
|
||||||
params: Curve::P256 {
|
curve: Curve::P256 {
|
||||||
d: None,
|
d: None,
|
||||||
x: [1u8; 32].into(),
|
x: [1u8; 32].into(),
|
||||||
y: [2u8; 32].into(),
|
y: [2u8; 32].into(),
|
||||||
@ -114,19 +128,7 @@ fn serialize_hs256() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn deserialize_rs256() {
|
fn deserialize_rs256() {
|
||||||
let jwk_str = r#"{
|
let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap();
|
||||||
"p": "_LSip5o4eaGf25uvwyUq9ubRtKemrCaoCxumoj63Au0",
|
|
||||||
"kty": "RSA",
|
|
||||||
"q": "l20iLpicEW3uja0Zg2xP6DjZa86bD4IQ3wFXCcKCf1c",
|
|
||||||
"d": "Xo0VAHtfV38HwJbAI6X-Fu7vuyoQjnuiSlQhcSjxn0BZfLP_DKxdJ2ANgTGVE0x243YHqhWRHLobbmDcnUuMOQ",
|
|
||||||
"e": "AQAB",
|
|
||||||
"qi": "2mzAaSr7I1D3vDtOhbWKS9-9ELRHKbAHz4dhn4DSCBo",
|
|
||||||
"dp": "-kyswxeVEpyM6wdU2xRobu-HDMn145PSZFY6AX_e460",
|
|
||||||
"alg": "RS256",
|
|
||||||
"dq": "OqMWE3khJlatg8s-D_hHUSOCfg65WN4C7ng0XiEmK20",
|
|
||||||
"n": "lXpGmBoIxj56TpptApaac6V19_7WWbq0a14a5UHBBlkc54NwIUa2X4p9OeK2sy6rLQ_1g1AcSwfsVUy8MP-Riw"
|
|
||||||
}"#;
|
|
||||||
let jwk = JsonWebKey::from_str(jwk_str).unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
jwk,
|
jwk,
|
||||||
JsonWebKey {
|
JsonWebKey {
|
||||||
@ -262,24 +264,71 @@ fn mismatched_algorithm() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "conversion")]
|
#[cfg(feature = "convert")]
|
||||||
#[test]
|
#[test]
|
||||||
fn es256_to_pem() {
|
fn p256_private_to_pem() {
|
||||||
let jwk_str = r#"{
|
// generated using mkjwk, converted using node-jwk-to-pem, verified using openssl
|
||||||
"kty": "EC",
|
let jwk = JsonWebKey::from_str(P256_JWK_FIXTURE).unwrap();
|
||||||
"d": "ZoKQ9j4dhIBlMRVrv-QG8P_T9sutv3_95eio9MtpgKg",
|
|
||||||
"crv": "P-256",
|
|
||||||
"x": "QOMHmv96tVlJv-uNqprnDSKIj5AiLTXKRomXYnav0N0",
|
|
||||||
"y": "TjYZoHnctatEE6NCrKmXQdJJPnNzZEX8nBmZde3AY4k"
|
|
||||||
}"#;
|
|
||||||
let jwk = JsonWebKey::from_str(jwk_str).unwrap();
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
base64::encode(jwk.key.to_pem().unwrap()),
|
jwk.key.to_pem().unwrap(),
|
||||||
"-----BEGIN PRIVATE KEY-----
|
"-----BEGIN PRIVATE KEY-----
|
||||||
MHcCAQEEIGaCkPY+HYSAZTEVa7/kBvD/0/bLrb9//eXoqPTLaYCooAoGCCqGSM49
|
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgZoKQ9j4dhIBlMRVr
|
||||||
AwEHoUQDQgAEQOMHmv96tVlJv+uNqprnDSKIj5AiLTXKRomXYnav0N1ONhmgedy1
|
v+QG8P/T9sutv3/95eio9MtpgKigCgYIKoZIzj0DAQehRANCAARA4wea/3q1WUm/
|
||||||
q0QTo0KsqZdB0kk+c3NkRfycGZl17cBjiQ==
|
642qmucNIoiPkCItNcpGiZdidq/Q3U42GaB53LWrRBOjQqypl0HSST5zc2RF/JwZ
|
||||||
-----END PRIVATE KEY-----"
|
mXXtwGOJ
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "convert")]
|
||||||
|
#[test]
|
||||||
|
fn p256_public_to_pem() {
|
||||||
|
let jwk = JsonWebKey::from_str(P256_JWK_FIXTURE).unwrap();
|
||||||
|
#[rustfmt::skip]
|
||||||
|
assert_eq!(
|
||||||
|
jwk.key.to_public().unwrap().to_pem().unwrap(),
|
||||||
|
"-----BEGIN PUBLIC KEY-----
|
||||||
|
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQOMHmv96tVlJv+uNqprnDSKIj5Ai
|
||||||
|
LTXKRomXYnav0N1ONhmgedy1q0QTo0KsqZdB0kk+c3NkRfycGZl17cBjiQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "convert")]
|
||||||
|
#[test]
|
||||||
|
fn rsa_private_to_pem() {
|
||||||
|
let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap();
|
||||||
|
#[rustfmt::skip]
|
||||||
|
assert_eq!(
|
||||||
|
jwk.key.to_pem().unwrap(),
|
||||||
|
"-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApCzbcd9kjvg5rfGH
|
||||||
|
dEMWnXo49zbB6FLQ+m0B0BvVp0aojVWYa0xujC+ZP7ZhxByPxyc2PazwFJJi9ivZ
|
||||||
|
/ggRwwIDAQABAkBB2nxrwN/lOUxpqWhcCk0X/d67ywsc1Ztd2DvR7lhVMCzRZR9o
|
||||||
|
h/lzef3pGsMMDOYwTCAqcnsDU0n02XPPhnQBAiEA6AQ4yHef17an/i5LQPHNIxzp
|
||||||
|
H65xWOSf/qCB7q+lXyMCIQC1JV+l58Kx/WJaYCzXNi/F0SxSK/RUwS5AG1tOKOP8
|
||||||
|
4QIhAKlZy4inqEhvzpc9ezhgRneGtrKlRZ644f+dcLmkA3U5AiAYv8RzrFiDbPUV
|
||||||
|
F/LIbJTWWB/QEkVNlx80jwhIg3mywQIgadhQHH8IGXFfLEMnZ5t/TeCp5zgSwQkt
|
||||||
|
J2lmylxUG0M=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "convert")]
|
||||||
|
#[test]
|
||||||
|
fn rsa_public_to_pem() {
|
||||||
|
let jwk = JsonWebKey::from_str(RSA_JWK_FIXTURE).unwrap();
|
||||||
|
#[rustfmt::skip]
|
||||||
|
assert_eq!(
|
||||||
|
jwk.key.to_public().unwrap().to_pem().unwrap(),
|
||||||
|
"-----BEGIN PUBLIC KEY-----
|
||||||
|
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKQs23HfZI74Oa3xh3RDFp16OPc2wehS
|
||||||
|
0PptAdAb1adGqI1VmGtMbowvmT+2YcQcj8cnNj2s8BSSYvYr2f4IEcMCAwEAAQ==
|
||||||
|
-----END PUBLIC KEY-----
|
||||||
|
"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
50
src/utils.rs
50
src/utils.rs
@ -30,3 +30,53 @@ pub fn deserialize_base64<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D:
|
|||||||
de::Error::custom(err_msg.strip_suffix(".").unwrap_or(&err_msg))
|
de::Error::custom(err_msg.strip_suffix(".").unwrap_or(&err_msg))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "convert")]
|
||||||
|
pub mod pkcs8 {
|
||||||
|
use yasna::{
|
||||||
|
models::{ObjectIdentifier, TaggedDerValue},
|
||||||
|
DERWriter, DERWriterSeq,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn write_oids(writer: &mut DERWriterSeq, oids: &[Option<&ObjectIdentifier>]) {
|
||||||
|
for oid in oids {
|
||||||
|
match oid {
|
||||||
|
Some(oid) => writer.next().write_oid(oid),
|
||||||
|
None => writer.next().write_null(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_private(
|
||||||
|
oids: &[Option<&ObjectIdentifier>],
|
||||||
|
body_writer: impl FnOnce(&mut DERWriterSeq),
|
||||||
|
) -> Vec<u8> {
|
||||||
|
yasna::construct_der(|writer| {
|
||||||
|
writer.write_sequence(|writer| {
|
||||||
|
writer.next().write_i8(0); // version
|
||||||
|
writer
|
||||||
|
.next()
|
||||||
|
.write_sequence(|writer| write_oids(writer, oids));
|
||||||
|
|
||||||
|
let body = yasna::construct_der(|writer| writer.write_sequence(body_writer));
|
||||||
|
writer
|
||||||
|
.next()
|
||||||
|
.write_tagged_der(&TaggedDerValue::from_octetstring(body));
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_public(
|
||||||
|
oids: &[Option<&ObjectIdentifier>],
|
||||||
|
body_writer: impl FnOnce(DERWriter),
|
||||||
|
) -> Vec<u8> {
|
||||||
|
yasna::construct_der(|writer| {
|
||||||
|
writer.write_sequence(|writer| {
|
||||||
|
writer
|
||||||
|
.next()
|
||||||
|
.write_sequence(|writer| write_oids(writer, oids));
|
||||||
|
body_writer(writer.next());
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user