util: refactor crypto API

main
anna 1 year ago
parent 96392afc39
commit 89aa01c266
Signed by: fef
GPG Key ID: EC22E476DC2D3D84

136
Cargo.lock generated

@ -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"

@ -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"

@ -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<T> = std::result::Result<T, Error>;
@ -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<reqwest::Error> for Error {
}
}
impl From<openssl::error::ErrorStack> for Error {
fn from(e: openssl::error::ErrorStack) -> Error {
Error::OpenSSL(e)
impl From<crypto::Error> for Error {
fn from(e: crypto::Error) -> Error {
Error::Crypto(e)
}
}

@ -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<AccountId> for Account {

@ -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(())
}
}

@ -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<Account>;
@ -15,7 +15,7 @@ pub struct Account {
pub name: String,
pub domain: String,
pub display_name: Option<String>,
pub public_key: Option<PublicKeyDer>,
pub public_key: Option<PubKey>,
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<String>,
pub public_key: Option<PublicKeyDer>,
pub public_key: Option<PubKey>,
}
impl From<Account> for AccountId {

@ -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<String>,
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<String>,
pub locale: String,
pub private_key: PrivateKeyDer,
pub private_key: PrivKey,
}
impl From<User> for UserId {

@ -38,4 +38,14 @@ impl AccountRepo {
self.mem.store(account.clone()).await;
Ok(account)
}
pub async fn delete<I>(&self, id: I) -> Result<()>
where
I: Into<AccountId> + Send,
{
let id = id.into();
self.db.delete(id).await?;
self.mem.delete(id).await;
Ok(())
}
}

@ -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<SignupData>, state: AppState) -> Result<HttpResponse> {
let data: Sane<SignupData> = 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<SignupData>, state: AppState) -> Result<HttpResp
name: data.username,
domain: "localhost".into(),
display_name: None,
public_key: Some(pubkey),
public_key: Some(public_key),
}))
.await?;
state
let user_result = state
.repo
.users
.create(Insane::from(NewUser {
@ -46,9 +47,13 @@ async fn signup(data: web::Form<SignupData>, state: AppState) -> Result<HttpResp
password: data.password.hash(),
locale: data.locale,
reason: data.reason,
private_key: privkey,
private_key,
}))
.await?;
.await;
if user_result.is_err() {
state.repo.accounts.delete(account).await?;
}
Ok(HttpResponse::Ok().finish())
}

@ -0,0 +1,429 @@
use rsa::{
pkcs1,
pkcs1v15::{Signature, SigningKey, VerifyingKey},
pkcs8::{
self, DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey, LineEnding,
},
rand_core::OsRng,
sha2::Sha256,
signature::{RandomizedSigner, Verifier},
RsaPrivateKey, RsaPublicKey,
};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use sqlx::{
database::{HasArguments, HasValueRef},
encode::IsNull,
error::BoxDynError,
Database,
};
use std::{fmt, future};
use tokio::sync::OnceCell;
use crate::core::*;
pub const DEFAULT_KEY_SIZE: usize = 2048;
/// Our abstract wrapper around "any" type of public key.
/// We currently assume all keys are RSA.
#[derive(Clone)]
pub struct PubKey {
pkey: OnceCell<RsaPublicKey>,
der: Vec<u8>,
}
/// Our abstract wrapper around "any" type of private key.
/// We currently assume all keys are RSA.
#[derive(Clone)]
pub struct PrivKey {
pkey: OnceCell<RsaPrivateKey>,
der: Vec<u8>,
}
pub struct Error(rsa::errors::Error);
impl PubKey {
pub fn from_der_unchecked(der: Vec<u8>) -> PubKey {
PubKey {
pkey: OnceCell::new(),
der,
}
}
pub fn from_pem(pem: &str) -> Result<PubKey> {
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<PubKey> {
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<PubKey> {
let pkey = <RsaPublicKey as pkcs1::DecodeRsaPublicKey>::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<String> {
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<Sha256> = 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<PrivKey> {
// 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 =
<RsaPrivateKey as pkcs1::DecodeRsaPrivateKey>::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<u8>) -> PrivKey {
PrivKey {
pkey: OnceCell::new(),
der,
}
}
pub async fn derive_pubkey(&self) -> Result<PubKey> {
let pkey = self.get_pkey().await?;
PubKey::try_from(pkey.to_public_key())
}
pub async fn sign(&self, data: &[u8]) -> Result<Vec<u8>> {
let pkey = self.get_pkey().await?;
let signing_key: SigningKey<Sha256> = 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<RsaPrivateKey> for PrivKey {
type Error = crate::core::Error;
fn try_from(val: RsaPrivateKey) -> Result<PrivKey> {
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<RsaPublicKey> for PubKey {
type Error = crate::core::Error;
fn try_from(val: RsaPublicKey) -> Result<PubKey> {
let der = val.to_public_key_der().map_err(Error::from)?.into_vec();
Ok(PubKey {
pkey: OnceCell::from(val),
der,
})
}
}
impl<DB: Database> sqlx::Type<DB> for PubKey
where
Vec<u8>: sqlx::Type<DB>,
{
fn type_info() -> DB::TypeInfo {
<Vec<u8> as sqlx::Type<DB>>::type_info()
}
fn compatible(ty: &DB::TypeInfo) -> bool {
<Vec<u8> as sqlx::Type<DB>>::compatible(ty)
}
}
impl<DB: Database> sqlx::Type<DB> for PrivKey
where
Vec<u8>: sqlx::Type<DB>,
{
fn type_info() -> DB::TypeInfo {
<Vec<u8> as sqlx::Type<DB>>::type_info()
}
fn compatible(ty: &DB::TypeInfo) -> bool {
<Vec<u8> as sqlx::Type<DB>>::compatible(ty)
}
}
impl<'q, DB: Database> sqlx::Encode<'q, DB> for PubKey
where
Vec<u8>: sqlx::Encode<'q, DB>,
{
fn encode(self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
self.der.encode(buf)
}
fn encode_by_ref(&self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
self.der.encode_by_ref(buf)
}
fn produces(&self) -> Option<DB::TypeInfo> {
self.der.produces()
}
fn size_hint(&self) -> usize {
self.der.size_hint()
}
}
impl<'q, DB: Database> sqlx::Encode<'q, DB> for PrivKey
where
Vec<u8>: sqlx::Encode<'q, DB>,
{
fn encode(self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
self.der.encode(buf)
}
fn encode_by_ref(&self, buf: &mut <DB as HasArguments<'q>>::ArgumentBuffer) -> IsNull {
self.der.encode_by_ref(buf)
}
fn produces(&self) -> Option<DB::TypeInfo> {
self.der.produces()
}
fn size_hint(&self) -> usize {
self.der.size_hint()
}
}
impl<'r, DB: Database> sqlx::Decode<'r, DB> for PubKey
where
Vec<u8>: sqlx::Decode<'r, DB>,
{
fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> std::result::Result<Self, BoxDynError> {
let value = <Vec<u8> 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<u8>: sqlx::Decode<'r, DB>,
{
fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> std::result::Result<Self, BoxDynError> {
let value = <Vec<u8> as sqlx::Decode<'r, DB>>::decode(value)?;
Ok(PrivKey::from_der_unchecked(value))
}
}
impl Serialize for PubKey
where
Vec<u8>: Serialize,
{
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.der.serialize(serializer)
}
}
impl Serialize for PrivKey
where
Vec<u8>: Serialize,
{
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
self.der.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for PubKey
where
Vec<u8>: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> std::result::Result<PubKey, D::Error>
where
D: Deserializer<'de>,
{
Vec::<u8>::deserialize(deserializer).map(PubKey::from_der_unchecked)
}
}
impl<'de> Deserialize<'de> for PrivKey
where
Vec<u8>: Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> std::result::Result<PrivKey, D::Error>
where
D: Deserializer<'de>,
{
Vec::<u8>::deserialize(deserializer).map(PrivKey::from_der_unchecked)
}
}
impl From<rsa::errors::Error> for Error {
fn from(val: rsa::errors::Error) -> Error {
Error(val)
}
}
impl From<pkcs1::Error> for Error {
fn from(val: pkcs1::Error) -> Error {
Error::from(rsa::errors::Error::from(val))
}
}
impl From<pkcs8::Error> for Error {
fn from(val: pkcs8::Error) -> Error {
Error::from(rsa::errors::Error::from(val))
}
}
impl From<pkcs8::spki::Error> 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);
}
}

@ -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<u8>);
#[derive(sqlx::Type, Clone, Serialize, Deserialize)]
#[sqlx(transparent)]
pub struct PrivateKeyDer(Vec<u8>);
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))
}

@ -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;

Loading…
Cancel
Save