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
jose: split the keystores in different modules
This commit is contained in:
23
crates/jose/src/keystore/mod.rs
Normal file
23
crates/jose/src/keystore/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// 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 shared_secret;
|
||||||
|
mod static_keystore;
|
||||||
|
mod traits;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
shared_secret::SharedSecret,
|
||||||
|
static_keystore::StaticKeystore,
|
||||||
|
traits::{ExportJwks, SigningKeystore, VerifyingKeystore},
|
||||||
|
};
|
134
crates/jose/src/keystore/shared_secret.rs
Normal file
134
crates/jose/src/keystore/shared_secret.rs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// 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 anyhow::bail;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
|
use sha2::{Sha256, Sha384, Sha512};
|
||||||
|
|
||||||
|
use super::{SigningKeystore, VerifyingKeystore};
|
||||||
|
use crate::{JsonWebSignatureAlgorithm, JwtHeader};
|
||||||
|
|
||||||
|
pub struct SharedSecret<'a> {
|
||||||
|
inner: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SharedSecret<'a> {
|
||||||
|
pub fn new(source: &'a impl AsRef<[u8]>) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: source.as_ref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'a> SigningKeystore for &SharedSecret<'a> {
|
||||||
|
async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result<JwtHeader> {
|
||||||
|
if !matches!(
|
||||||
|
alg,
|
||||||
|
JsonWebSignatureAlgorithm::Hs256
|
||||||
|
| JsonWebSignatureAlgorithm::Hs384
|
||||||
|
| JsonWebSignatureAlgorithm::Hs512,
|
||||||
|
) {
|
||||||
|
bail!("unsupported algorithm")
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(JwtHeader::new(alg))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn sign(self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
||||||
|
// TODO: do the signing in a blocking task
|
||||||
|
// TODO: should we bail out if the key is too small?
|
||||||
|
let signature = match header.alg() {
|
||||||
|
JsonWebSignatureAlgorithm::Hs256 => {
|
||||||
|
let mut mac = Hmac::<Sha256>::new_from_slice(self.inner)?;
|
||||||
|
mac.update(msg);
|
||||||
|
mac.finalize().into_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonWebSignatureAlgorithm::Hs384 => {
|
||||||
|
let mut mac = Hmac::<Sha384>::new_from_slice(self.inner)?;
|
||||||
|
mac.update(msg);
|
||||||
|
mac.finalize().into_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonWebSignatureAlgorithm::Hs512 => {
|
||||||
|
let mut mac = Hmac::<Sha512>::new_from_slice(self.inner)?;
|
||||||
|
mac.update(msg);
|
||||||
|
mac.finalize().into_bytes().to_vec()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => bail!("unsupported algorithm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<'a> VerifyingKeystore for &SharedSecret<'a> {
|
||||||
|
async fn verify(
|
||||||
|
self,
|
||||||
|
header: &JwtHeader,
|
||||||
|
payload: &[u8],
|
||||||
|
signature: &[u8],
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
// TODO: do the verification in a blocking task
|
||||||
|
match header.alg() {
|
||||||
|
JsonWebSignatureAlgorithm::Hs256 => {
|
||||||
|
let mut mac = Hmac::<Sha256>::new_from_slice(self.inner)?;
|
||||||
|
mac.update(payload);
|
||||||
|
mac.verify(signature.try_into()?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonWebSignatureAlgorithm::Hs384 => {
|
||||||
|
let mut mac = Hmac::<Sha384>::new_from_slice(self.inner)?;
|
||||||
|
mac.update(payload);
|
||||||
|
mac.verify(signature.try_into()?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonWebSignatureAlgorithm::Hs512 => {
|
||||||
|
let mut mac = Hmac::<Sha512>::new_from_slice(self.inner)?;
|
||||||
|
mac.update(payload);
|
||||||
|
mac.verify(signature.try_into()?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => bail!("unsupported algorithm"),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_shared_secret() {
|
||||||
|
let secret = "super-complicated-secret-that-should-be-big-enough-for-sha512";
|
||||||
|
let message = "this is the message to sign".as_bytes();
|
||||||
|
let store = SharedSecret::new(&secret);
|
||||||
|
for alg in [
|
||||||
|
JsonWebSignatureAlgorithm::Hs256,
|
||||||
|
JsonWebSignatureAlgorithm::Hs384,
|
||||||
|
JsonWebSignatureAlgorithm::Hs512,
|
||||||
|
] {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -19,7 +19,6 @@ use async_trait::async_trait;
|
|||||||
use base64ct::{Base64UrlUnpadded, Encoding};
|
use base64ct::{Base64UrlUnpadded, Encoding};
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use ecdsa::VerifyingKey;
|
use ecdsa::VerifyingKey;
|
||||||
use hmac::{Hmac, Mac};
|
|
||||||
use p256::{NistP256, PublicKey};
|
use p256::{NistP256, PublicKey};
|
||||||
use pkcs1::EncodeRsaPublicKey;
|
use pkcs1::EncodeRsaPublicKey;
|
||||||
use pkcs8::EncodePublicKey;
|
use pkcs8::EncodePublicKey;
|
||||||
@@ -27,118 +26,12 @@ use rsa::{PublicKey as _, RsaPublicKey};
|
|||||||
use sha2::{Sha256, Sha384, Sha512};
|
use sha2::{Sha256, Sha384, Sha512};
|
||||||
use signature::{Signature, Signer, Verifier};
|
use signature::{Signature, Signer, Verifier};
|
||||||
|
|
||||||
|
use super::{ExportJwks, SigningKeystore, VerifyingKeystore};
|
||||||
use crate::{
|
use crate::{
|
||||||
iana::JsonWebSignatureAlgorithm, JsonWebKey, JsonWebKeyOperation, JsonWebKeySet, JwtHeader,
|
iana::{JsonWebKeyOperation, JsonWebSignatureAlgorithm},
|
||||||
|
JsonWebKey, JsonWebKeySet, JwtHeader,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait SigningKeystore {
|
|
||||||
async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result<JwtHeader>;
|
|
||||||
|
|
||||||
async fn sign(self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait VerifyingKeystore {
|
|
||||||
async fn verify(self, header: &JwtHeader, msg: &[u8], signature: &[u8]) -> anyhow::Result<()>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait ExportJwks {
|
|
||||||
async fn export_jwks(self) -> JsonWebKeySet;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SharedSecret<'a> {
|
|
||||||
inner: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> SharedSecret<'a> {
|
|
||||||
pub fn new(source: &'a impl AsRef<[u8]>) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: source.as_ref(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<'a> SigningKeystore for &SharedSecret<'a> {
|
|
||||||
async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result<JwtHeader> {
|
|
||||||
if !matches!(
|
|
||||||
alg,
|
|
||||||
JsonWebSignatureAlgorithm::Hs256
|
|
||||||
| JsonWebSignatureAlgorithm::Hs384
|
|
||||||
| JsonWebSignatureAlgorithm::Hs512,
|
|
||||||
) {
|
|
||||||
bail!("unsupported algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(JwtHeader::new(alg))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn sign(self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>> {
|
|
||||||
// TODO: do the signing in a blocking task
|
|
||||||
// TODO: should we bail out if the key is too small?
|
|
||||||
let signature = match header.alg() {
|
|
||||||
JsonWebSignatureAlgorithm::Hs256 => {
|
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(msg);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlgorithm::Hs384 => {
|
|
||||||
let mut mac = Hmac::<Sha384>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(msg);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlgorithm::Hs512 => {
|
|
||||||
let mut mac = Hmac::<Sha512>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(msg);
|
|
||||||
mac.finalize().into_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => bail!("unsupported algorithm"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(signature)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<'a> VerifyingKeystore for &SharedSecret<'a> {
|
|
||||||
async fn verify(
|
|
||||||
self,
|
|
||||||
header: &JwtHeader,
|
|
||||||
payload: &[u8],
|
|
||||||
signature: &[u8],
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
// TODO: do the verification in a blocking task
|
|
||||||
match header.alg() {
|
|
||||||
JsonWebSignatureAlgorithm::Hs256 => {
|
|
||||||
let mut mac = Hmac::<Sha256>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(payload);
|
|
||||||
mac.verify(signature.try_into()?)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlgorithm::Hs384 => {
|
|
||||||
let mut mac = Hmac::<Sha384>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(payload);
|
|
||||||
mac.verify(signature.try_into()?)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonWebSignatureAlgorithm::Hs512 => {
|
|
||||||
let mut mac = Hmac::<Sha512>::new_from_slice(self.inner)?;
|
|
||||||
mac.update(payload);
|
|
||||||
mac.verify(signature.try_into()?)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => bail!("unsupported algorithm"),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct StaticKeystore {
|
pub struct StaticKeystore {
|
||||||
rsa_keys: HashMap<String, rsa::RsaPrivateKey>,
|
rsa_keys: HashMap<String, rsa::RsaPrivateKey>,
|
||||||
@@ -463,23 +356,6 @@ iYPb7jv3uzncFtwJ7RhDOvEA0fChRANCAATCKn2AEqa9785k+TmwkeCvLub8XGrF
|
|||||||
ezE6bA/blaPVE3nu4SUVYKULRJQxNjeOSra8TQrlIS8e5ItbMn8Tv9KV
|
ezE6bA/blaPVE3nu4SUVYKULRJQxNjeOSra8TQrlIS8e5ItbMn8Tv9KV
|
||||||
-----END PRIVATE KEY-----";
|
-----END PRIVATE KEY-----";
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_shared_secret() {
|
|
||||||
let secret = "super-complicated-secret-that-should-be-big-enough-for-sha512";
|
|
||||||
let message = "this is the message to sign".as_bytes();
|
|
||||||
let store = SharedSecret::new(&secret);
|
|
||||||
for alg in [
|
|
||||||
JsonWebSignatureAlgorithm::Hs256,
|
|
||||||
JsonWebSignatureAlgorithm::Hs384,
|
|
||||||
JsonWebSignatureAlgorithm::Hs512,
|
|
||||||
] {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_static_store() {
|
async fn test_static_store() {
|
||||||
let message = "this is the message to sign".as_bytes();
|
let message = "this is the message to sign".as_bytes();
|
37
crates/jose/src/keystore/traits.rs
Normal file
37
crates/jose/src/keystore/traits.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// 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 async_trait::async_trait;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
iana::JsonWebSignatureAlgorithm, JsonWebKeySet, JwtHeader,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait SigningKeystore {
|
||||||
|
async fn prepare_header(self, alg: JsonWebSignatureAlgorithm) -> anyhow::Result<JwtHeader>;
|
||||||
|
|
||||||
|
async fn sign(self, header: &JwtHeader, msg: &[u8]) -> anyhow::Result<Vec<u8>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait VerifyingKeystore {
|
||||||
|
async fn verify(self, header: &JwtHeader, msg: &[u8], signature: &[u8]) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait ExportJwks {
|
||||||
|
async fn export_jwks(self) -> JsonWebKeySet;
|
||||||
|
}
|
||||||
|
|
Reference in New Issue
Block a user