|
@@ -1,5 +1,3 @@
|
|
|
-use std::{cell::RefCell, sync::Arc};
|
|
|
-
|
|
|
use crate::{schema, UIDCError};
|
|
|
use hmac::{Hmac, Mac};
|
|
|
use microrm::prelude::*;
|
|
@@ -17,7 +15,7 @@ pub enum KeyError {
|
|
|
}
|
|
|
|
|
|
#[non_exhaustive]
|
|
|
-#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
|
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
|
pub enum HMacType {
|
|
|
Sha256,
|
|
|
Sha512,
|
|
@@ -33,7 +31,7 @@ impl HMacType {
|
|
|
}
|
|
|
|
|
|
#[non_exhaustive]
|
|
|
-#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
|
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
|
|
pub enum KeyType {
|
|
|
HMac(HMacType),
|
|
|
RSA2048,
|
|
@@ -70,172 +68,50 @@ impl std::str::FromStr for KeyType {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub enum ParsedKey {
|
|
|
- HMAC {
|
|
|
- key_id: String,
|
|
|
- keydata: Vec<u8>,
|
|
|
- hmty: HMacType,
|
|
|
- },
|
|
|
- Ed25519 {
|
|
|
- key_id: String,
|
|
|
- keypair: ring::signature::Ed25519KeyPair,
|
|
|
- },
|
|
|
- RSA {
|
|
|
- key_id: String,
|
|
|
- keypair: ring::signature::RsaKeyPair,
|
|
|
- },
|
|
|
+#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
|
|
+struct RsaPublicData<'l> {
|
|
|
+ modulus: &'l [u8],
|
|
|
+ exponent: &'l [u8],
|
|
|
}
|
|
|
|
|
|
-impl ParsedKey {
|
|
|
- pub fn key_id(&self) -> &str {
|
|
|
- match self {
|
|
|
- Self::HMAC { key_id, .. } => key_id.as_str(),
|
|
|
- Self::Ed25519 { key_id, .. } => key_id.as_str(),
|
|
|
- Self::RSA { key_id, .. } => key_id.as_str(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn generate_signature(&self, data: &[u8]) -> Result<Vec<u8>, UIDCError> {
|
|
|
- match self {
|
|
|
- Self::HMAC { keydata, hmty, .. } => match hmty {
|
|
|
- HMacType::Sha256 => Ok(Hmac::<sha2::Sha256>::new_from_slice(keydata.as_slice())
|
|
|
- .unwrap()
|
|
|
- .chain_update(data)
|
|
|
- .finalize()
|
|
|
- .into_bytes()
|
|
|
- .to_vec()),
|
|
|
- HMacType::Sha512 => Ok(Hmac::<sha2::Sha512>::new_from_slice(keydata.as_slice())
|
|
|
- .unwrap()
|
|
|
- .chain_update(data)
|
|
|
- .finalize()
|
|
|
- .into_bytes()
|
|
|
- .to_vec()),
|
|
|
+impl schema::Key {
|
|
|
+ pub fn into_jwk(self) -> jsonwebtoken::jwk::Jwk {
|
|
|
+ use jsonwebtoken::jwk;
|
|
|
+
|
|
|
+ match self.key_type.as_ref() {
|
|
|
+ KeyType::RSA2048 | KeyType::RSA4096 => {
|
|
|
+ let rpubdata : RsaPublicData = bincode::deserialize(self.public_data.as_slice()).unwrap();
|
|
|
+
|
|
|
+ jwk::Jwk {
|
|
|
+ common: jwk::CommonParameters {
|
|
|
+ public_key_use: Some(jwk::PublicKeyUse::Signature),
|
|
|
+ key_algorithm: Some(jwk::KeyAlgorithm::RS256),
|
|
|
+ key_id: Some(self.key_id),
|
|
|
+ ..Default::default()
|
|
|
+ },
|
|
|
+ algorithm: jwk::AlgorithmParameters::RSA(jwk::RSAKeyParameters {
|
|
|
+ key_type: jwk::RSAKeyType::RSA,
|
|
|
+ n: base64::encode(rpubdata.modulus),
|
|
|
+ e: base64::encode(rpubdata.exponent),
|
|
|
+ })
|
|
|
+ }
|
|
|
},
|
|
|
- Self::Ed25519 { keypair, .. } => Ok(keypair.sign(data).as_ref().into()),
|
|
|
- Self::RSA { keypair, .. } => {
|
|
|
- let rng = ring::rand::SystemRandom::new();
|
|
|
- let mut signature = vec![];
|
|
|
- signature.resize(keypair.public_modulus_len(), 0);
|
|
|
- keypair
|
|
|
- .sign(
|
|
|
- &ring::signature::RSA_PKCS1_SHA256,
|
|
|
- &rng,
|
|
|
- data,
|
|
|
- signature.as_mut_slice(),
|
|
|
- )
|
|
|
- .map_err(|_| KeyError::Plain("failed to generate RSA signature!"))?;
|
|
|
- Ok(signature)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn verify_signature(&self, data: &[u8], signature: &[u8]) -> Result<bool, UIDCError> {
|
|
|
- use ring::signature::VerificationAlgorithm;
|
|
|
- match self {
|
|
|
- Self::HMAC { .. } => Ok(ring::constant_time::verify_slices_are_equal(
|
|
|
- self.generate_signature(data)?.as_slice(),
|
|
|
- signature,
|
|
|
- )
|
|
|
- .is_ok()),
|
|
|
- Self::Ed25519 { keypair, .. } => Ok(ring::signature::ED25519
|
|
|
- .verify(
|
|
|
- keypair.public_key().as_ref().into(),
|
|
|
- data.into(),
|
|
|
- signature.into(),
|
|
|
- )
|
|
|
- .is_ok()),
|
|
|
- Self::RSA { keypair, .. } => Ok(ring::signature::RSA_PKCS1_2048_8192_SHA256
|
|
|
- .verify(
|
|
|
- keypair.public_key().as_ref().into(),
|
|
|
- data.into(),
|
|
|
- signature.into(),
|
|
|
- )
|
|
|
- .is_ok()),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn parse_from(key: &schema::Key) -> Result<Self, UIDCError> {
|
|
|
- match key.key_type.as_ref() {
|
|
|
- KeyType::HMac(hmty) => Ok(ParsedKey::HMAC {
|
|
|
- key_id: key.key_id.clone(),
|
|
|
- keydata: key.secret_data.clone(),
|
|
|
- hmty: *hmty,
|
|
|
- }),
|
|
|
- KeyType::RSA2048 | KeyType::RSA4096 => Ok(ParsedKey::RSA {
|
|
|
- key_id: key.key_id.clone(),
|
|
|
- keypair: ring::signature::RsaKeyPair::from_pkcs8(&key.secret_data)
|
|
|
- .map_err(|_| UIDCError::Abort("could not load RSA key from database"))?,
|
|
|
- }),
|
|
|
- KeyType::Ed25519 => Ok(ParsedKey::Ed25519 {
|
|
|
- key_id: key.key_id.clone(),
|
|
|
- keypair: Ed25519KeyPair::from_pkcs8(&key.secret_data)
|
|
|
- .map_err(|_| UIDCError::Abort("could not load ed25519 key from database"))?,
|
|
|
- }),
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-pub struct RealmKeys {
|
|
|
- realm: schema::Realm,
|
|
|
- // key cache, to avoid reparsing the key data repeatedly
|
|
|
- keys: RefCell<Vec<(KeyType, Arc<ParsedKey>)>>,
|
|
|
-}
|
|
|
-
|
|
|
-impl RealmKeys {
|
|
|
- pub fn new(realm: schema::Realm) -> Self {
|
|
|
- Self {
|
|
|
- realm,
|
|
|
- keys: vec![].into(),
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn by_key_id(&mut self, id: &String) -> Result<Option<Arc<ParsedKey>>, UIDCError> {
|
|
|
- // check the cache
|
|
|
- for key in self.keys.borrow().iter() {
|
|
|
- if key.1.key_id() == id {
|
|
|
- return Ok(Some(key.1.clone()));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // then check the database
|
|
|
- let key = self.realm.keys.with(schema::Key::KeyId, id).first().get()?;
|
|
|
-
|
|
|
- if let Some(key) = key {
|
|
|
- let parsed = Arc::new(ParsedKey::parse_from(key.as_ref())?);
|
|
|
-
|
|
|
- self.keys
|
|
|
- .borrow_mut()
|
|
|
- .push((*key.key_type.as_ref(), parsed.clone()));
|
|
|
-
|
|
|
- Ok(Some(parsed))
|
|
|
- } else {
|
|
|
- Ok(None)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- pub fn by_type(&mut self, kty: KeyType) -> Result<Option<Arc<ParsedKey>>, UIDCError> {
|
|
|
- // check the cache
|
|
|
- for key in self.keys.borrow().iter() {
|
|
|
- if key.0 == kty {
|
|
|
- return Ok(Some(key.1.clone()));
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // then check the database
|
|
|
- let key = self
|
|
|
- .realm
|
|
|
- .keys
|
|
|
- .with(schema::Key::KeyType, &kty.into())
|
|
|
- .first()
|
|
|
- .get()?;
|
|
|
-
|
|
|
- if let Some(key) = key {
|
|
|
- let parsed = Arc::new(ParsedKey::parse_from(key.as_ref())?);
|
|
|
- self.keys.borrow_mut().push((kty, parsed.clone()));
|
|
|
-
|
|
|
- Ok(Some(parsed))
|
|
|
- } else {
|
|
|
- Ok(None)
|
|
|
+ KeyType::Ed25519 => {
|
|
|
+ jwk::Jwk {
|
|
|
+ common: jwk::CommonParameters {
|
|
|
+ public_key_use: Some(jwk::PublicKeyUse::Signature),
|
|
|
+ key_algorithm: Some(jwk::KeyAlgorithm::EdDSA),
|
|
|
+ key_id: Some(self.key_id),
|
|
|
+ ..Default::default()
|
|
|
+ },
|
|
|
+ algorithm: jwk::AlgorithmParameters::OctetKeyPair(jwk::OctetKeyPairParameters {
|
|
|
+ key_type: jwk::OctetKeyPairType::OctetKeyPair,
|
|
|
+ curve: jwk::EllipticCurve::Ed25519,
|
|
|
+ x: base64::encode(self.public_data),
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ _ => todo!()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -248,7 +124,7 @@ fn pubkey_id(data: &[u8]) -> String {
|
|
|
key_id
|
|
|
}
|
|
|
|
|
|
-fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<ParsedKey, UIDCError> {
|
|
|
+fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<String, UIDCError> {
|
|
|
// ring does not generate RSA keypairs, so we need to shell out to openssl for this.
|
|
|
let openssl_output = std::process::Command::new("sh")
|
|
|
.arg("-c")
|
|
@@ -257,9 +133,6 @@ fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<Pars
|
|
|
-algorithm RSA \
|
|
|
-pkeyopt rsa_keygen_pubexp:65537 \
|
|
|
-pkeyopt rsa_keygen_bits:{bits} \
|
|
|
- | openssl pkcs8 \
|
|
|
- -topk8 \
|
|
|
- -nocrypt \
|
|
|
-outform der"
|
|
|
))
|
|
|
.output()
|
|
@@ -267,25 +140,30 @@ fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<Pars
|
|
|
|
|
|
let secret = openssl_output.stdout;
|
|
|
|
|
|
- let keypair = ring::signature::RsaKeyPair::from_pkcs8(&secret)
|
|
|
+ let keypair = ring::signature::RsaKeyPair::from_der(&secret)
|
|
|
.map_err(|_| UIDCError::Abort("couldn't parse PKCS#8 output from openssl"))?;
|
|
|
let public = keypair.public_key().as_ref();
|
|
|
let key_id = pubkey_id(public.as_ref());
|
|
|
let expiry = time::OffsetDateTime::now_utc() + time::Duration::days(730);
|
|
|
|
|
|
+ let public_data = bincode::serialize(&RsaPublicData {
|
|
|
+ modulus: keypair.public_key().modulus().big_endian_without_leading_zero().into(),
|
|
|
+ exponent: keypair.public_key().exponent().big_endian_without_leading_zero().into(),
|
|
|
+ }).unwrap();
|
|
|
+
|
|
|
realm.keys.insert(schema::Key {
|
|
|
key_id: key_id.clone(),
|
|
|
key_type: kty.into(),
|
|
|
key_state: schema::KeyState::Active.into(),
|
|
|
- public_data: public.into(),
|
|
|
+ public_data,
|
|
|
secret_data: secret.into(),
|
|
|
expiry,
|
|
|
})?;
|
|
|
|
|
|
- Ok(ParsedKey::RSA { key_id, keypair })
|
|
|
+ Ok(key_id)
|
|
|
}
|
|
|
|
|
|
-pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UIDCError> {
|
|
|
+pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<String, UIDCError> {
|
|
|
let mut rng = ring::rand::SystemRandom::new();
|
|
|
match kty {
|
|
|
KeyType::HMac(hmty) => {
|
|
@@ -305,17 +183,12 @@ pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UID
|
|
|
key_id: key_id.clone(),
|
|
|
key_type: kty.into(),
|
|
|
key_state: schema::KeyState::Active.into(),
|
|
|
- // no separate public data for EdDSA keys
|
|
|
+ // no separate public data for HMAC keys
|
|
|
public_data: vec![],
|
|
|
secret_data: keydata.clone(),
|
|
|
expiry,
|
|
|
})?;
|
|
|
-
|
|
|
- Ok(ParsedKey::HMAC {
|
|
|
- key_id,
|
|
|
- keydata,
|
|
|
- hmty,
|
|
|
- })
|
|
|
+ Ok(key_id)
|
|
|
}
|
|
|
KeyType::RSA2048 => generate_rsa(realm, KeyType::RSA2048, 2048),
|
|
|
KeyType::RSA4096 => generate_rsa(realm, KeyType::RSA4096, 4096),
|
|
@@ -335,16 +208,12 @@ pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UID
|
|
|
key_id: key_id.clone(),
|
|
|
key_type: kty.into(),
|
|
|
key_state: schema::KeyState::Active.into(),
|
|
|
- // no separate public data for EdDSA keys
|
|
|
- public_data: vec![],
|
|
|
+ public_data: pubkey.as_ref().into(),
|
|
|
secret_data: keydata,
|
|
|
expiry,
|
|
|
})?;
|
|
|
|
|
|
- Ok(ParsedKey::Ed25519 {
|
|
|
- key_id,
|
|
|
- keypair: loaded_key,
|
|
|
- })
|
|
|
+ Ok(key_id)
|
|
|
}
|
|
|
}
|
|
|
}
|