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
Sign all the things
This commit is contained in:
280
crates/jose/src/constraints.rs
Normal file
280
crates/jose/src/constraints.rs
Normal file
@@ -0,0 +1,280 @@
|
||||
// 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;
|
||||
|
||||
use futures_util::future::Either;
|
||||
use mas_iana::jose::{JsonWebKeyType, JsonWebKeyUse, JsonWebSignatureAlg};
|
||||
|
||||
use crate::JsonWebSignatureHeader;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Constraint<'a> {
|
||||
Alg {
|
||||
constraint_alg: JsonWebSignatureAlg,
|
||||
},
|
||||
|
||||
Algs {
|
||||
constraint_algs: &'a [JsonWebSignatureAlg],
|
||||
},
|
||||
|
||||
Kid {
|
||||
constraint_kid: &'a str,
|
||||
},
|
||||
|
||||
Use {
|
||||
constraint_use: JsonWebKeyUse,
|
||||
},
|
||||
|
||||
Kty {
|
||||
constraint_kty: JsonWebKeyType,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> Constraint<'a> {
|
||||
#[must_use]
|
||||
pub fn alg(constraint_alg: JsonWebSignatureAlg) -> Self {
|
||||
Constraint::Alg { constraint_alg }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn algs(constraint_algs: &'a [JsonWebSignatureAlg]) -> Self {
|
||||
Constraint::Algs { constraint_algs }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn kid(constraint_kid: &'a str) -> Self {
|
||||
Constraint::Kid { constraint_kid }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn use_(constraint_use: JsonWebKeyUse) -> Self {
|
||||
Constraint::Use { constraint_use }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn kty(constraint_kty: JsonWebKeyType) -> Self {
|
||||
Constraint::Kty { constraint_kty }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ConstraintDecision {
|
||||
Positive,
|
||||
Neutral,
|
||||
Negative,
|
||||
}
|
||||
|
||||
pub trait Constrainable {
|
||||
/// List of available algorithms for this key
|
||||
fn algs(&self) -> Option<Vec<JsonWebSignatureAlg>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Key ID (`kid`) of this key
|
||||
fn kid(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Usage specified for this key
|
||||
fn use_(&self) -> Option<JsonWebKeyUse> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Key type (`kty`) of this key
|
||||
fn kty(&self) -> JsonWebKeyType;
|
||||
}
|
||||
|
||||
impl<L, R> Constrainable for Either<L, R>
|
||||
where
|
||||
L: Constrainable,
|
||||
R: Constrainable,
|
||||
{
|
||||
fn algs(&self) -> Option<Vec<JsonWebSignatureAlg>> {
|
||||
match self {
|
||||
Either::Left(l) => l.algs(),
|
||||
Either::Right(r) => r.algs(),
|
||||
}
|
||||
}
|
||||
|
||||
fn kid(&self) -> Option<&str> {
|
||||
match self {
|
||||
Either::Left(l) => l.kid(),
|
||||
Either::Right(r) => r.kid(),
|
||||
}
|
||||
}
|
||||
|
||||
fn use_(&self) -> Option<JsonWebKeyUse> {
|
||||
match self {
|
||||
Either::Left(l) => l.use_(),
|
||||
Either::Right(r) => r.use_(),
|
||||
}
|
||||
}
|
||||
|
||||
fn kty(&self) -> JsonWebKeyType {
|
||||
match self {
|
||||
Either::Left(l) => l.kty(),
|
||||
Either::Right(r) => r.kty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Constraint<'a> {
|
||||
fn decide<T: Constrainable>(&self, constrainable: &T) -> ConstraintDecision {
|
||||
match self {
|
||||
Constraint::Alg { constraint_alg } => {
|
||||
if let Some(algs) = constrainable.algs() {
|
||||
if algs.contains(constraint_alg) {
|
||||
ConstraintDecision::Positive
|
||||
} else {
|
||||
ConstraintDecision::Negative
|
||||
}
|
||||
} else {
|
||||
ConstraintDecision::Neutral
|
||||
}
|
||||
}
|
||||
Constraint::Algs { constraint_algs } => {
|
||||
if let Some(algs) = constrainable.algs() {
|
||||
if algs.iter().any(|alg| constraint_algs.contains(alg)) {
|
||||
ConstraintDecision::Positive
|
||||
} else {
|
||||
ConstraintDecision::Negative
|
||||
}
|
||||
} else {
|
||||
ConstraintDecision::Neutral
|
||||
}
|
||||
}
|
||||
Constraint::Kid { constraint_kid } => {
|
||||
if let Some(kid) = constrainable.kid() {
|
||||
if kid == *constraint_kid {
|
||||
ConstraintDecision::Positive
|
||||
} else {
|
||||
ConstraintDecision::Negative
|
||||
}
|
||||
} else {
|
||||
ConstraintDecision::Neutral
|
||||
}
|
||||
}
|
||||
Constraint::Use { constraint_use } => {
|
||||
if let Some(use_) = constrainable.use_() {
|
||||
if use_ == *constraint_use {
|
||||
ConstraintDecision::Positive
|
||||
} else {
|
||||
ConstraintDecision::Negative
|
||||
}
|
||||
} else {
|
||||
ConstraintDecision::Neutral
|
||||
}
|
||||
}
|
||||
Constraint::Kty { constraint_kty } => {
|
||||
if *constraint_kty == constrainable.kty() {
|
||||
ConstraintDecision::Positive
|
||||
} else {
|
||||
ConstraintDecision::Negative
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ConstraintSet<'a> {
|
||||
constraints: HashSet<Constraint<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<Constraint<'a>> for ConstraintSet<'a> {
|
||||
fn from_iter<T: IntoIterator<Item = Constraint<'a>>>(iter: T) -> Self {
|
||||
Self {
|
||||
constraints: HashSet::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl<'a> ConstraintSet<'a> {
|
||||
pub fn new(constraints: impl IntoIterator<Item = Constraint<'a>>) -> Self {
|
||||
constraints.into_iter().collect()
|
||||
}
|
||||
|
||||
pub fn filter<'b, T: Constrainable, I: IntoIterator<Item = &'b T>>(
|
||||
&self,
|
||||
constrainables: I,
|
||||
) -> Vec<&'b T> {
|
||||
let mut selected = Vec::new();
|
||||
|
||||
'outer: for constrainable in constrainables {
|
||||
let mut score = 0;
|
||||
|
||||
for constraint in &self.constraints {
|
||||
match constraint.decide(constrainable) {
|
||||
ConstraintDecision::Positive => score += 1,
|
||||
ConstraintDecision::Neutral => {}
|
||||
// If any constraint was negative, don't add it to the candidates
|
||||
ConstraintDecision::Negative => break 'outer,
|
||||
}
|
||||
}
|
||||
|
||||
selected.push((score, constrainable));
|
||||
}
|
||||
|
||||
selected.sort_by_key(|(score, _)| *score);
|
||||
|
||||
selected
|
||||
.into_iter()
|
||||
.map(|(_score, constrainable)| constrainable)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn alg(mut self, constraint_alg: JsonWebSignatureAlg) -> Self {
|
||||
self.constraints.insert(Constraint::alg(constraint_alg));
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn algs(mut self, constraint_algs: &'a [JsonWebSignatureAlg]) -> Self {
|
||||
self.constraints.insert(Constraint::algs(constraint_algs));
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn kid(mut self, constraint_kid: &'a str) -> Self {
|
||||
self.constraints.insert(Constraint::kid(constraint_kid));
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn use_(mut self, constraint_use: JsonWebKeyUse) -> Self {
|
||||
self.constraints.insert(Constraint::use_(constraint_use));
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn kty(mut self, constraint_kty: JsonWebKeyType) -> Self {
|
||||
self.constraints.insert(Constraint::kty(constraint_kty));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a JsonWebSignatureHeader> for ConstraintSet<'a> {
|
||||
fn from(header: &'a JsonWebSignatureHeader) -> Self {
|
||||
let mut constraints = Self::default().alg(header.alg());
|
||||
|
||||
if let Some(kid) = header.kid() {
|
||||
constraints = constraints.kid(kid);
|
||||
}
|
||||
|
||||
constraints
|
||||
}
|
||||
}
|
109
crates/jose/src/hmac.rs
Normal file
109
crates/jose/src/hmac.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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::marker::PhantomData;
|
||||
|
||||
use digest::{
|
||||
crypto_common::BlockSizeUser,
|
||||
generic_array::{ArrayLength, GenericArray},
|
||||
Digest, Mac, OutputSizeUser,
|
||||
};
|
||||
use signature::{Signer, Verifier};
|
||||
use thiserror::Error;
|
||||
|
||||
pub struct Signature<S: ArrayLength<u8>> {
|
||||
signature: GenericArray<u8, S>,
|
||||
}
|
||||
|
||||
impl<S: ArrayLength<u8>> PartialEq for Signature<S> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.signature == other.signature
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ArrayLength<u8>> Eq for Signature<S> {}
|
||||
|
||||
impl<S: ArrayLength<u8>> std::fmt::Debug for Signature<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ArrayLength<u8>> signature::Signature for Signature<S> {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, signature::Error> {
|
||||
if bytes.len() != S::to_usize() {
|
||||
return Err(signature::Error::new());
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
signature: GenericArray::from_slice(bytes).clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: ArrayLength<u8>> AsRef<[u8]> for Signature<S> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.signature.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Hmac<D> {
|
||||
key: Vec<u8>,
|
||||
digest: PhantomData<D>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("invalid length")]
|
||||
pub struct InvalidLength;
|
||||
|
||||
impl<D> From<Vec<u8>> for Hmac<D> {
|
||||
fn from(key: Vec<u8>) -> Self {
|
||||
Self {
|
||||
key,
|
||||
digest: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Digest + BlockSizeUser>
|
||||
Signer<Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>> for Hmac<D>
|
||||
{
|
||||
fn try_sign(
|
||||
&self,
|
||||
msg: &[u8],
|
||||
) -> Result<Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>, signature::Error>
|
||||
{
|
||||
let mut mac = <hmac::SimpleHmac<D> as Mac>::new_from_slice(&self.key)
|
||||
.map_err(signature::Error::from_source)?;
|
||||
mac.update(msg);
|
||||
let signature = mac.finalize().into_bytes();
|
||||
Ok(Signature { signature })
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Digest + BlockSizeUser>
|
||||
Verifier<Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>> for Hmac<D>
|
||||
{
|
||||
fn verify(
|
||||
&self,
|
||||
msg: &[u8],
|
||||
signature: &Signature<<hmac::SimpleHmac<D> as OutputSizeUser>::OutputSize>,
|
||||
) -> Result<(), signature::Error> {
|
||||
let new_signature = self.try_sign(msg)?;
|
||||
if &new_signature != signature {
|
||||
return Err(signature::Error::new());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -30,6 +30,8 @@ use serde_with::{
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use crate::constraints::Constrainable;
|
||||
|
||||
#[serde_as]
|
||||
#[skip_serializing_none]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
@@ -109,20 +111,6 @@ impl JsonWebKey {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn kty(&self) -> JsonWebKeyType {
|
||||
match self.parameters {
|
||||
JsonWebKeyParameters::Ec { .. } => JsonWebKeyType::Ec,
|
||||
JsonWebKeyParameters::Rsa { .. } => JsonWebKeyType::Rsa,
|
||||
JsonWebKeyParameters::Okp { .. } => JsonWebKeyType::Okp,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn kid(&self) -> Option<&str> {
|
||||
self.kid.as_deref()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub const fn alg(&self) -> Option<JsonWebSignatureAlg> {
|
||||
self.alg
|
||||
@@ -134,6 +122,58 @@ impl JsonWebKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl Constrainable for JsonWebKey {
|
||||
fn kid(&self) -> Option<&str> {
|
||||
self.kid.as_deref()
|
||||
}
|
||||
|
||||
fn kty(&self) -> JsonWebKeyType {
|
||||
match self.parameters {
|
||||
JsonWebKeyParameters::Ec { .. } => JsonWebKeyType::Ec,
|
||||
JsonWebKeyParameters::Rsa { .. } => JsonWebKeyType::Rsa,
|
||||
JsonWebKeyParameters::Okp { .. } => JsonWebKeyType::Okp,
|
||||
}
|
||||
}
|
||||
|
||||
fn algs(&self) -> Option<Vec<JsonWebSignatureAlg>> {
|
||||
if let Some(alg) = self.alg {
|
||||
Some(vec![alg])
|
||||
} else {
|
||||
match self.parameters {
|
||||
JsonWebKeyParameters::Rsa { .. } => Some(vec![
|
||||
JsonWebSignatureAlg::Rs256,
|
||||
JsonWebSignatureAlg::Rs384,
|
||||
JsonWebSignatureAlg::Rs512,
|
||||
JsonWebSignatureAlg::Ps256,
|
||||
JsonWebSignatureAlg::Ps384,
|
||||
JsonWebSignatureAlg::Ps512,
|
||||
]),
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::P256,
|
||||
..
|
||||
} => Some(vec![JsonWebSignatureAlg::Es256]),
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::P384,
|
||||
..
|
||||
} => Some(vec![JsonWebSignatureAlg::Es384]),
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::P521,
|
||||
..
|
||||
} => Some(vec![JsonWebSignatureAlg::Es512]),
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::Secp256K1,
|
||||
..
|
||||
} => Some(vec![JsonWebSignatureAlg::Es256K]),
|
||||
JsonWebKeyParameters::Okp { .. } => Some(vec![JsonWebSignatureAlg::EdDsa]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn use_(&self) -> Option<JsonWebKeyUse> {
|
||||
self.r#use
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct JsonWebKeySet {
|
||||
keys: Vec<JsonWebKey>,
|
||||
@@ -251,10 +291,11 @@ impl From<rsa::RsaPublicKey> for JsonWebKeyParameters {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::constraints::ConstraintSet;
|
||||
|
||||
#[test]
|
||||
fn load_google_keys() {
|
||||
let jwks = r#"{
|
||||
let jwks = serde_json::json!({
|
||||
"keys": [
|
||||
{
|
||||
"alg": "RS256",
|
||||
@@ -273,19 +314,33 @@ mod tests {
|
||||
"alg": "RS256"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
});
|
||||
|
||||
let jwks: JsonWebKeySet = serde_json::from_str(jwks).unwrap();
|
||||
let jwks: JsonWebKeySet = serde_json::from_value(jwks).unwrap();
|
||||
// Both keys are RSA public keys
|
||||
for jwk in jwks.keys {
|
||||
rsa::RsaPublicKey::try_from(jwk.parameters).unwrap();
|
||||
for jwk in &jwks.keys {
|
||||
rsa::RsaPublicKey::try_from(jwk.parameters.clone()).unwrap();
|
||||
}
|
||||
|
||||
let constraints = ConstraintSet::default()
|
||||
.use_(JsonWebKeyUse::Sig)
|
||||
.kty(JsonWebKeyType::Rsa)
|
||||
.alg(JsonWebSignatureAlg::Rs256);
|
||||
let candidates = constraints.filter(&jwks.keys);
|
||||
assert_eq!(candidates.len(), 2);
|
||||
|
||||
let constraints = ConstraintSet::default()
|
||||
.use_(JsonWebKeyUse::Sig)
|
||||
.kty(JsonWebKeyType::Rsa)
|
||||
.kid("03e84aed4ef4431014e8617567864c4efaaaede9");
|
||||
let candidates = constraints.filter(&jwks.keys);
|
||||
assert_eq!(candidates.len(), 1);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
#[test]
|
||||
fn load_keycloak_keys() {
|
||||
let jwks = r#"{
|
||||
let jwks = serde_json::json!({
|
||||
"keys": [
|
||||
{
|
||||
"kid": "SuGUPE9Sr-1Gha2NLse33r5NQu3XoS_I3Qds3bcmfQE",
|
||||
@@ -393,9 +448,9 @@ mod tests {
|
||||
"y": "Vu3rTFNn_9zWTki95UGT1Bd9PN84KDXmttCrJ1bsYHTWQCaEONk8iwA3U6mEDrg4xtZSTXXKCFdFP13ONWB9oZ4"
|
||||
}
|
||||
]
|
||||
}"#;
|
||||
});
|
||||
|
||||
let jwks: JsonWebKeySet = serde_json::from_str(jwks).unwrap();
|
||||
let jwks: JsonWebKeySet = serde_json::from_value(jwks).unwrap();
|
||||
// The first 6 keys are RSA, 7th is P-256
|
||||
let mut keys = jwks.keys.into_iter();
|
||||
rsa::RsaPublicKey::try_from(keys.next().unwrap().parameters).unwrap();
|
||||
|
@@ -115,14 +115,17 @@ where
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
let raw = RawJwt::try_from(value)?;
|
||||
|
||||
let header_reader =
|
||||
base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.header().as_bytes())
|
||||
.map_err(JwtDecodeError::decode_header)?;
|
||||
let header =
|
||||
Base64UrlUnpadded::decode_vec(raw.header()).map_err(JwtDecodeError::decode_header)?;
|
||||
let header = serde_json::from_slice(&header).map_err(JwtDecodeError::deserialize_header)?;
|
||||
serde_json::from_reader(header_reader).map_err(JwtDecodeError::deserialize_header)?;
|
||||
|
||||
let payload_reader =
|
||||
base64ct::Decoder::<'_, Base64UrlUnpadded>::new(raw.payload().as_bytes())
|
||||
.map_err(JwtDecodeError::decode_payload)?;
|
||||
let payload =
|
||||
Base64UrlUnpadded::decode_vec(raw.payload()).map_err(JwtDecodeError::decode_payload)?;
|
||||
let payload =
|
||||
serde_json::from_slice(&payload).map_err(JwtDecodeError::deserialize_payload)?;
|
||||
serde_json::from_reader(payload_reader).map_err(JwtDecodeError::deserialize_payload)?;
|
||||
|
||||
let signature = Base64UrlUnpadded::decode_vec(raw.signature())
|
||||
.map_err(JwtDecodeError::decode_signature)?;
|
||||
@@ -162,6 +165,14 @@ impl JwtVerificationError {
|
||||
}
|
||||
|
||||
impl<'a, T> Jwt<'a, T> {
|
||||
pub fn header(&self) -> &JsonWebSignatureHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
pub fn payload(&self) -> &T {
|
||||
&self.payload
|
||||
}
|
||||
|
||||
pub fn verify<K, S>(&self, key: &K) -> Result<(), JwtVerificationError>
|
||||
where
|
||||
K: Verifier<S>,
|
||||
|
@@ -21,7 +21,10 @@ use sha2::{Sha256, Sha384, Sha512};
|
||||
use signature::{Signature, Verifier};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader, VerifyingKeystore};
|
||||
use crate::{
|
||||
constraints::Constrainable, JsonWebKey, JsonWebKeySet, JsonWebSignatureHeader,
|
||||
VerifyingKeystore,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
|
@@ -18,9 +18,13 @@
|
||||
#![allow(clippy::missing_errors_doc, clippy::module_name_repetitions)]
|
||||
|
||||
pub mod claims;
|
||||
pub mod constraints;
|
||||
pub mod hmac;
|
||||
pub(crate) mod jwk;
|
||||
pub(crate) mod jwt;
|
||||
mod keystore;
|
||||
pub(crate) mod rsa;
|
||||
pub mod verifier;
|
||||
|
||||
pub use futures_util::future::Either;
|
||||
|
||||
|
146
crates/jose/src/rsa.rs
Normal file
146
crates/jose/src/rsa.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
// 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.
|
||||
|
||||
// This is a temporary wrapper until the RSA crate actually hashes the input
|
||||
// See <https://github.com/RustCrypto/RSA/pull/174#issuecomment-1227330296>
|
||||
|
||||
pub(crate) mod pkcs1v15 {
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use digest::Digest;
|
||||
use rsa::RsaPublicKey;
|
||||
use sha2::{Sha256, Sha384, Sha512};
|
||||
|
||||
pub struct VerifyingKey<H> {
|
||||
inner: rsa::pkcs1v15::VerifyingKey,
|
||||
hash: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl From<RsaPublicKey> for VerifyingKey<Sha256> {
|
||||
fn from(key: RsaPublicKey) -> Self {
|
||||
let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_256);
|
||||
ensure_verifier(Self {
|
||||
inner,
|
||||
hash: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RsaPublicKey> for VerifyingKey<Sha384> {
|
||||
fn from(key: RsaPublicKey) -> Self {
|
||||
let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_384);
|
||||
ensure_verifier(Self {
|
||||
inner,
|
||||
hash: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RsaPublicKey> for VerifyingKey<Sha512> {
|
||||
fn from(key: RsaPublicKey) -> Self {
|
||||
let inner = rsa::pkcs1v15::VerifyingKey::new_with_hash(key, rsa::Hash::SHA2_512);
|
||||
ensure_verifier(Self {
|
||||
inner,
|
||||
hash: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ensure_verifier<T>(t: T) -> T
|
||||
where
|
||||
T: signature::Verifier<rsa::pkcs1v15::Signature>,
|
||||
{
|
||||
t
|
||||
}
|
||||
|
||||
impl<H> signature::Verifier<rsa::pkcs1v15::Signature> for VerifyingKey<H>
|
||||
where
|
||||
H: Digest,
|
||||
{
|
||||
fn verify(
|
||||
&self,
|
||||
msg: &[u8],
|
||||
signature: &rsa::pkcs1v15::Signature,
|
||||
) -> Result<(), signature::Error> {
|
||||
let digest = H::digest(msg);
|
||||
self.inner.verify(&digest, signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod pss {
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use digest::Digest;
|
||||
use rsa::RsaPublicKey;
|
||||
use sha2::{Sha256, Sha384, Sha512};
|
||||
|
||||
pub struct VerifyingKey<H> {
|
||||
inner: rsa::pss::VerifyingKey,
|
||||
hash: PhantomData<H>,
|
||||
}
|
||||
|
||||
impl From<RsaPublicKey> for VerifyingKey<Sha256> {
|
||||
fn from(key: RsaPublicKey) -> Self {
|
||||
let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha256::new()));
|
||||
ensure_verifier(Self {
|
||||
inner,
|
||||
hash: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RsaPublicKey> for VerifyingKey<Sha384> {
|
||||
fn from(key: RsaPublicKey) -> Self {
|
||||
let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha384::new()));
|
||||
ensure_verifier(Self {
|
||||
inner,
|
||||
hash: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RsaPublicKey> for VerifyingKey<Sha512> {
|
||||
fn from(key: RsaPublicKey) -> Self {
|
||||
let inner = rsa::pss::VerifyingKey::new(key, Box::new(Sha512::new()));
|
||||
ensure_verifier(Self {
|
||||
inner,
|
||||
hash: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ensure_verifier<T>(t: T) -> T
|
||||
where
|
||||
T: signature::Verifier<rsa::pss::Signature>,
|
||||
{
|
||||
t
|
||||
}
|
||||
|
||||
impl<H> signature::Verifier<rsa::pss::Signature> for VerifyingKey<H>
|
||||
where
|
||||
H: Digest,
|
||||
{
|
||||
fn verify(
|
||||
&self,
|
||||
msg: &[u8],
|
||||
signature: &rsa::pss::Signature,
|
||||
) -> Result<(), signature::Error> {
|
||||
let digest = H::digest(msg);
|
||||
self.inner.verify(&digest, signature)
|
||||
}
|
||||
}
|
||||
}
|
330
crates/jose/src/verifier.rs
Normal file
330
crates/jose/src/verifier.rs
Normal file
@@ -0,0 +1,330 @@
|
||||
// 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 mas_iana::jose::{JsonWebKeyEcEllipticCurve, JsonWebSignatureAlg};
|
||||
use rsa::BigUint;
|
||||
use sha2::{Sha256, Sha384, Sha512};
|
||||
use signature::Signature;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::jwk::JsonWebKeyParameters;
|
||||
|
||||
pub enum Verifier {
|
||||
Hs256 {
|
||||
key: crate::hmac::Hmac<Sha256>,
|
||||
},
|
||||
Hs384 {
|
||||
key: crate::hmac::Hmac<Sha384>,
|
||||
},
|
||||
Hs512 {
|
||||
key: crate::hmac::Hmac<Sha512>,
|
||||
},
|
||||
Rs256 {
|
||||
key: crate::rsa::pkcs1v15::VerifyingKey<Sha256>,
|
||||
},
|
||||
Rs384 {
|
||||
key: crate::rsa::pkcs1v15::VerifyingKey<Sha384>,
|
||||
},
|
||||
Rs512 {
|
||||
key: crate::rsa::pkcs1v15::VerifyingKey<Sha512>,
|
||||
},
|
||||
Ps256 {
|
||||
key: crate::rsa::pss::VerifyingKey<Sha256>,
|
||||
},
|
||||
Ps384 {
|
||||
key: crate::rsa::pss::VerifyingKey<Sha384>,
|
||||
},
|
||||
Ps512 {
|
||||
key: crate::rsa::pss::VerifyingKey<Sha512>,
|
||||
},
|
||||
Es256 {
|
||||
key: ecdsa::VerifyingKey<p256::NistP256>,
|
||||
},
|
||||
Es384 {
|
||||
key: ecdsa::VerifyingKey<p384::NistP384>,
|
||||
},
|
||||
Es256K {
|
||||
key: ecdsa::VerifyingKey<k256::Secp256k1>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerifierFromJwkError {
|
||||
#[error("invalid RSA key")]
|
||||
InvalidRsaKey {
|
||||
#[from]
|
||||
inner: rsa::errors::Error,
|
||||
},
|
||||
|
||||
#[error("invalid elliptic curve key")]
|
||||
InvalidEcKey {
|
||||
#[from]
|
||||
inner: ecdsa::Error,
|
||||
},
|
||||
|
||||
#[error("invalid curve parameter X")]
|
||||
InvalidCurveParameterX,
|
||||
|
||||
#[error("invalid curve parameter Y")]
|
||||
InvalidCurveParameterY,
|
||||
|
||||
#[error("algorithm {algorithm} is not supported")]
|
||||
UnsupportedAlgorithm { algorithm: JsonWebSignatureAlg },
|
||||
|
||||
#[error("key is not suitable for algorithm {algorithm}")]
|
||||
KeyNotSuitable { algorithm: JsonWebSignatureAlg },
|
||||
}
|
||||
#[derive(Debug, Error)]
|
||||
pub enum VerifierFromOctError {
|
||||
#[error("algorithm {algorithm} is not supported")]
|
||||
UnsupportedAlgorithm { algorithm: JsonWebSignatureAlg },
|
||||
}
|
||||
|
||||
impl Verifier {
|
||||
pub fn for_oct_and_alg(
|
||||
key: Vec<u8>,
|
||||
alg: JsonWebSignatureAlg,
|
||||
) -> Result<Self, VerifierFromOctError> {
|
||||
match alg {
|
||||
JsonWebSignatureAlg::Hs256 => Ok(Self::Hs256 { key: key.into() }),
|
||||
JsonWebSignatureAlg::Hs384 => Ok(Self::Hs384 { key: key.into() }),
|
||||
JsonWebSignatureAlg::Hs512 => Ok(Self::Hs512 { key: key.into() }),
|
||||
algorithm => Err(VerifierFromOctError::UnsupportedAlgorithm { algorithm }),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn for_jwk_and_alg(
|
||||
key: &JsonWebKeyParameters,
|
||||
alg: JsonWebSignatureAlg,
|
||||
) -> Result<Self, VerifierFromJwkError> {
|
||||
match (key, alg) {
|
||||
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs256) => {
|
||||
let n = BigUint::from_bytes_be(n);
|
||||
let e = BigUint::from_bytes_be(e);
|
||||
let key = rsa::RsaPublicKey::new(n, e)?;
|
||||
Ok(Self::Rs256 { key: key.into() })
|
||||
}
|
||||
|
||||
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs384) => {
|
||||
let n = BigUint::from_bytes_be(n);
|
||||
let e = BigUint::from_bytes_be(e);
|
||||
let key = rsa::RsaPublicKey::new(n, e)?;
|
||||
Ok(Self::Rs384 { key: key.into() })
|
||||
}
|
||||
|
||||
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Rs512) => {
|
||||
let n = BigUint::from_bytes_be(n);
|
||||
let e = BigUint::from_bytes_be(e);
|
||||
let key = rsa::RsaPublicKey::new(n, e)?;
|
||||
Ok(Self::Rs512 { key: key.into() })
|
||||
}
|
||||
|
||||
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps256) => {
|
||||
let n = BigUint::from_bytes_be(n);
|
||||
let e = BigUint::from_bytes_be(e);
|
||||
let key = rsa::RsaPublicKey::new(n, e)?;
|
||||
Ok(Self::Ps256 { key: key.into() })
|
||||
}
|
||||
|
||||
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps384) => {
|
||||
let n = BigUint::from_bytes_be(n);
|
||||
let e = BigUint::from_bytes_be(e);
|
||||
let key = rsa::RsaPublicKey::new(n, e)?;
|
||||
Ok(Self::Ps384 { key: key.into() })
|
||||
}
|
||||
|
||||
(JsonWebKeyParameters::Rsa { n, e }, JsonWebSignatureAlg::Ps512) => {
|
||||
let n = BigUint::from_bytes_be(n);
|
||||
let e = BigUint::from_bytes_be(e);
|
||||
let key = rsa::RsaPublicKey::new(n, e)?;
|
||||
Ok(Self::Ps512 { key: key.into() })
|
||||
}
|
||||
|
||||
(
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::P256,
|
||||
x,
|
||||
y,
|
||||
},
|
||||
JsonWebSignatureAlg::Es256,
|
||||
) => {
|
||||
let x: &[u8; 32] = x
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?;
|
||||
let y: &[u8; 32] = y
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?;
|
||||
|
||||
let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false);
|
||||
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
|
||||
|
||||
Ok(Self::Es256 { key })
|
||||
}
|
||||
|
||||
(
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::P384,
|
||||
x,
|
||||
y,
|
||||
},
|
||||
JsonWebSignatureAlg::Es384,
|
||||
) => {
|
||||
let x: &[u8; 48] = x
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?;
|
||||
let y: &[u8; 48] = y
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?;
|
||||
|
||||
let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false);
|
||||
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
|
||||
|
||||
Ok(Self::Es384 { key })
|
||||
}
|
||||
|
||||
(
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::P521,
|
||||
x: _,
|
||||
y: _,
|
||||
},
|
||||
JsonWebSignatureAlg::Es512,
|
||||
) => Err(VerifierFromJwkError::UnsupportedAlgorithm {
|
||||
algorithm: JsonWebSignatureAlg::Es512,
|
||||
}),
|
||||
|
||||
(
|
||||
JsonWebKeyParameters::Ec {
|
||||
crv: JsonWebKeyEcEllipticCurve::Secp256K1,
|
||||
x,
|
||||
y,
|
||||
},
|
||||
JsonWebSignatureAlg::Es256K,
|
||||
) => {
|
||||
let x: &[u8; 32] = x
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterX)?;
|
||||
let y: &[u8; 32] = y
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.map_err(|_| VerifierFromJwkError::InvalidCurveParameterY)?;
|
||||
|
||||
let point = sec1::EncodedPoint::from_affine_coordinates(x.into(), y.into(), false);
|
||||
let key = ecdsa::VerifyingKey::from_encoded_point(&point)?;
|
||||
|
||||
Ok(Self::Es256K { key })
|
||||
}
|
||||
|
||||
(JsonWebKeyParameters::Okp { crv: _, x: _ }, JsonWebSignatureAlg::EdDsa) => {
|
||||
Err(VerifierFromJwkError::UnsupportedAlgorithm {
|
||||
algorithm: JsonWebSignatureAlg::EdDsa,
|
||||
})
|
||||
}
|
||||
|
||||
(_, algorithm) => Err(VerifierFromJwkError::KeyNotSuitable { algorithm }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenericSignature {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for GenericSignature {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.bytes
|
||||
}
|
||||
}
|
||||
|
||||
impl signature::Signature for GenericSignature {
|
||||
fn from_bytes(bytes: &[u8]) -> Result<Self, signature::Error> {
|
||||
Ok(Self {
|
||||
bytes: bytes.to_vec(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl signature::Verifier<GenericSignature> for Verifier {
|
||||
fn verify(&self, msg: &[u8], signature: &GenericSignature) -> Result<(), signature::Error> {
|
||||
match self {
|
||||
Verifier::Hs256 { key } => {
|
||||
let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Hs384 { key } => {
|
||||
let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Hs512 { key } => {
|
||||
let signature = crate::hmac::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Rs256 { key } => {
|
||||
let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Rs384 { key } => {
|
||||
let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Rs512 { key } => {
|
||||
let signature = rsa::pkcs1v15::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Ps256 { key } => {
|
||||
let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Ps384 { key } => {
|
||||
let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Ps512 { key } => {
|
||||
let signature = rsa::pss::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Es256 { key } => {
|
||||
let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Es384 { key } => {
|
||||
let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
Verifier::Es256K { key } => {
|
||||
let signature = ecdsa::Signature::from_bytes(signature.as_bytes())?;
|
||||
key.verify(msg, &signature)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user