From 89aa01c266390680c0542cdeee4e8290c4e4b593 Mon Sep 17 00:00:00 2001 From: fef Date: Mon, 23 Jan 2023 21:14:30 +0100 Subject: [PATCH] util: refactor crypto API --- Cargo.lock | 136 +++++++++++ Cargo.toml | 2 + src/core/error.rs | 15 +- src/data/account/mem.rs | 4 + src/data/account/pg.rs | 8 + src/model/account.rs | 6 +- src/model/user.rs | 6 +- src/repo/account.rs | 10 + src/route/api/v1/accounts.rs | 19 +- src/util/crypto.rs | 429 +++++++++++++++++++++++++++++++++++ src/util/keys.rs | 20 -- src/util/mod.rs | 2 +- 12 files changed, 617 insertions(+), 40 deletions(-) create mode 100644 src/util/crypto.rs delete mode 100644 src/util/keys.rs diff --git a/Cargo.lock b/Cargo.lock index d5b0bce..e2b1693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -416,6 +416,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "const-oid" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" + [[package]] name = "contextual" version = "0.1.4" @@ -567,6 +573,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5440d1dc8ea7cae44cda3c64568db29bfa2434aba51ae66a50c00488841a65a3" +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "derivative" version = "2.2.0" @@ -598,6 +615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1291,6 +1309,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "lexical" @@ -1371,6 +1392,12 @@ version = "0.2.138" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + [[package]] name = "link-cplusplus" version = "1.0.7" @@ -1521,6 +1548,23 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1531,6 +1575,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -1538,6 +1593,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1571,8 +1627,10 @@ dependencies = [ "mime", "openssl", "pretty_env_logger", + "rand", "rdf-types", "reqwest", + "rsa", "serde", "serde_json", "serde_test", @@ -1704,6 +1762,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1240e3e7bfdde5660ba0fc3e9e3565d3b30c2ae1973e218e93b869da771ff2e9" +[[package]] +name = "pem-rfc7468" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d159833a9105500e0398934e205e0773f0b27529557134ecfc51c27646adac" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1728,6 +1795,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff33bdbdfc54cc98a2eca766ebdec3e1b8fb7387523d5c9c9a2891da856f719" +dependencies = [ + "der", + "pkcs8", + "spki", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.26" @@ -1949,6 +2038,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "rsa" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b3896c9b7790b70a9aa314a30e4ae114200992a19c96cbe0ca6070edd32ab8" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "subtle", + "zeroize", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -2141,6 +2251,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "slab" version = "0.4.7" @@ -2181,6 +2301,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "sqlformat" version = "0.2.0" @@ -2874,6 +3004,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" diff --git a/Cargo.toml b/Cargo.toml index 4d23dcc..41f1b93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,8 +21,10 @@ log = "0.4" mime = "0.3" openssl = "0.10" pretty_env_logger = "0.4" +rand = "0.8" rdf-types = "0.12" reqwest = { version = "0.11", features = [ "rustls" ] } +rsa = { version = "0.8", features = [ "sha2" ] } serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" serde_test = "1.0" diff --git a/src/core/error.rs b/src/core/error.rs index 16389e3..bba06f7 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -6,7 +6,7 @@ use actix_web::{ use serde::{ser::SerializeMap, Serialize, Serializer}; use std::{fmt, io}; -use crate::util::validate; +use crate::util::{crypto, validate}; pub type Result = std::result::Result; @@ -15,7 +15,9 @@ pub enum Error { BadBearcap, BadCredentials, BadRequest, + BadSignature, BadToken(jsonwebtoken::errors::Error), + Crypto(crypto::Error), Database(sqlx::Error), Invalid(validate::Error), Io(io::Error), @@ -23,7 +25,6 @@ pub enum Error { MalformedHeader(header::ToStrError), NotFound, Reqwest(reqwest::Error), - OpenSSL(openssl::error::ErrorStack), } impl ResponseError for Error { @@ -32,6 +33,7 @@ impl ResponseError for Error { Error::BadBearcap => StatusCode::UNPROCESSABLE_ENTITY, Error::BadCredentials => StatusCode::UNAUTHORIZED, Error::BadRequest => StatusCode::BAD_REQUEST, + Error::BadSignature => StatusCode::UNAUTHORIZED, Error::BadToken(_) => StatusCode::UNAUTHORIZED, Error::Invalid(_) => StatusCode::UNPROCESSABLE_ENTITY, Error::MalformedApub(_) => StatusCode::UNPROCESSABLE_ENTITY, @@ -52,7 +54,9 @@ impl fmt::Display for Error { Error::BadBearcap => write!(f, "Invalid bearcap URL"), Error::BadCredentials => write!(f, "Invalid user name or password"), Error::BadRequest => write!(f, "Bad request"), + Error::BadSignature => write!(f, "Bad signature"), Error::BadToken(jwt_error) => jwt_error.fmt(f), + Error::Crypto(crypto_error) => crypto_error.fmt(f), Error::Database(sqlx_error) => sqlx_error.fmt(f), Error::Invalid(validate_error) => validate_error.fmt(f), Error::Io(io_error) => io_error.fmt(f), @@ -60,7 +64,6 @@ impl fmt::Display for Error { Error::MalformedApub(msg) => write!(f, "Malformed ActivityPub: {msg}"), Error::MalformedHeader(to_str_error) => to_str_error.fmt(f), Error::Reqwest(reqwest_error) => reqwest_error.fmt(f), - Error::OpenSSL(_) => write!(f, "OpenSSL failed for some reason"), } } } @@ -104,9 +107,9 @@ impl From for Error { } } -impl From for Error { - fn from(e: openssl::error::ErrorStack) -> Error { - Error::OpenSSL(e) +impl From for Error { + fn from(e: crypto::Error) -> Error { + Error::Crypto(e) } } diff --git a/src/data/account/mem.rs b/src/data/account/mem.rs index 8d7c674..c32d8ed 100644 --- a/src/data/account/mem.rs +++ b/src/data/account/mem.rs @@ -19,6 +19,10 @@ impl MemAccountDataSource { pub async fn store(&self, account: Account) { self.cache.put(account).await } + + pub async fn delete(&self, id: AccountId) { + self.cache.del(id).await; + } } impl Indexable for Account { diff --git a/src/data/account/pg.rs b/src/data/account/pg.rs index 5e01c19..09faef0 100644 --- a/src/data/account/pg.rs +++ b/src/data/account/pg.rs @@ -35,4 +35,12 @@ impl PgAccountDataSource { .await?; Ok(account) } + + pub async fn delete(&self, id: AccountId) -> Result<()> { + sqlx::query("DELETE FROM accounts WHERE id = $1") + .bind(id) + .execute(&self.pool) + .await?; + Ok(()) + } } diff --git a/src/model/account.rs b/src/model/account.rs index 4a50442..df37c9d 100644 --- a/src/model/account.rs +++ b/src/model/account.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use sqlx::FromRow; use crate::core::*; -use crate::util::keys::PublicKeyDer; +use crate::util::crypto::PubKey; use crate::util::validate::{ResultBuilder, Validate}; pub type AccountId = Id; @@ -15,7 +15,7 @@ pub struct Account { pub name: String, pub domain: String, pub display_name: Option, - pub public_key: Option, + pub public_key: Option, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, } @@ -25,7 +25,7 @@ pub struct NewAccount { pub name: String, pub domain: String, pub display_name: Option, - pub public_key: Option, + pub public_key: Option, } impl From for AccountId { diff --git a/src/model/user.rs b/src/model/user.rs index 40c14dd..58860a1 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -3,7 +3,7 @@ use sqlx::FromRow; use crate::core::*; use crate::model::AccountId; -use crate::util::keys::PrivateKeyDer; +use crate::util::crypto::PrivKey; use crate::util::password::HashedPassword; use crate::util::validate::{ResultBuilder, Validate}; @@ -18,7 +18,7 @@ pub struct User { pub reason: Option, pub locale: String, pub activated: bool, - pub private_key: PrivateKeyDer, + pub private_key: PrivKey, } pub struct NewUser { @@ -27,7 +27,7 @@ pub struct NewUser { pub password: HashedPassword, pub reason: Option, pub locale: String, - pub private_key: PrivateKeyDer, + pub private_key: PrivKey, } impl From for UserId { diff --git a/src/repo/account.rs b/src/repo/account.rs index d395711..652cc4a 100644 --- a/src/repo/account.rs +++ b/src/repo/account.rs @@ -38,4 +38,14 @@ impl AccountRepo { self.mem.store(account.clone()).await; Ok(account) } + + pub async fn delete(&self, id: I) -> Result<()> + where + I: Into + Send, + { + let id = id.into(); + self.db.delete(id).await?; + self.mem.delete(id).await; + Ok(()) + } } diff --git a/src/route/api/v1/accounts.rs b/src/route/api/v1/accounts.rs index 4c722ba..b416820 100644 --- a/src/route/api/v1/accounts.rs +++ b/src/route/api/v1/accounts.rs @@ -6,8 +6,8 @@ use crate::ent; use crate::middle::AuthData; use crate::model::{AccountId, NewAccount, NewUser}; use crate::state::AppState; -use crate::util::keys::generate_keypair; -use crate::util::password::{self, ClearPassword}; +use crate::util::crypto::PrivKey; +use crate::util::password::ClearPassword; use crate::util::validate::{ResultBuilder, Validate}; #[derive(Deserialize)] @@ -24,7 +24,8 @@ struct SignupData { async fn signup(data: web::Form, state: AppState) -> Result { let data: Sane = Insane::from(data.into_inner()).try_into()?; let data = data.inner(); - let (pubkey, privkey) = generate_keypair()?; + let private_key = PrivKey::new()?; + let public_key = private_key.derive_pubkey().await?; let account = state .repo @@ -34,10 +35,10 @@ async fn signup(data: web::Form, state: AppState) -> Result, state: AppState) -> Result, + der: Vec, +} + +/// Our abstract wrapper around "any" type of private key. +/// We currently assume all keys are RSA. +#[derive(Clone)] +pub struct PrivKey { + pkey: OnceCell, + der: Vec, +} + +pub struct Error(rsa::errors::Error); + +impl PubKey { + pub fn from_der_unchecked(der: Vec) -> PubKey { + PubKey { + pkey: OnceCell::new(), + der, + } + } + + pub fn from_pem(pem: &str) -> Result { + if pem.starts_with("-----BEGIN PUBLIC KEY-----") { + PubKey::from_pkcs8_pem(pem) + } else { + PubKey::from_pkcs1_pem(pem) + } + } + + pub fn from_pkcs8_pem(pem: &str) -> Result { + let pkey = RsaPublicKey::from_public_key_pem(pem).map_err(Error::from)?; + let der = pkey.to_public_key_der().map_err(Error::from)?.into_vec(); + Ok(PubKey { + pkey: OnceCell::from(pkey), + der, + }) + } + + pub fn from_pkcs1_pem(pem: &str) -> Result { + let pkey = ::from_pkcs1_pem(pem) + .map_err(Error::from)?; + let der = pkey.to_public_key_der().map_err(Error::from)?.into_vec(); + Ok(PubKey { + pkey: OnceCell::from(pkey), + der, + }) + } + + pub async fn to_pem(&self) -> Result { + let pkey = self.get_pkey().await?; + let pem = pkey + .to_public_key_pem(LineEnding::LF) + .map_err(Error::from)?; + Ok(pem) + } + + pub async fn verify(&self, data: &[u8], signature: &[u8]) -> Result<()> { + let pkey = self.get_pkey().await?; + let signature = Signature::from(Box::from(signature)); + let verifying_key: VerifyingKey = VerifyingKey::new_with_prefix(pkey.clone()); + verifying_key + .verify(data, &signature) + .map_err(|_| crate::core::Error::BadSignature) + } + + async fn get_pkey(&self) -> Result<&RsaPublicKey> { + self.pkey + .get_or_try_init(|| { + future::ready( + RsaPublicKey::from_public_key_der(self.der.as_slice()) + .map_err(Error::from) + .map_err(crate::core::Error::from), + ) + }) + .await + } +} + +impl PrivKey { + /// Generate a new private key. + pub fn new() -> Result { + // The rsa crate takes like two orders of magnitude longer to generate a key, + // so until they get that under control we'll use the raw OpenSSL bindings to + // generate a key, encode it to PKCS#1 DER, and load it again. + let pkey = openssl::rsa::Rsa::generate(DEFAULT_KEY_SIZE as u32).unwrap(); + let pkcs1_der = pkey.private_key_to_der().unwrap(); + let pkey = + ::from_pkcs1_der(pkcs1_der.as_slice()) + .map_err(Error::from)?; + let der = pkey.to_pkcs8_der().map_err(Error::from)?; + let der = Vec::from(der.as_bytes()); + Ok(PrivKey { + pkey: OnceCell::from(pkey), + der, + }) + } + + pub fn from_der_unchecked(der: Vec) -> PrivKey { + PrivKey { + pkey: OnceCell::new(), + der, + } + } + + pub async fn derive_pubkey(&self) -> Result { + let pkey = self.get_pkey().await?; + PubKey::try_from(pkey.to_public_key()) + } + + pub async fn sign(&self, data: &[u8]) -> Result> { + let pkey = self.get_pkey().await?; + let signing_key: SigningKey = SigningKey::new_with_prefix(pkey.clone()); + let signature = signing_key.sign_with_rng(&mut OsRng, data); + Ok(Vec::from(signature.as_ref())) + } + + async fn get_pkey(&self) -> Result<&RsaPrivateKey> { + self.pkey + .get_or_try_init(|| { + future::ready( + RsaPrivateKey::from_pkcs8_der(self.der.as_slice()) + .map_err(|e| Error::from(e).into()), + ) + }) + .await + } +} + +impl TryFrom for PrivKey { + type Error = crate::core::Error; + + fn try_from(val: RsaPrivateKey) -> Result { + let der = val.to_pkcs8_der().map_err(Error::from)?; + let der = Vec::from(der.as_bytes()); + Ok(PrivKey { + pkey: OnceCell::from(val), + der, + }) + } +} + +impl TryFrom for PubKey { + type Error = crate::core::Error; + + fn try_from(val: RsaPublicKey) -> Result { + let der = val.to_public_key_der().map_err(Error::from)?.into_vec(); + Ok(PubKey { + pkey: OnceCell::from(val), + der, + }) + } +} + +impl sqlx::Type for PubKey +where + Vec: sqlx::Type, +{ + fn type_info() -> DB::TypeInfo { + as sqlx::Type>::type_info() + } + + fn compatible(ty: &DB::TypeInfo) -> bool { + as sqlx::Type>::compatible(ty) + } +} + +impl sqlx::Type for PrivKey +where + Vec: sqlx::Type, +{ + fn type_info() -> DB::TypeInfo { + as sqlx::Type>::type_info() + } + + fn compatible(ty: &DB::TypeInfo) -> bool { + as sqlx::Type>::compatible(ty) + } +} + +impl<'q, DB: Database> sqlx::Encode<'q, DB> for PubKey +where + Vec: sqlx::Encode<'q, DB>, +{ + fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull { + self.der.encode(buf) + } + + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + self.der.encode_by_ref(buf) + } + + fn produces(&self) -> Option { + self.der.produces() + } + + fn size_hint(&self) -> usize { + self.der.size_hint() + } +} + +impl<'q, DB: Database> sqlx::Encode<'q, DB> for PrivKey +where + Vec: sqlx::Encode<'q, DB>, +{ + fn encode(self, buf: &mut >::ArgumentBuffer) -> IsNull { + self.der.encode(buf) + } + + fn encode_by_ref(&self, buf: &mut >::ArgumentBuffer) -> IsNull { + self.der.encode_by_ref(buf) + } + + fn produces(&self) -> Option { + self.der.produces() + } + + fn size_hint(&self) -> usize { + self.der.size_hint() + } +} + +impl<'r, DB: Database> sqlx::Decode<'r, DB> for PubKey +where + Vec: sqlx::Decode<'r, DB>, +{ + fn decode(value: >::ValueRef) -> std::result::Result { + let value = as sqlx::Decode<'r, DB>>::decode(value)?; + Ok(PubKey::from_der_unchecked(value)) + } +} + +impl<'r, DB: Database> sqlx::Decode<'r, DB> for PrivKey +where + Vec: sqlx::Decode<'r, DB>, +{ + fn decode(value: >::ValueRef) -> std::result::Result { + let value = as sqlx::Decode<'r, DB>>::decode(value)?; + Ok(PrivKey::from_der_unchecked(value)) + } +} + +impl Serialize for PubKey +where + Vec: Serialize, +{ + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + self.der.serialize(serializer) + } +} + +impl Serialize for PrivKey +where + Vec: Serialize, +{ + fn serialize(&self, serializer: S) -> std::result::Result + where + S: Serializer, + { + self.der.serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for PubKey +where + Vec: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + Vec::::deserialize(deserializer).map(PubKey::from_der_unchecked) + } +} + +impl<'de> Deserialize<'de> for PrivKey +where + Vec: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> std::result::Result + where + D: Deserializer<'de>, + { + Vec::::deserialize(deserializer).map(PrivKey::from_der_unchecked) + } +} + +impl From for Error { + fn from(val: rsa::errors::Error) -> Error { + Error(val) + } +} + +impl From for Error { + fn from(val: pkcs1::Error) -> Error { + Error::from(rsa::errors::Error::from(val)) + } +} + +impl From for Error { + fn from(val: pkcs8::Error) -> Error { + Error::from(rsa::errors::Error::from(val)) + } +} + +impl From for Error { + fn from(val: pkcs8::spki::Error) -> Error { + Error::from(pkcs8::Error::from(val)) + } +} + +impl fmt::Debug for Error +where + rsa::errors::Error: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +impl fmt::Display for Error +where + rsa::errors::Error: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_PUBKEY_PKCS8: &str = "-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz4muPogkMCRdIShQLyCv +6QMb7d9epNfKGFyPi4C5w9rZTJ5Ox7X4cueXA6imMDJ0DCfD34QESJoIjkXht3W0 +AanLSQnFh+p/5RsbEPb4zaUzG7OHGrYsIE2/LEUFyAuE15KVnXMmtoqN4k8Y5NtC +2GWGnnNW/iD+mr6SMLPQ44+bdPegBjbQmAJ3I/H4byoYvRWWE7g9klWyEZmlSwQQ +MG4m86utQeO7JQ9dHUiG6PtuEm0PVB0pUT0a/qF3wRCMPIpPiA/E+z3yfYYnivKu +wPsehgVguIGxzQaIOaN5UU7UmL36bAT3E0yhelmDdXkxeo6dQDnkuLRBwMTtFY3w +/QIDAQAB +-----END PUBLIC KEY----- +"; + const TEST_PUBKEY_PKCS1: &str = "-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAz4muPogkMCRdIShQLyCv6QMb7d9epNfKGFyPi4C5w9rZTJ5Ox7X4 +cueXA6imMDJ0DCfD34QESJoIjkXht3W0AanLSQnFh+p/5RsbEPb4zaUzG7OHGrYs +IE2/LEUFyAuE15KVnXMmtoqN4k8Y5NtC2GWGnnNW/iD+mr6SMLPQ44+bdPegBjbQ +mAJ3I/H4byoYvRWWE7g9klWyEZmlSwQQMG4m86utQeO7JQ9dHUiG6PtuEm0PVB0p +UT0a/qF3wRCMPIpPiA/E+z3yfYYnivKuwPsehgVguIGxzQaIOaN5UU7UmL36bAT3 +E0yhelmDdXkxeo6dQDnkuLRBwMTtFY3w/QIDAQAB +-----END RSA PUBLIC KEY----- +"; + + #[actix_web::test] + async fn verify_signatures() { + let priv_key = PrivKey::new().unwrap(); + let pub_key = priv_key.derive_pubkey().await.unwrap(); + + let message = String::from("hello, world"); + let signature = priv_key.sign(message.as_bytes()).await.unwrap(); + + assert_ne!(signature.as_slice(), message.as_bytes()); + + assert!(pub_key + .verify(message.as_bytes(), signature.as_slice()) + .await + .is_ok()); + + let tampered_message = String::from("hello, world!"); + assert!(pub_key + .verify(tampered_message.as_bytes(), signature.as_slice()) + .await + .is_err()); + + let mut tampered_signature = signature.clone(); + tampered_signature[0] ^= 1; + assert!(pub_key + .verify(message.as_bytes(), tampered_signature.as_slice()) + .await + .is_err()); + } + + #[actix_web::test] + async fn parse_pkcs8_pem() { + let pub_key = PubKey::from_pem(TEST_PUBKEY_PKCS8).unwrap(); + let pem = pub_key.to_pem().await.unwrap(); + assert_eq!(pem, TEST_PUBKEY_PKCS8); + } + + #[actix_web::test] + async fn parse_pkcs1_pem() { + let pub_key = PubKey::from_pem(TEST_PUBKEY_PKCS1).unwrap(); + let pem = pub_key.to_pem().await.unwrap(); + // to_pem() should always return PKCS#8 + assert_eq!(pem, TEST_PUBKEY_PKCS8); + } +} diff --git a/src/util/keys.rs b/src/util/keys.rs deleted file mode 100644 index c1b4f84..0000000 --- a/src/util/keys.rs +++ /dev/null @@ -1,20 +0,0 @@ -use openssl::pkey::{Private, Public}; -use openssl::rsa::Rsa; -use serde::{Deserialize, Serialize}; - -use crate::core::*; - -#[derive(sqlx::Type, Clone, Serialize, Deserialize)] -#[sqlx(transparent)] -pub struct PublicKeyDer(Vec); - -#[derive(sqlx::Type, Clone, Serialize, Deserialize)] -#[sqlx(transparent)] -pub struct PrivateKeyDer(Vec); - -pub fn generate_keypair() -> Result<(PublicKeyDer, PrivateKeyDer)> { - let private = Rsa::generate(4096)?; - let public_key = PublicKeyDer(private.public_key_to_der()?); - let private_key = PrivateKeyDer(private.private_key_to_der()?); - Ok((public_key, private_key)) -} diff --git a/src/util/mod.rs b/src/util/mod.rs index 6853c71..6a3f4f1 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,6 +1,6 @@ pub mod bear; +pub mod crypto; pub mod http; -pub mod keys; pub mod password; pub mod token; pub mod validate;