From 8c25dc03ce63fdf86993a967d631e3d20ab463b5 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Wed, 31 Aug 2022 18:42:23 +0200 Subject: [PATCH] Move the Encrypter from the config to the keystore --- Cargo.lock | 22 ++-- crates/axum-utils/Cargo.toml | 11 +- crates/axum-utils/src/client_authorization.rs | 2 +- crates/config/Cargo.toml | 3 - crates/config/src/sections/mod.rs | 2 +- crates/config/src/sections/secrets.rs | 94 +--------------- .../handlers/src/compat/login_sso_complete.rs | 2 +- crates/handlers/src/lib.rs | 4 +- .../src/oauth2/authorization/complete.rs | 2 +- .../handlers/src/oauth2/authorization/mod.rs | 2 +- crates/handlers/src/oauth2/consent.rs | 2 +- crates/handlers/src/oauth2/introspection.rs | 2 +- crates/handlers/src/oauth2/token.rs | 3 +- .../handlers/src/views/account/emails/add.rs | 2 +- .../handlers/src/views/account/emails/mod.rs | 2 +- .../src/views/account/emails/verify.rs | 2 +- crates/handlers/src/views/account/mod.rs | 2 +- crates/handlers/src/views/account/password.rs | 2 +- crates/handlers/src/views/index.rs | 2 +- crates/handlers/src/views/login.rs | 2 +- crates/handlers/src/views/logout.rs | 2 +- crates/handlers/src/views/reauth.rs | 2 +- crates/handlers/src/views/register.rs | 2 +- crates/keystore/Cargo.toml | 13 ++- crates/keystore/src/encrypter.rs | 104 ++++++++++++++++++ crates/keystore/src/lib.rs | 6 +- 26 files changed, 157 insertions(+), 137 deletions(-) create mode 100644 crates/keystore/src/encrypter.rs diff --git a/Cargo.lock b/Cargo.lock index b83c3dd3..ac3d8b9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "aead" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae06cea71059b6b79d879afcdd237a33ac61afc052fdd605815e6f3916254abf" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" dependencies = [ "crypto-common", "generic-array", @@ -790,7 +790,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead 0.5.0", + "aead 0.5.1", "chacha20", "cipher 0.4.3", "poly1305", @@ -1698,9 +1698,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -2358,11 +2358,11 @@ dependencies = [ "headers", "http", "http-body", - "mas-config", "mas-data-model", "mas-http", "mas-iana", "mas-jose", + "mas-keystore", "mas-storage", "mas-templates", "mime", @@ -2426,10 +2426,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "chacha20poly1305", "chrono", - "cookie", - "data-encoding", "figment", "indoc", "lettre", @@ -2616,11 +2613,16 @@ dependencies = [ name = "mas-keystore" version = "0.1.0" dependencies = [ + "aead 0.5.1", "anyhow", + "base64ct", + "chacha20poly1305", "const-oid", + "cookie", "der", "ecdsa", "elliptic-curve", + "generic-array", "k256", "mas-iana", "mas-jose", @@ -2629,7 +2631,7 @@ dependencies = [ "pem-rfc7468", "pkcs1", "pkcs8", - "rand_core", + "rand", "rsa", "sec1", "spki", diff --git a/crates/axum-utils/Cargo.toml b/crates/axum-utils/Cargo.toml index 790278d3..601259d5 100644 --- a/crates/axum-utils/Cargo.toml +++ b/crates/axum-utils/Cargo.toml @@ -29,11 +29,10 @@ tower = { version = "0.4.13", features = ["util"] } tracing = "0.1.36" url = "2.2.2" -# TODO: remove the config dependency by moving out the encrypter -mas-config = { path = "../config" } -mas-templates = { path = "../templates" } -mas-storage = { path = "../storage" } mas-data-model = { path = "../data-model" } -mas-jose = { path = "../jose" } -mas-iana = { path = "../iana" } mas-http = { path = "../http", features = ["client"] } +mas-iana = { path = "../iana" } +mas-jose = { path = "../jose" } +mas-keystore = { path = "../keystore" } +mas-storage = { path = "../storage" } +mas-templates = { path = "../templates" } diff --git a/crates/axum-utils/src/client_authorization.rs b/crates/axum-utils/src/client_authorization.rs index 2dbec7e7..8f184911 100644 --- a/crates/axum-utils/src/client_authorization.rs +++ b/crates/axum-utils/src/client_authorization.rs @@ -26,7 +26,7 @@ use axum::{ }; use headers::{authorization::Basic, Authorization}; use http::StatusCode; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_data_model::{Client, JwksOrJwksUri, StorageBackend}; use mas_http::HttpServiceExt; use mas_iana::oauth::OAuthClientAuthenticationMethod; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 410ec10d..81cd585a 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -26,9 +26,6 @@ lettre = { version = "0.10.1", default-features = false, features = ["serde", "b pem-rfc7468 = "0.6.0" rand = "0.8.5" -chacha20poly1305 = { version = "0.10.1", features = ["std"] } -cookie = { version = "0.16.0", features = ["private", "key-expansion"] } -data-encoding = "2.3.2" indoc = "1.0.7" diff --git a/crates/config/src/sections/mod.rs b/crates/config/src/sections/mod.rs index 6d11f2f9..a2b1ff5a 100644 --- a/crates/config/src/sections/mod.rs +++ b/crates/config/src/sections/mod.rs @@ -35,7 +35,7 @@ pub use self::{ http::HttpConfig, matrix::MatrixConfig, policy::PolicyConfig, - secrets::{Encrypter, SecretsConfig}, + secrets::SecretsConfig, telemetry::{ MetricsConfig, MetricsExporterConfig, Propagator, TelemetryConfig, TracingConfig, TracingExporterConfig, diff --git a/crates/config/src/sections/secrets.rs b/crates/config/src/sections/secrets.rs index 6feed113..f9fc9905 100644 --- a/crates/config/src/sections/secrets.rs +++ b/crates/config/src/sections/secrets.rs @@ -12,18 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{borrow::Cow, path::PathBuf, sync::Arc}; +use std::{borrow::Cow, path::PathBuf}; use anyhow::Context; use async_trait::async_trait; -use chacha20poly1305::{ - aead::{generic_array::GenericArray, Aead}, - ChaCha20Poly1305, KeyInit, -}; -use cookie::Key; -use data_encoding::BASE64; use mas_jose::jwk::{JsonWebKey, JsonWebKeySet}; -use mas_keystore::{Keystore, PrivateKey}; +use mas_keystore::{Encrypter, Keystore, PrivateKey}; use rand::{ distributions::{Alphanumeric, DistString}, thread_rng, @@ -36,90 +30,6 @@ use tracing::info; use super::ConfigurationSection; -/// Helps encrypting and decrypting data -#[derive(Clone)] -pub struct Encrypter { - cookie_key: Arc, - aead: Arc, -} - -// TODO: move this somewhere else -impl Encrypter { - /// Creates an [`Encrypter`] out of an encryption key - #[must_use] - pub fn new(key: &[u8; 32]) -> Self { - let cookie_key = Key::derive_from(&key[..]); - let cookie_key = Arc::new(cookie_key); - let key = GenericArray::from_slice(key); - let aead = ChaCha20Poly1305::new(key); - let aead = Arc::new(aead); - Self { cookie_key, aead } - } - - /// Encrypt a payload - /// - /// # Errors - /// - /// Will return `Err` when the payload failed to encrypt - pub fn encrypt(&self, nonce: &[u8; 12], decrypted: &[u8]) -> anyhow::Result> { - let nonce = GenericArray::from_slice(&nonce[..]); - let encrypted = self.aead.encrypt(nonce, decrypted)?; - Ok(encrypted) - } - - /// Decrypts a payload - /// - /// # Errors - /// - /// Will return `Err` when the payload failed to decrypt - pub fn decrypt(&self, nonce: &[u8; 12], encrypted: &[u8]) -> anyhow::Result> { - let nonce = GenericArray::from_slice(&nonce[..]); - let encrypted = self.aead.decrypt(nonce, encrypted)?; - Ok(encrypted) - } - - /// Encrypt a payload to a self-contained base64-encoded string - /// - /// # Errors - /// - /// Will return `Err` when the payload failed to encrypt - pub fn encryt_to_string(&self, decrypted: &[u8]) -> anyhow::Result { - let nonce = rand::random(); - let encrypted = self.encrypt(&nonce, decrypted)?; - let encrypted = [&nonce[..], &encrypted].concat(); - let encrypted = BASE64.encode(&encrypted); - Ok(encrypted) - } - - /// Decrypt a payload from a self-contained base64-encoded string - /// - /// # Errors - /// - /// Will return `Err` when the payload failed to decrypt - pub fn decrypt_string(&self, encrypted: &str) -> anyhow::Result> { - let encrypted = BASE64.decode(encrypted.as_bytes())?; - - let nonce: &[u8; 12] = encrypted - .get(0..12) - .ok_or_else(|| anyhow::anyhow!("invalid payload serialization"))? - .try_into()?; - - let payload = encrypted - .get(12..) - .ok_or_else(|| anyhow::anyhow!("invalid payload serialization"))?; - - let decrypted_client_secret = self.decrypt(nonce, payload)?; - - Ok(decrypted_client_secret) - } -} - -impl From for Key { - fn from(e: Encrypter) -> Self { - e.cookie_key.as_ref().clone() - } -} - fn example_secret() -> &'static str { "0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff" } diff --git a/crates/handlers/src/compat/login_sso_complete.rs b/crates/handlers/src/compat/login_sso_complete.rs index 9cce52ea..7ae1f314 100644 --- a/crates/handlers/src/compat/login_sso_complete.rs +++ b/crates/handlers/src/compat/login_sso_complete.rs @@ -26,8 +26,8 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; use mas_data_model::Device; +use mas_keystore::Encrypter; use mas_router::{CompatLoginSsoAction, PostAuthAction, Route}; use mas_storage::compat::{fullfill_compat_sso_login, get_compat_sso_login_by_id}; use mas_templates::{CompatSsoContext, ErrorContext, TemplateContext, Templates}; diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index d5baf2bf..629f0c90 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -30,10 +30,10 @@ use axum::{ }; use headers::HeaderName; use hyper::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_LANGUAGE, CONTENT_TYPE}; -use mas_config::{Encrypter, MatrixConfig}; +use mas_config::MatrixConfig; use mas_email::Mailer; use mas_http::CorsLayerExt; -use mas_keystore::Keystore; +use mas_keystore::{Encrypter, Keystore}; use mas_policy::PolicyFactory; use mas_router::{Route, UrlBuilder}; use mas_templates::{ErrorContext, Templates}; diff --git a/crates/handlers/src/oauth2/authorization/complete.rs b/crates/handlers/src/oauth2/authorization/complete.rs index 5a90e604..3385f371 100644 --- a/crates/handlers/src/oauth2/authorization/complete.rs +++ b/crates/handlers/src/oauth2/authorization/complete.rs @@ -23,8 +23,8 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use hyper::StatusCode; use mas_axum_utils::SessionInfoExt; -use mas_config::Encrypter; use mas_data_model::{AuthorizationGrant, BrowserSession}; +use mas_keystore::Encrypter; use mas_policy::PolicyFactory; use mas_router::{PostAuthAction, Route}; use mas_storage::{ diff --git a/crates/handlers/src/oauth2/authorization/mod.rs b/crates/handlers/src/oauth2/authorization/mod.rs index 40f4c8d4..c327e3b8 100644 --- a/crates/handlers/src/oauth2/authorization/mod.rs +++ b/crates/handlers/src/oauth2/authorization/mod.rs @@ -22,9 +22,9 @@ use axum::{ use axum_extra::extract::PrivateCookieJar; use hyper::StatusCode; use mas_axum_utils::SessionInfoExt; -use mas_config::Encrypter; use mas_data_model::{AuthorizationCode, Pkce}; use mas_iana::oauth::OAuthAuthorizationEndpointResponseType; +use mas_keystore::Encrypter; use mas_policy::PolicyFactory; use mas_router::{PostAuthAction, Route}; use mas_storage::oauth2::{ diff --git a/crates/handlers/src/oauth2/consent.rs b/crates/handlers/src/oauth2/consent.rs index 0410edb3..dd7cf0f9 100644 --- a/crates/handlers/src/oauth2/consent.rs +++ b/crates/handlers/src/oauth2/consent.rs @@ -25,8 +25,8 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, SessionInfoExt, }; -use mas_config::Encrypter; use mas_data_model::AuthorizationGrantStage; +use mas_keystore::Encrypter; use mas_policy::PolicyFactory; use mas_router::{PostAuthAction, Route}; use mas_storage::oauth2::{ diff --git a/crates/handlers/src/oauth2/introspection.rs b/crates/handlers/src/oauth2/introspection.rs index 7a4bb9c4..d60989c0 100644 --- a/crates/handlers/src/oauth2/introspection.rs +++ b/crates/handlers/src/oauth2/introspection.rs @@ -15,9 +15,9 @@ use axum::{extract::Extension, response::IntoResponse, Json}; use hyper::StatusCode; use mas_axum_utils::client_authorization::{ClientAuthorization, CredentialsVerificationError}; -use mas_config::Encrypter; use mas_data_model::{TokenFormatError, TokenType}; use mas_iana::oauth::{OAuthClientAuthenticationMethod, OAuthTokenTypeHint}; +use mas_keystore::Encrypter; use mas_storage::{ compat::{ lookup_active_compat_access_token, lookup_active_compat_refresh_token, diff --git a/crates/handlers/src/oauth2/token.rs b/crates/handlers/src/oauth2/token.rs index d8c7f45e..27c8f357 100644 --- a/crates/handlers/src/oauth2/token.rs +++ b/crates/handlers/src/oauth2/token.rs @@ -21,7 +21,6 @@ use data_encoding::BASE64URL_NOPAD; use headers::{CacheControl, HeaderMap, HeaderMapExt, Pragma}; use hyper::StatusCode; use mas_axum_utils::client_authorization::{ClientAuthorization, CredentialsVerificationError}; -use mas_config::Encrypter; use mas_data_model::{AuthorizationGrantStage, Client, TokenType}; use mas_iana::jose::JsonWebSignatureAlg; use mas_jose::{ @@ -29,7 +28,7 @@ use mas_jose::{ constraints::Constrainable, jwt::{JsonWebSignatureHeader, Jwt, JwtSignatureError}, }; -use mas_keystore::Keystore; +use mas_keystore::{Encrypter, Keystore}; use mas_router::UrlBuilder; use mas_storage::{ oauth2::{ diff --git a/crates/handlers/src/views/account/emails/add.rs b/crates/handlers/src/views/account/emails/add.rs index 58f0928a..760b6f4b 100644 --- a/crates/handlers/src/views/account/emails/add.rs +++ b/crates/handlers/src/views/account/emails/add.rs @@ -21,8 +21,8 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; use mas_email::Mailer; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::user::add_user_email; use mas_templates::{EmailAddContext, TemplateContext, Templates}; diff --git a/crates/handlers/src/views/account/emails/mod.rs b/crates/handlers/src/views/account/emails/mod.rs index 13a1291f..89223e18 100644 --- a/crates/handlers/src/views/account/emails/mod.rs +++ b/crates/handlers/src/views/account/emails/mod.rs @@ -22,9 +22,9 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; use mas_data_model::{BrowserSession, User, UserEmail}; use mas_email::Mailer; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::{ user::{ diff --git a/crates/handlers/src/views/account/emails/verify.rs b/crates/handlers/src/views/account/emails/verify.rs index 29c524c6..8d6f1f59 100644 --- a/crates/handlers/src/views/account/emails/verify.rs +++ b/crates/handlers/src/views/account/emails/verify.rs @@ -22,7 +22,7 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::user::{ consume_email_verification, lookup_user_email_by_id, lookup_user_email_verification_code, diff --git a/crates/handlers/src/views/account/mod.rs b/crates/handlers/src/views/account/mod.rs index 51bc9900..f8e09a6f 100644 --- a/crates/handlers/src/views/account/mod.rs +++ b/crates/handlers/src/views/account/mod.rs @@ -21,7 +21,7 @@ use axum::{ }; use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt}; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::user::{count_active_sessions, get_user_emails}; use mas_templates::{AccountContext, TemplateContext, Templates}; diff --git a/crates/handlers/src/views/account/password.rs b/crates/handlers/src/views/account/password.rs index 91a51d28..5afc66ba 100644 --- a/crates/handlers/src/views/account/password.rs +++ b/crates/handlers/src/views/account/password.rs @@ -22,8 +22,8 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; use mas_data_model::BrowserSession; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::{ user::{authenticate_session, set_password}, diff --git a/crates/handlers/src/views/index.rs b/crates/handlers/src/views/index.rs index e046961c..d2b09fbd 100644 --- a/crates/handlers/src/views/index.rs +++ b/crates/handlers/src/views/index.rs @@ -18,7 +18,7 @@ use axum::{ }; use axum_extra::extract::PrivateCookieJar; use mas_axum_utils::{csrf::CsrfExt, FancyError, SessionInfoExt}; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_router::UrlBuilder; use mas_templates::{IndexContext, TemplateContext, Templates}; use sqlx::PgPool; diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index 7dcb48e5..6d1493c2 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -21,7 +21,7 @@ use mas_axum_utils::{ csrf::{CsrfExt, CsrfToken, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::user::{login, LoginError}; use mas_templates::{ diff --git a/crates/handlers/src/views/logout.rs b/crates/handlers/src/views/logout.rs index dc509b5e..9318f159 100644 --- a/crates/handlers/src/views/logout.rs +++ b/crates/handlers/src/views/logout.rs @@ -21,7 +21,7 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_router::{PostAuthAction, Route}; use mas_storage::user::end_session; use sqlx::PgPool; diff --git a/crates/handlers/src/views/reauth.rs b/crates/handlers/src/views/reauth.rs index 9d7edcdb..15939009 100644 --- a/crates/handlers/src/views/reauth.rs +++ b/crates/handlers/src/views/reauth.rs @@ -21,7 +21,7 @@ use mas_axum_utils::{ csrf::{CsrfExt, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; +use mas_keystore::Encrypter; use mas_router::Route; use mas_storage::user::authenticate_session; use mas_templates::{ReauthContext, TemplateContext, Templates}; diff --git a/crates/handlers/src/views/register.rs b/crates/handlers/src/views/register.rs index fbf5a436..e09c6ebe 100644 --- a/crates/handlers/src/views/register.rs +++ b/crates/handlers/src/views/register.rs @@ -27,8 +27,8 @@ use mas_axum_utils::{ csrf::{CsrfExt, CsrfToken, ProtectedForm}, FancyError, SessionInfoExt, }; -use mas_config::Encrypter; use mas_email::Mailer; +use mas_keystore::Encrypter; use mas_policy::PolicyFactory; use mas_router::Route; use mas_storage::user::{ diff --git a/crates/keystore/Cargo.toml b/crates/keystore/Cargo.toml index 9aadc633..43163d51 100644 --- a/crates/keystore/Cargo.toml +++ b/crates/keystore/Cargo.toml @@ -7,21 +7,26 @@ license = "Apache-2.0" [dependencies] anyhow = "1.0.62" +aead = { version = "0.5.1", features = ["std"] } +const-oid = { version = "0.9.0", features = ["std"] } +cookie = { version = "0.16.0", features = ["key-expansion", "private"] } der = { version = "0.6.0", features = ["std"] } +ecdsa = { version = "0.14.4", features = ["std"] } elliptic-curve = { version = "0.12.3", features = ["std", "pem", "sec1"] } k256 = { version = "0.11.1", features = ["std"] } p256 = { version = "0.11.1", features = ["std"] } p384 = { version = "0.11.1", features = ["std"] } pem-rfc7468 = { version = "0.6.0", features = ["std"] } pkcs1 = { version = "0.4.0", features = ["std"] } -spki = { version = "0.6.0", features = ["std"] } -ecdsa = { version = "0.14.4", features = ["std"] } pkcs8 = { version = "0.9.0", features = ["std", "pkcs5", "encryption"] } -const-oid = { version = "0.9.0", features = ["std"] } +rand = "0.8.5" rsa = { git = "https://github.com/RustCrypto/RSA.git", features = ["std", "pem"] } sec1 = { version = "0.3.0", features = ["std"] } +spki = { version = "0.6.0", features = ["std"] } thiserror = "1.0.32" -rand_core = "0.6.3" +generic-array = "0.14.6" +chacha20poly1305 = { version = "0.10.1", features = ["std"] } +base64ct = "1.5.2" mas-iana = { path = "../iana" } mas-jose = { path = "../jose" } diff --git a/crates/keystore/src/encrypter.rs b/crates/keystore/src/encrypter.rs new file mode 100644 index 00000000..6ce80006 --- /dev/null +++ b/crates/keystore/src/encrypter.rs @@ -0,0 +1,104 @@ +// 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::sync::Arc; + +use aead::Aead; +use base64ct::{Base64, Encoding}; +use chacha20poly1305::{ChaCha20Poly1305, KeyInit}; +use cookie::Key; +use generic_array::GenericArray; + +/// Helps encrypting and decrypting data +#[derive(Clone)] +pub struct Encrypter { + cookie_key: Arc, + aead: Arc, +} + +impl From for Key { + fn from(e: Encrypter) -> Self { + e.cookie_key.as_ref().clone() + } +} + +impl Encrypter { + /// Creates an [`Encrypter`] out of an encryption key + #[must_use] + pub fn new(key: &[u8; 32]) -> Self { + let cookie_key = Key::derive_from(&key[..]); + let cookie_key = Arc::new(cookie_key); + let key = GenericArray::from_slice(key); + let aead = ChaCha20Poly1305::new(key); + let aead = Arc::new(aead); + Self { cookie_key, aead } + } + + /// Encrypt a payload + /// + /// # Errors + /// + /// Will return `Err` when the payload failed to encrypt + pub fn encrypt(&self, nonce: &[u8; 12], decrypted: &[u8]) -> anyhow::Result> { + let nonce = GenericArray::from_slice(&nonce[..]); + let encrypted = self.aead.encrypt(nonce, decrypted)?; + Ok(encrypted) + } + + /// Decrypts a payload + /// + /// # Errors + /// + /// Will return `Err` when the payload failed to decrypt + pub fn decrypt(&self, nonce: &[u8; 12], encrypted: &[u8]) -> anyhow::Result> { + let nonce = GenericArray::from_slice(&nonce[..]); + let encrypted = self.aead.decrypt(nonce, encrypted)?; + Ok(encrypted) + } + + /// Encrypt a payload to a self-contained base64-encoded string + /// + /// # Errors + /// + /// Will return `Err` when the payload failed to encrypt + pub fn encryt_to_string(&self, decrypted: &[u8]) -> anyhow::Result { + let nonce = rand::random(); + let encrypted = self.encrypt(&nonce, decrypted)?; + let encrypted = [&nonce[..], &encrypted].concat(); + let encrypted = Base64::encode_string(&encrypted); + Ok(encrypted) + } + + /// Decrypt a payload from a self-contained base64-encoded string + /// + /// # Errors + /// + /// Will return `Err` when the payload failed to decrypt + pub fn decrypt_string(&self, encrypted: &str) -> anyhow::Result> { + let encrypted = Base64::decode_vec(encrypted)?; + + let nonce: &[u8; 12] = encrypted + .get(0..12) + .ok_or_else(|| anyhow::anyhow!("invalid payload serialization"))? + .try_into()?; + + let payload = encrypted + .get(12..) + .ok_or_else(|| anyhow::anyhow!("invalid payload serialization"))?; + + let decrypted_client_secret = self.decrypt(nonce, payload)?; + + Ok(decrypted_client_secret) + } +} diff --git a/crates/keystore/src/lib.rs b/crates/keystore/src/lib.rs index 7da9b474..c0ac5786 100644 --- a/crates/keystore/src/lib.rs +++ b/crates/keystore/src/lib.rs @@ -37,11 +37,15 @@ use mas_jose::{ use pem_rfc7468::PemLabel; use pkcs1::EncodeRsaPrivateKey; use pkcs8::{AssociatedOid, PrivateKeyInfo}; -use rand_core::{CryptoRng, RngCore}; +use rand::{CryptoRng, RngCore}; use rsa::BigUint; use sec1::EncodeEcPrivateKey; use thiserror::Error; +mod encrypter; + +pub use self::encrypter::Encrypter; + /// Error type used when a key could not be loaded #[derive(Debug, Error)] pub enum LoadError {