util: refactor crypto API
This commit is contained in:
parent
96392afc39
commit
89aa01c266
12 changed files with 617 additions and 40 deletions
136
Cargo.lock
generated
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())
|
||||
}
|
||||
|
||||
|
|
429
src/util/crypto.rs
Normal file
429
src/util/crypto.rs
Normal file
|
@ -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…
Reference in a new issue