You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-08-07 17:03:01 +03:00
Add a dedicated keystore crate
This commit is contained in:
@@ -16,7 +16,7 @@ use std::collections::HashSet;
|
||||
|
||||
use mas_iana::jose::{JsonWebKeyType, JsonWebKeyUse, JsonWebSignatureAlg};
|
||||
|
||||
use crate::JsonWebSignatureHeader;
|
||||
use crate::jwt::JsonWebSignatureHeader;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Constraint<'a> {
|
||||
|
@@ -104,17 +104,7 @@ impl TryFrom<PrivateJsonWebKey> for PublicJsonWebKey {
|
||||
type Error = SymetricKeyError;
|
||||
|
||||
fn try_from(value: PrivateJsonWebKey) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
parameters: value.parameters.try_into()?,
|
||||
r#use: value.r#use,
|
||||
key_ops: value.key_ops,
|
||||
alg: value.alg,
|
||||
kid: value.kid,
|
||||
x5u: value.x5u,
|
||||
x5c: value.x5c,
|
||||
x5t: value.x5t,
|
||||
x5t_s256: value.x5t_s256,
|
||||
})
|
||||
value.try_map(JsonWebKeyPublicParameters::try_from)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +124,74 @@ impl<P> JsonWebKey<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_map<M, O, E>(self, mapper: M) -> Result<JsonWebKey<O>, E>
|
||||
where
|
||||
M: FnOnce(P) -> Result<O, E>,
|
||||
{
|
||||
Ok(JsonWebKey {
|
||||
parameters: mapper(self.parameters)?,
|
||||
r#use: self.r#use,
|
||||
key_ops: self.key_ops,
|
||||
alg: self.alg,
|
||||
kid: self.kid,
|
||||
x5u: self.x5u,
|
||||
x5c: self.x5c,
|
||||
x5t: self.x5t,
|
||||
x5t_s256: self.x5t_s256,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn map<M, O>(self, mapper: M) -> JsonWebKey<O>
|
||||
where
|
||||
M: FnOnce(P) -> O,
|
||||
{
|
||||
JsonWebKey {
|
||||
parameters: mapper(self.parameters),
|
||||
r#use: self.r#use,
|
||||
key_ops: self.key_ops,
|
||||
alg: self.alg,
|
||||
kid: self.kid,
|
||||
x5u: self.x5u,
|
||||
x5c: self.x5c,
|
||||
x5t: self.x5t,
|
||||
x5t_s256: self.x5t_s256,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_cloned_map<M, O, E>(&self, mapper: M) -> Result<JsonWebKey<O>, E>
|
||||
where
|
||||
M: FnOnce(&P) -> Result<O, E>,
|
||||
{
|
||||
Ok(JsonWebKey {
|
||||
parameters: mapper(&self.parameters)?,
|
||||
r#use: self.r#use,
|
||||
key_ops: self.key_ops.clone(),
|
||||
alg: self.alg,
|
||||
kid: self.kid.clone(),
|
||||
x5u: self.x5u.clone(),
|
||||
x5c: self.x5c.clone(),
|
||||
x5t: self.x5t.clone(),
|
||||
x5t_s256: self.x5t_s256.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cloned_map<M, O>(&self, mapper: M) -> JsonWebKey<O>
|
||||
where
|
||||
M: FnOnce(&P) -> O,
|
||||
{
|
||||
JsonWebKey {
|
||||
parameters: mapper(&self.parameters),
|
||||
r#use: self.r#use,
|
||||
key_ops: self.key_ops.clone(),
|
||||
alg: self.alg,
|
||||
kid: self.kid.clone(),
|
||||
x5u: self.x5u.clone(),
|
||||
x5c: self.x5c.clone(),
|
||||
x5t: self.x5t.clone(),
|
||||
x5t_s256: self.x5t_s256.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn with_use(mut self, value: JsonWebKeyUse) -> Self {
|
||||
self.r#use = Some(value);
|
||||
@@ -199,6 +257,14 @@ pub struct JsonWebKeySet<P> {
|
||||
keys: Vec<JsonWebKey<P>>,
|
||||
}
|
||||
|
||||
impl<P> Default for JsonWebKeySet<P> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
keys: Vec::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type PublicJsonWebKeySet = JsonWebKeySet<self::public_parameters::JsonWebKeyPublicParameters>;
|
||||
pub type PrivateJsonWebKeySet =
|
||||
JsonWebKeySet<self::private_parameters::JsonWebKeyPrivateParameters>;
|
||||
@@ -229,6 +295,13 @@ impl<P> JsonWebKeySet<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> FromIterator<JsonWebKey<P>> for JsonWebKeySet<P> {
|
||||
fn from_iter<T: IntoIterator<Item = JsonWebKey<P>>>(iter: T) -> Self {
|
||||
let keys = iter.into_iter().collect();
|
||||
Self { keys }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@@ -12,181 +12,11 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use base64ct::{Base64UrlUnpadded, Encoding};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{SigningKeystore, VerifyingKeystore};
|
||||
|
||||
mod header;
|
||||
mod raw;
|
||||
mod signed;
|
||||
|
||||
pub use self::{header::JsonWebSignatureHeader, signed::Jwt};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct JsonWebTokenParts {
|
||||
payload: String,
|
||||
signature: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("failed to decode JWT")]
|
||||
pub enum JwtPartsDecodeError {
|
||||
#[error("no dots found in the JWT")]
|
||||
NoDots,
|
||||
|
||||
#[error("could not decode signature")]
|
||||
SignatureEncoding {
|
||||
#[from]
|
||||
inner: base64ct::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl FromStr for JsonWebTokenParts {
|
||||
type Err = JwtPartsDecodeError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (payload, signature) = s.rsplit_once('.').ok_or(JwtPartsDecodeError::NoDots)?;
|
||||
let signature = Base64UrlUnpadded::decode_vec(signature)?;
|
||||
let payload = payload.to_owned();
|
||||
Ok(Self { payload, signature })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("failed to serialize JWT")]
|
||||
pub enum JwtSerializeError {
|
||||
#[error("failed to serialize JWT header")]
|
||||
Header {
|
||||
#[source]
|
||||
inner: serde_json::Error,
|
||||
},
|
||||
|
||||
#[error("failed to serialize payload")]
|
||||
Payload {
|
||||
#[source]
|
||||
inner: serde_json::Error,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("failed to serialize JWT")]
|
||||
pub enum JwtSignatureError {
|
||||
Serialize {
|
||||
#[from]
|
||||
inner: JwtSerializeError,
|
||||
},
|
||||
|
||||
Sign {
|
||||
#[source]
|
||||
inner: anyhow::Error,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct DecodedJsonWebToken<T> {
|
||||
header: JsonWebSignatureHeader,
|
||||
payload: T,
|
||||
}
|
||||
|
||||
impl<T> DecodedJsonWebToken<T>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
fn serialize(&self) -> Result<String, JwtSerializeError> {
|
||||
let header = serde_json::to_vec(&self.header)
|
||||
.map_err(|inner| JwtSerializeError::Header { inner })?;
|
||||
let header = Base64UrlUnpadded::encode_string(&header);
|
||||
|
||||
let payload = serde_json::to_vec(&self.payload)
|
||||
.map_err(|inner| JwtSerializeError::Payload { inner })?;
|
||||
let payload = Base64UrlUnpadded::encode_string(&payload);
|
||||
|
||||
Ok(format!("{}.{}", header, payload))
|
||||
}
|
||||
|
||||
pub async fn sign<S: SigningKeystore>(
|
||||
&self,
|
||||
store: &S,
|
||||
) -> Result<JsonWebTokenParts, JwtSignatureError> {
|
||||
let payload = self.serialize()?;
|
||||
let signature = store
|
||||
.sign(&self.header, payload.as_bytes())
|
||||
.await
|
||||
.map_err(|inner| JwtSignatureError::Sign { inner })?;
|
||||
Ok(JsonWebTokenParts { payload, signature })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DecodedJsonWebToken<T> {
|
||||
pub fn new(header: JsonWebSignatureHeader, payload: T) -> Self {
|
||||
Self { header, payload }
|
||||
}
|
||||
|
||||
pub fn claims(&self) -> &T {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &JsonWebSignatureHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
pub fn split(self) -> (JsonWebSignatureHeader, T) {
|
||||
(self.header, self.payload)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FromStr for DecodedJsonWebToken<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (header, payload) = s
|
||||
.split_once('.')
|
||||
.ok_or_else(|| anyhow::anyhow!("invalid payload"))?;
|
||||
|
||||
let header = Base64UrlUnpadded::decode_vec(header)?;
|
||||
let header = serde_json::from_slice(&header)?;
|
||||
let payload = Base64UrlUnpadded::decode_vec(payload)?;
|
||||
let payload = serde_json::from_slice(&payload)?;
|
||||
Ok(Self { header, payload })
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonWebTokenParts {
|
||||
pub fn decode<T: DeserializeOwned>(&self) -> anyhow::Result<DecodedJsonWebToken<T>> {
|
||||
let decoded = self.payload.parse()?;
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
pub fn verify<S: VerifyingKeystore>(
|
||||
&self,
|
||||
header: &JsonWebSignatureHeader,
|
||||
store: &S,
|
||||
) -> S::Future {
|
||||
store.verify(header, self.payload.as_bytes(), &self.signature)
|
||||
}
|
||||
|
||||
pub async fn decode_and_verify<T: DeserializeOwned, S: VerifyingKeystore>(
|
||||
&self,
|
||||
store: &S,
|
||||
) -> anyhow::Result<DecodedJsonWebToken<T>>
|
||||
where
|
||||
S::Error: std::error::Error + Send + Sync + 'static,
|
||||
{
|
||||
let decoded = self.decode()?;
|
||||
self.verify(&decoded.header, store).await?;
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn serialize(&self) -> String {
|
||||
let payload = &self.payload;
|
||||
let signature = Base64UrlUnpadded::encode_string(&self.signature);
|
||||
format!("{}.{}", payload, signature)
|
||||
}
|
||||
}
|
||||
pub use self::{
|
||||
header::JsonWebSignatureHeader,
|
||||
signed::{Jwt, JwtDecodeError, JwtSignatureError, JwtVerificationError},
|
||||
};
|
||||
|
@@ -1,21 +0,0 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
mod static_keystore;
|
||||
mod traits;
|
||||
|
||||
pub use self::{
|
||||
static_keystore::StaticKeystore,
|
||||
traits::{SigningKeystore, VerifyingKeystore},
|
||||
};
|
@@ -1,413 +0,0 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
future::Ready,
|
||||
};
|
||||
|
||||
use anyhow::bail;
|
||||
use async_trait::async_trait;
|
||||
use base64ct::{Base64UrlUnpadded, Encoding};
|
||||
use digest::Digest;
|
||||
use ecdsa::{SigningKey, VerifyingKey};
|
||||
use mas_iana::jose::{JsonWebKeyUse, JsonWebSignatureAlg};
|
||||
use p256::{NistP256, PublicKey};
|
||||
use pkcs1::{DecodeRsaPrivateKey, EncodeRsaPublicKey};
|
||||
use pkcs8::{DecodePrivateKey, EncodePublicKey};
|
||||
use rsa::{PublicKey as _, RsaPrivateKey, RsaPublicKey};
|
||||
use sha2::{Sha256, Sha384, Sha512};
|
||||
use signature::{Signature, Signer, Verifier};
|
||||
|
||||
use super::{SigningKeystore, VerifyingKeystore};
|
||||
use crate::{
|
||||
jwk::{JsonWebKey, PublicJsonWebKeySet},
|
||||
JsonWebSignatureHeader,
|
||||
};
|
||||
|
||||
// Generate with
|
||||
// openssl genrsa 2048
|
||||
const TEST_RSA_PKCS1_PEM: &str = "-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEA1j7Y2CH6Ss8tgaNvcQPaRJKnCZD8ABqNPyKDWLQLph6Zi7gZ
|
||||
GqmRtTzMuevo2ezpkbCiQAPEp1ms022P92bB+uqG7xmzHTzbwLtnq3OAdjmrnaFV
|
||||
I4v89WHUsTXX9hiYOK5dOM81bNZ6muxWZ0L/xw4jVWe7xkqnp2Lluq0HknlzP5yJ
|
||||
UEikf5BkpX0iyIu2/X4r8YVp8uzG34l/8qBx6k3rO2VkOQOSybZj1oij5KZCusnu
|
||||
QjJLKWXCqJToWE6iVn+Q0N6ySDLgmJ7Zq0Sou/9N/oWKn94FOsouQgET5NuzoIFR
|
||||
qTb321fQ8gbqt/OupBbBKEo1qUU+cS77TD/AuQIDAQABAoIBAQDLSZzmD+93lnf+
|
||||
f36ZxOcRk/nNGPYUfx0xH+VzgHthJ73YFlozs1xflQ5JB/DM/4BsziZWCX1KsctM
|
||||
XrRxMt6y4GAidcc/4eQ+T1RCGfl1tKkDi/bGIOloSGjRsV5208V0WvZ3lh2CZUy2
|
||||
vbQKjUc3sFGUkzZYI7RLHosPA2mg78IVuSnqvNaU0TgA2KkaxWs6Ecr/ys80cUvj
|
||||
KKj04DmX5xaXwUKmz353i5gIt3aY3G5CAw5fU/ocDKR8nzVCpBAGbRRiUaVKIT06
|
||||
APSkLDTUnxSYtHtDJGHjgU/TsvAwTA92J3ue5Ysu9xTE+WyHA6Rgux7RQSD/wWHr
|
||||
LdRPwxPFAoGBAOytMPh/f2zKmotanjho0QNfhAUHoQUfPudYT0nnDceOsi1jYWbQ
|
||||
c/wPeQQC4Hp/pTUrkSIQPEz/hSxzZ6RPxxuGB8O94I0uLwQK4V1UwbgfsRa9zQzW
|
||||
n0kgKZ8w8h8B7qyiKyIAnZzvKtNEnKrzrct4HsN3OEoXTwuAUYlvWtQTAoGBAOe8
|
||||
0liNaH9V6ecZiojkRR1tiQkr/dCV+a13eXXaRA/8y/3wKCQ4idYncclQJTLKsAwW
|
||||
hHuDd4uLgtifREVIBD2jGdlznNr9HQNuZgwjuUoH+r1YLGgiMWeVYSr0m8lyDlQl
|
||||
BJKTAphrqo6VJWDAnM18v+by//yRleSjVMqZ3zmDAoGBAMpA0rl5EyagGON/g/hG
|
||||
sl8Ej+hQdazP38yJbfCEsATaD6+z3rei6Yr8mfjwkG5+iGrgmT0XzMAsF909ndMP
|
||||
jeIabqY6rBtZ3TnCJobAeG9lPctmVUVkX2h5QLhWdoJC/3iteNis2AQVam5yksOQ
|
||||
S/O16ew2BHdkZds5Q/SDoYXbAoGAK9tVZ8LjWu30hXMU/9FLr0USoTS9JWOszAKH
|
||||
byFuriPmq1lvD2PP2kK+yx2q3JD1fmQokIOR9Uvi6IJD1mTJwKyEcN3reppailKz
|
||||
Z2q/X15hOsJcLR0DgpoHuKxwa1B1m8Ehu2etHxGJRtC9MTFiu5T3cIrenXskBhBP
|
||||
NMSoNWcCgYAD3u3zdeVo3gVoxneS7GNVI2WBhjtqgNIbINuxGZvfztm7+vNPE6sQ
|
||||
VL8i+09uoM1H6sXbe2XXORmtW0j/6MmYhSoBXNdqWTNAiyNRhwEQtowqgl5R7PBu
|
||||
//QZTF1z62R9IKDMRG3f5Wn8e1Dys6tXBuG603g+Dkkc/km476mrgw==
|
||||
-----END RSA PRIVATE KEY-----";
|
||||
|
||||
// Generate with
|
||||
// openssl ecparam -genkey -name prime256v1 | openssl pkcs8 -topk8 -nocrypt
|
||||
const TEST_ECDSA_PKCS8_PEM: &str = "-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg+tHxet7G+uar2Cef
|
||||
iYPb7jv3uzncFtwJ7RhDOvEA0fChRANCAATCKn2AEqa9785k+TmwkeCvLub8XGrF
|
||||
ezE6bA/blaPVE3nu4SUVYKULRJQxNjeOSra8TQrlIS8e5ItbMn8Tv9KV
|
||||
-----END PRIVATE KEY-----";
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct StaticKeystore {
|
||||
rsa_keys: HashMap<String, rsa::RsaPrivateKey>,
|
||||
es256_keys: HashMap<String, SigningKey<NistP256>>,
|
||||
}
|
||||
|
||||
impl StaticKeystore {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
StaticKeystore::default()
|
||||
}
|
||||
|
||||
pub fn add_test_rsa_key(&mut self) -> anyhow::Result<()> {
|
||||
let rsa = RsaPrivateKey::from_pkcs1_pem(TEST_RSA_PKCS1_PEM)?;
|
||||
self.add_rsa_key(rsa)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_test_ecdsa_key(&mut self) -> anyhow::Result<()> {
|
||||
let ecdsa = SigningKey::from_pkcs8_pem(TEST_ECDSA_PKCS8_PEM)?;
|
||||
self.add_ecdsa_key(ecdsa)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_rsa_key(&mut self, key: rsa::RsaPrivateKey) -> anyhow::Result<()> {
|
||||
let pubkey: &RsaPublicKey = &key;
|
||||
let der = pubkey.to_pkcs1_der()?;
|
||||
let digest = {
|
||||
let mut digest = Sha256::new();
|
||||
digest.update(&der);
|
||||
digest.finalize()
|
||||
};
|
||||
// Truncate the digest to the 120 first bits
|
||||
let digest = &digest[0..15];
|
||||
let digest = Base64UrlUnpadded::encode_string(digest);
|
||||
let kid = format!("rsa-{}", digest);
|
||||
self.rsa_keys.insert(kid, key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_ecdsa_key(&mut self, key: SigningKey<NistP256>) -> anyhow::Result<()> {
|
||||
let pubkey: PublicKey = key.verifying_key().into();
|
||||
let der = EncodePublicKey::to_public_key_der(&pubkey)?;
|
||||
let digest = {
|
||||
let mut digest = Sha256::new();
|
||||
digest.update(&der);
|
||||
digest.finalize()
|
||||
};
|
||||
// Truncate the digest to the 120 first bits
|
||||
let digest = &digest[0..15];
|
||||
let digest = Base64UrlUnpadded::encode_string(digest);
|
||||
let kid = format!("ec-{}", digest);
|
||||
self.es256_keys.insert(kid, key);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_public_jwks(&self) -> PublicJsonWebKeySet {
|
||||
let rsa = self.rsa_keys.iter().map(|(kid, key)| {
|
||||
let pubkey = RsaPublicKey::from(key);
|
||||
JsonWebKey::new(pubkey.into())
|
||||
.with_kid(kid)
|
||||
.with_use(JsonWebKeyUse::Sig)
|
||||
});
|
||||
|
||||
let es256 = self.es256_keys.iter().map(|(kid, key)| {
|
||||
let pubkey = ecdsa::VerifyingKey::from(key);
|
||||
JsonWebKey::new(pubkey.into())
|
||||
.with_kid(kid)
|
||||
.with_use(JsonWebKeyUse::Sig)
|
||||
.with_alg(JsonWebSignatureAlg::Es256)
|
||||
});
|
||||
|
||||
let keys = rsa.chain(es256).collect();
|
||||
PublicJsonWebKeySet::new(keys)
|
||||
}
|
||||
|
||||
fn verify_sync(
|
||||
&self,
|
||||
header: &JsonWebSignatureHeader,
|
||||
payload: &[u8],
|
||||
signature: &[u8],
|
||||
) -> anyhow::Result<()> {
|
||||
let kid = header
|
||||
.kid()
|
||||
.ok_or_else(|| anyhow::anyhow!("missing kid claim in JWT header"))?;
|
||||
|
||||
// TODO: do the verification in a blocking task
|
||||
match header.alg() {
|
||||
JsonWebSignatureAlg::Rs256 => {
|
||||
let key = self
|
||||
.rsa_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find RSA key in key store"))?;
|
||||
|
||||
let pubkey = rsa::RsaPublicKey::from(key);
|
||||
|
||||
let digest = {
|
||||
let mut digest = Sha256::new();
|
||||
digest.update(&payload);
|
||||
digest.finalize()
|
||||
};
|
||||
|
||||
pubkey.verify(
|
||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
|
||||
&digest,
|
||||
signature,
|
||||
)?;
|
||||
}
|
||||
|
||||
JsonWebSignatureAlg::Rs384 => {
|
||||
let key = self
|
||||
.rsa_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find RSA key in key store"))?;
|
||||
|
||||
let pubkey = rsa::RsaPublicKey::from(key);
|
||||
|
||||
let digest = {
|
||||
let mut digest = Sha384::new();
|
||||
digest.update(&payload);
|
||||
digest.finalize()
|
||||
};
|
||||
|
||||
pubkey.verify(
|
||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_384)),
|
||||
&digest,
|
||||
signature,
|
||||
)?;
|
||||
}
|
||||
|
||||
JsonWebSignatureAlg::Rs512 => {
|
||||
let key = self
|
||||
.rsa_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find RSA key in key store"))?;
|
||||
|
||||
let pubkey = rsa::RsaPublicKey::from(key);
|
||||
|
||||
let digest = {
|
||||
let mut digest = Sha512::new();
|
||||
digest.update(&payload);
|
||||
digest.finalize()
|
||||
};
|
||||
|
||||
pubkey.verify(
|
||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_512)),
|
||||
&digest,
|
||||
signature,
|
||||
)?;
|
||||
}
|
||||
|
||||
JsonWebSignatureAlg::Es256 => {
|
||||
let key = self
|
||||
.es256_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("could not find ECDSA key in key store"))?;
|
||||
|
||||
let pubkey = VerifyingKey::from(key);
|
||||
let signature = ecdsa::Signature::from_bytes(signature)?;
|
||||
|
||||
pubkey.verify(payload, &signature)?;
|
||||
}
|
||||
_ => bail!("unsupported algorithm"),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SigningKeystore for StaticKeystore {
|
||||
fn supported_algorithms(&self) -> HashSet<JsonWebSignatureAlg> {
|
||||
let has_rsa = !self.rsa_keys.is_empty();
|
||||
let has_es256 = !self.es256_keys.is_empty();
|
||||
|
||||
let capacity = (if has_rsa { 3 } else { 0 }) + (if has_es256 { 1 } else { 0 });
|
||||
let mut algorithms = HashSet::with_capacity(capacity);
|
||||
|
||||
if has_rsa {
|
||||
algorithms.insert(JsonWebSignatureAlg::Rs256);
|
||||
algorithms.insert(JsonWebSignatureAlg::Rs384);
|
||||
algorithms.insert(JsonWebSignatureAlg::Rs512);
|
||||
}
|
||||
|
||||
if has_es256 {
|
||||
algorithms.insert(JsonWebSignatureAlg::Es256);
|
||||
}
|
||||
|
||||
algorithms
|
||||
}
|
||||
|
||||
async fn prepare_header(
|
||||
&self,
|
||||
alg: JsonWebSignatureAlg,
|
||||
) -> anyhow::Result<JsonWebSignatureHeader> {
|
||||
let header = JsonWebSignatureHeader::new(alg);
|
||||
|
||||
let kid = match alg {
|
||||
JsonWebSignatureAlg::Rs256
|
||||
| JsonWebSignatureAlg::Rs384
|
||||
| JsonWebSignatureAlg::Rs512 => self
|
||||
.rsa_keys
|
||||
.keys()
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("no RSA keys in keystore"))?,
|
||||
JsonWebSignatureAlg::Es256 => self
|
||||
.es256_keys
|
||||
.keys()
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("no ECDSA keys in keystore"))?,
|
||||
_ => bail!("unsupported algorithm"),
|
||||
};
|
||||
|
||||
Ok(header.with_kid(kid))
|
||||
}
|
||||
|
||||
async fn sign(&self, header: &JsonWebSignatureHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
||||
let kid = header
|
||||
.kid()
|
||||
.ok_or_else(|| anyhow::anyhow!("missing kid from the JWT header"))?;
|
||||
|
||||
// TODO: do the signing in a blocking task
|
||||
let signature = match header.alg() {
|
||||
JsonWebSignatureAlg::Rs256 => {
|
||||
let key = self
|
||||
.rsa_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("RSA key not found in key store"))?;
|
||||
|
||||
let digest = {
|
||||
let mut digest = Sha256::new();
|
||||
digest.update(&msg);
|
||||
digest.finalize()
|
||||
};
|
||||
|
||||
key.sign(
|
||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_256)),
|
||||
&digest,
|
||||
)?
|
||||
}
|
||||
|
||||
JsonWebSignatureAlg::Rs384 => {
|
||||
let key = self
|
||||
.rsa_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("RSA key not found in key store"))?;
|
||||
|
||||
let digest = {
|
||||
let mut digest = Sha384::new();
|
||||
digest.update(&msg);
|
||||
digest.finalize()
|
||||
};
|
||||
|
||||
key.sign(
|
||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_384)),
|
||||
&digest,
|
||||
)?
|
||||
}
|
||||
|
||||
JsonWebSignatureAlg::Rs512 => {
|
||||
let key = self
|
||||
.rsa_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("RSA key not found in key store"))?;
|
||||
|
||||
let digest = {
|
||||
let mut digest = Sha512::new();
|
||||
digest.update(&msg);
|
||||
digest.finalize()
|
||||
};
|
||||
|
||||
key.sign(
|
||||
rsa::PaddingScheme::new_pkcs1v15_sign(Some(rsa::Hash::SHA2_512)),
|
||||
&digest,
|
||||
)?
|
||||
}
|
||||
|
||||
JsonWebSignatureAlg::Es256 => {
|
||||
let key = self
|
||||
.es256_keys
|
||||
.get(kid)
|
||||
.ok_or_else(|| anyhow::anyhow!("ECDSA key not found in key store"))?;
|
||||
|
||||
let signature = key.try_sign(msg)?;
|
||||
let signature: &[u8] = signature.as_ref();
|
||||
signature.to_vec()
|
||||
}
|
||||
|
||||
_ => bail!("Unsupported algorithm"),
|
||||
};
|
||||
|
||||
Ok(signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl VerifyingKeystore for StaticKeystore {
|
||||
type Error = anyhow::Error;
|
||||
type Future = Ready<Result<(), Self::Error>>;
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
header: &JsonWebSignatureHeader,
|
||||
msg: &[u8],
|
||||
signature: &[u8],
|
||||
) -> Self::Future {
|
||||
std::future::ready(self.verify_sync(header, msg, signature))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_static_store() {
|
||||
let message = "this is the message to sign".as_bytes();
|
||||
let store = {
|
||||
let mut s = StaticKeystore::new();
|
||||
s.add_test_rsa_key().unwrap();
|
||||
s.add_test_ecdsa_key().unwrap();
|
||||
s
|
||||
};
|
||||
|
||||
for alg in [
|
||||
JsonWebSignatureAlg::Rs256,
|
||||
JsonWebSignatureAlg::Rs384,
|
||||
JsonWebSignatureAlg::Rs512,
|
||||
JsonWebSignatureAlg::Es256,
|
||||
] {
|
||||
let header = store.prepare_header(alg).await.unwrap();
|
||||
assert_eq!(header.alg(), alg);
|
||||
let signature = store.sign(&header, message).await.unwrap();
|
||||
store.verify(&header, message, &signature).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{collections::HashSet, future::Future};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use mas_iana::jose::JsonWebSignatureAlg;
|
||||
|
||||
use crate::JsonWebSignatureHeader;
|
||||
|
||||
#[async_trait]
|
||||
pub trait SigningKeystore {
|
||||
fn supported_algorithms(&self) -> HashSet<JsonWebSignatureAlg>;
|
||||
|
||||
async fn prepare_header(
|
||||
&self,
|
||||
alg: JsonWebSignatureAlg,
|
||||
) -> anyhow::Result<JsonWebSignatureHeader>;
|
||||
|
||||
async fn sign(&self, header: &JsonWebSignatureHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>>;
|
||||
}
|
||||
|
||||
pub trait VerifyingKeystore {
|
||||
type Error;
|
||||
type Future: Future<Output = Result<(), Self::Error>>;
|
||||
|
||||
fn verify(&self, header: &JsonWebSignatureHeader, msg: &[u8], signature: &[u8])
|
||||
-> Self::Future;
|
||||
}
|
@@ -19,14 +19,8 @@
|
||||
|
||||
pub mod claims;
|
||||
pub mod constraints;
|
||||
pub(crate) mod jwa;
|
||||
pub mod jwa;
|
||||
pub mod jwk;
|
||||
pub(crate) mod jwt;
|
||||
mod keystore;
|
||||
pub mod jwt;
|
||||
pub mod signer;
|
||||
pub mod verifier;
|
||||
|
||||
pub use self::{
|
||||
jwt::{DecodedJsonWebToken, JsonWebSignatureHeader, JsonWebTokenParts, Jwt, JwtSignatureError},
|
||||
keystore::{SigningKeystore, StaticKeystore, VerifyingKeystore},
|
||||
};
|
||||
|
@@ -36,6 +36,78 @@ pub enum Signer {
|
||||
Es256K { key: jwa::Es256KSigningKey },
|
||||
}
|
||||
|
||||
impl From<jwa::Hs256Key> for Signer {
|
||||
fn from(key: jwa::Hs256Key) -> Self {
|
||||
Self::Hs256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Hs384Key> for Signer {
|
||||
fn from(key: jwa::Hs384Key) -> Self {
|
||||
Self::Hs384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Hs512Key> for Signer {
|
||||
fn from(key: jwa::Hs512Key) -> Self {
|
||||
Self::Hs512 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Rs256SigningKey> for Signer {
|
||||
fn from(key: jwa::Rs256SigningKey) -> Self {
|
||||
Self::Rs256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Rs384SigningKey> for Signer {
|
||||
fn from(key: jwa::Rs384SigningKey) -> Self {
|
||||
Self::Rs384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Rs512SigningKey> for Signer {
|
||||
fn from(key: jwa::Rs512SigningKey) -> Self {
|
||||
Self::Rs512 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Ps256SigningKey> for Signer {
|
||||
fn from(key: jwa::Ps256SigningKey) -> Self {
|
||||
Self::Ps256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Ps384SigningKey> for Signer {
|
||||
fn from(key: jwa::Ps384SigningKey) -> Self {
|
||||
Self::Ps384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Ps512SigningKey> for Signer {
|
||||
fn from(key: jwa::Ps512SigningKey) -> Self {
|
||||
Self::Ps512 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Es256SigningKey> for Signer {
|
||||
fn from(key: jwa::Es256SigningKey) -> Self {
|
||||
Self::Es256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Es384SigningKey> for Signer {
|
||||
fn from(key: jwa::Es384SigningKey) -> Self {
|
||||
Self::Es384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Es256KSigningKey> for Signer {
|
||||
fn from(key: jwa::Es256KSigningKey) -> Self {
|
||||
Self::Es256K { key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SignerFromJwkError {
|
||||
#[error("invalid RSA key")]
|
||||
|
@@ -36,6 +36,78 @@ pub enum Verifier {
|
||||
Es256K { key: jwa::Es256KVerifyingKey },
|
||||
}
|
||||
|
||||
impl From<jwa::Hs256Key> for Verifier {
|
||||
fn from(key: jwa::Hs256Key) -> Self {
|
||||
Self::Hs256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Hs384Key> for Verifier {
|
||||
fn from(key: jwa::Hs384Key) -> Self {
|
||||
Self::Hs384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Hs512Key> for Verifier {
|
||||
fn from(key: jwa::Hs512Key) -> Self {
|
||||
Self::Hs512 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Rs256VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Rs256VerifyingKey) -> Self {
|
||||
Self::Rs256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Rs384VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Rs384VerifyingKey) -> Self {
|
||||
Self::Rs384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Rs512VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Rs512VerifyingKey) -> Self {
|
||||
Self::Rs512 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Ps256VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Ps256VerifyingKey) -> Self {
|
||||
Self::Ps256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Ps384VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Ps384VerifyingKey) -> Self {
|
||||
Self::Ps384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Ps512VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Ps512VerifyingKey) -> Self {
|
||||
Self::Ps512 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Es256VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Es256VerifyingKey) -> Self {
|
||||
Self::Es256 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Es384VerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Es384VerifyingKey) -> Self {
|
||||
Self::Es384 { key }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<jwa::Es256KVerifyingKey> for Verifier {
|
||||
fn from(key: jwa::Es256KVerifyingKey) -> Self {
|
||||
Self::Es256K { key }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerifierFromJwkError {
|
||||
#[error("invalid RSA key")]
|
||||
|
Reference in New Issue
Block a user