feat: jose

This commit is contained in:
2024-11-13 00:15:30 +08:00
parent 9b2c40d662
commit 2a9d6dfaac
4 changed files with 616 additions and 29 deletions

View File

@@ -0,0 +1,153 @@
use aes_gcm_stream::Aes256GcmStreamEncryptor;
use aes_kw::Kek;
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
use base64::Engine;
use josekit::jwe;
use josekit::jwe::alg::aeskw::AeskwJweAlgorithm;
use josekit::jwe::alg::rsaes::RsaesJweAlgorithm;
use josekit::jwe::JweHeader;
use josekit::jwk::alg::rsa::RsaKeyPair;
use josekit::jwk::Jwk;
use rand::random;
use rand::rngs::OsRng;
use rsa::RsaPrivateKey;
use rust_util::XResult;
use serde::Serialize;
use serde_json::Value;
const LOCAL_KMS_PREFIX: &str = "LKMS:";
// JWE format:
// BASE64URL(UTF8(JWE Protected Header)) || '.' ||
// BASE64URL(JWE Encrypted Key) || '.' || BASE64URL(JWE Initialization Vector)
// || '.' || BASE64URL(JWE Ciphertext) || '.' || BASE64URL(JWE Authentication Tag).
//
// RSA JWE Header:
// {"enc":"A256GCM","vendor":"local-mini-kms","alg":"RSA-OAEP"}
// eyJlbmMiOiJBMjU2R0NNIiwidmVuZG9yIjoibG9jYWwtbWluaS1rbXMiLCJhbGciOiJSU0EtT0FFUCJ9.VQ_R
// yGjXqQlUIbRIMgaYRSaX5FMRBzZ6ApfdZ2yAwiG70hjNfR3ss7x4PYqMm6QtITm1O4_fp7I3bY8iUz5Njyth_
// Min7Xm2-WsQ6gq9yN58btkUBFm60c7SC5XLaqE1pEtBAz7786bJk6M4NeOtDAOFAmIb2j1EwnS5vweBtmNv7N
// UFIgvx806T3WkCFDOkMSJ10_6LSa0z-lIac-s68svsU5WW8CXVKxHAbxaHyX_otu2HxXzDZlF5Goamh5ZJtr0
// 0yW_bzDCx3hZ2nMK3Ve7IJ2ZLAMmvhj9LKWkPtoqH0dGHaPHWff5P3rZ4vtKywt_h5b6SYII_mEoJcpByMyGw
// TXCtZymDt82Tyv_FesW2721JgyGxnukuOxQRTw4MfGYIO5bldL3uGGI_H4HXlXhM_kp3wuPAZ0vH4Jj2KD6MV
// DDTJQaEBQIEF07i7WiNynr57kbahYwextRXYP7LgoUHfFwA5GGGpN-UkuWLlKkYLTmXGrPYnL6Cf9D3euP7nF
// ml2oA2hjig-UuYf9A_QSEqNsMxYDuG-rggn3H_iXNl4ooYcxSVOXhTKfoV578MkNwG75BdHN5FeRYIKq0HCTM
// lGqqBWmDibPtMd7Uq1JrDd8774lnA8JcZcCMSia4m6WJSbG0kOuJ4NJPOUrYtNEJXgWKU3FQzDB-apLMQdac.
// WYJgsdZRLk310KWd.P333-S2VYg.PCfruTdk8vh3a8wcjJCe-g
//
// RSA-OAEP RSA using Optimal Asymmetric Encryption Padding (OAEP), as defined in RFC 3447 [RFC3447]
// A256GCM Advanced Encryption Standard (AES) using 256 bit keys in Galois/Counter Mode, as defined in [FIPS197] and [NIST80038D]
//
// AES JWE Header:
// {"enc":"A256GCM","vendor":"local-mini-kms","version":"5b90f66a1c6a918d","alg":"A256KW"}
// eyJlbmMiOiJBMjU2R0NNIiwidmVuZG9yIjoibG9jYWwtbWluaS1rbXMiLCJ2ZXJzaW9uIjoiNWI5MGY2NmExYz
// ZhOTE4ZCIsImFsZyI6IkEyNTZLVyJ9.K2_P-b_Gq9wbrssbcS5AmiUwcnNTnnZSe7rBI1SixVrC7TfFK0fruw.
// ez3OKjOHAIIYnfM0.wSO3aXo.-vGJwk8JQKhi3voIlAA9gQ
//
// A256GCM Advanced Encryption Standard (AES) using 256 bit keys in Galois/Counter Mode, as defined in [FIPS197] and [NIST80038D]
// A256KW Advanced Encryption Standard (AES) Key Wrap Algorithm using 256 bit keys, as defined in RFC 3394 [RFC3394]
pub fn generate_rsa_key_2(bits: u32) -> XResult<RsaPrivateKey> {
let mut rng = OsRng::default();
Ok(RsaPrivateKey::new(&mut rng, bits as usize)?)
}
pub fn generate_rsa_key(bits: u32) -> XResult<RsaKeyPair> {
Ok(RsaKeyPair::generate(bits)?)
}
pub fn serialize_jwe_rsa_2(payload: &[u8], jwk: &Jwk) {
// TODO ...
}
pub fn serialize_jwe_rsa(payload: &[u8], jwk: &Jwk) -> XResult<String> {
let mut header = JweHeader::new();
header.set_content_encryption("A256GCM");
header.set_claim("vendor", Some(Value::String("local-mini-kms".to_string())))?;
let encrypter = RsaesJweAlgorithm::RsaOaep.encrypter_from_jwk(jwk)?;
Ok(format!("{}{}", LOCAL_KMS_PREFIX, jwe::serialize_compact(payload, &header, &encrypter)?))
}
pub fn deserialize_jwe_rsa(jwe: &str, jwk: &Jwk) -> XResult<(Vec<u8>, JweHeader)> {
let decrypter = RsaesJweAlgorithm::RsaOaep.decrypter_from_jwk(jwk)?;
Ok(jwe::deserialize_compact(&get_jwe(jwe), &decrypter)?)
}
// JWE Header: {"alg":"dir","enc":"A256GCM"}
// Encrypted key (CEK): (blank)
// IV: Vlf_WdLm-spHbfJe
// Ciphertext: RxMPrw
// Authentication Tag: 5VC8Y_qSPdSubbGNGyfn6A
//
// JWE Header: {"alg":"A256KW","enc":"A256GCM"}
// Encrypted key (CEK): 66xZoxFI18zfvLMO6WU1zzqqX1tT8xu_qZzMQyPcfVuajPNkOJUXQA
// IV: X5ZL8yaOektXmfny
// Ciphertext: brz-Lg
// Authentication Tag: xG-EvM-9hrw0XRiuRW7HrA
//
// https://security.stackexchange.com/questions/80966/what-is-the-point-of-aes-key-wrap-with-json-web-encryption
#[derive(Serialize)]
struct JweHeader2 {
pub enc: String,
pub alg: String,
pub vendor: String,
}
pub fn serialize_jwe_aes_2(payload: &[u8], key: [u8; 32]) -> XResult<String> {
let header = JweHeader2 {
enc: "A256GCM".to_string(),
alg: "A256KW".to_string(),
vendor: "local-mini-kms".to_string(),
};
let header_str = serde_json::to_string(&header).unwrap();
let header_b64 = URL_SAFE_NO_PAD.encode(header_str.as_bytes());
// let key: [u8; 32] = key.into();
let data_key: [u8; 32] = random();
let nonce: [u8; 12] = random();
let mut encryptor = Aes256GcmStreamEncryptor::new(data_key, &nonce);
encryptor.init_adata(header_b64.as_bytes());
let mut e = encryptor.update(payload);
let (f, t) = encryptor.finalize();
e.extend_from_slice(&f);
let kek = Kek::from(key);
let wrap_key = kek.wrap_vec(&data_key[..]).unwrap();
let mut jwe = String::new();
jwe.push_str(&header_b64);
jwe.push_str(".");
jwe.push_str(&URL_SAFE_NO_PAD.encode(&wrap_key));
jwe.push_str(".");
jwe.push_str(&URL_SAFE_NO_PAD.encode(&nonce));
jwe.push_str(".");
jwe.push_str(&URL_SAFE_NO_PAD.encode(&e));
jwe.push_str(".");
jwe.push_str(&URL_SAFE_NO_PAD.encode(&t));
Ok(jwe)
}
pub fn serialize_jwe_aes(payload: &[u8], key: &[u8]) -> XResult<String> {
let mut header = JweHeader::new();
header.set_content_encryption("A256GCM");
header.set_claim("vendor", Some(Value::String("local-mini-kms".to_string())))?;
// header.set_claim("version", Some(Value::String(get_master_key_checksum(key))))?;
let encrypter = AeskwJweAlgorithm::A256kw.encrypter_from_bytes(key)?;
Ok(format!("{}{}", LOCAL_KMS_PREFIX, jwe::serialize_compact(payload, &header, &encrypter)?))
}
pub fn deserialize_jwe_aes(jwe: &str, key: &[u8]) -> XResult<(Vec<u8>, JweHeader)> {
let decrypter = AeskwJweAlgorithm::A256kw.decrypter_from_bytes(key)?;
Ok(jwe::deserialize_compact(&get_jwe(jwe), &decrypter)?)
}
fn get_jwe(jwe: &str) -> String {
if jwe.starts_with(LOCAL_KMS_PREFIX) {
jwe.chars().skip(LOCAL_KMS_PREFIX.len()).collect()
} else {
jwe.to_string()
}
}