273bd1b
use hmac::{Hmac, KeyInit, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use tonic::Status;
use crate::error::InternalError;
use crate::nonce::NonceStore;
pub const AUDIENCE: &str = "event";
const CLAIMS_VERSION: u8 = 1;
type HmacSha256 = Hmac<Sha256>;
/// 信封内层明文:用户标识 + 权威复核需要的最小字段。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
pub v: u8,
pub aud: String,
pub uid: i64,
#[serde(default)]
pub sv: i64,
#[serde(default)]
pub sid: String,
pub iat: i64,
pub exp: i64,
pub nonce: String,
pub rid: String,
}
/// 解密 + 验签 + 去重之后的身份;后续权限校验只使用这里的 user_id/security_version。
#[derive(Debug, Clone)]
pub struct VerifiedIdentity {
pub user_id: i64,
pub security_version: i64,
pub session_id: String,
pub request_id: String,
pub key_id: String,
}
impl VerifiedIdentity {
pub fn user_id(&self) -> i64 {
self.user_id
}
}
fn now_ms() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|duration| duration.as_millis() as i64)
.unwrap_or_default()
}
/// 规范化签名输入:长度前缀避免字段拼接歧义。
fn mac_input(
key_id: &str,
ciphertext: &str,
mac_key_id: &str,
issued_at: i64,
expires_at: i64,
nonce: &str,
audience: &str,
) -> Vec<u8> {
let mut input = Vec::new();
for part in [
key_id.as_bytes(),
ciphertext.as_bytes(),
mac_key_id.as_bytes(),
&issued_at.to_be_bytes(),
&expires_at.to_be_bytes(),
nonce.as_bytes(),
audience.as_bytes(),
] {
input.extend_from_slice(&(part.len() as u32).to_be_bytes());
input.extend_from_slice(part);
}
input
}
fn compute_mac(key: &[u8], input: &[u8]) -> Result<Vec<u8>, InternalError> {
let mut mac = HmacSha256::new_from_slice(key)
.map_err(|error| InternalError::Config(format!("invalid mac key: {error}")))?;
mac.update(input);
Ok(mac.finalize().into_bytes().to_vec())
}
fn verify_mac(key: &[u8], input: &[u8], presented: &[u8]) -> Result<(), InternalError> {
let expected = compute_mac(key, input)?;
if crypt::constant_time_eq(&expected, presented) {
Ok(())
} else {
Err(InternalError::Envelope("mac mismatch".to_string()))
}
}
/// 平台侧:用 event 公钥封装用户标识,并用共享密钥做完整性签名。
pub struct EnvelopeSealer {
encryptor: crypt::RsaEncryptor,
key_id: String,
mac_key_id: String,
mac_key: Vec<u8>,
ttl_secs: u64,
clock_skew_secs: i64,
audience: String,
}
impl EnvelopeSealer {
pub fn new(
key_id: &str,
public_pem: &str,
mac_key_id: &str,
mac_key: &[u8],
ttl_secs: u64,
clock_skew_secs: u64,
) -> Result<Self, InternalError> {
let encryptor = crypt::RsaEncryptor::from_public_pem(key_id, public_pem)
.map_err(|error| InternalError::Config(format!("invalid event public key: {error}")))?;
Ok(Self {
encryptor,
key_id: key_id.to_owned(),
mac_key_id: mac_key_id.to_owned(),
mac_key: mac_key.to_vec(),
ttl_secs: ttl_secs.max(1),
clock_skew_secs: clock_skew_secs as i64,
audience: AUDIENCE.to_string(),
})
}
pub fn seal(
&self,
user_id: i64,
security_version: i64,
session_id: &str,
request_id: &str,
) -> Result<proto::internal::v1::IdentityEnvelope, InternalError> {
let issued_at = now_ms();
let expires_at = issued_at + (self.ttl_secs as i64) * 1000;
let claims = Claims {
v: CLAIMS_VERSION,
aud: self.audience.clone(),
uid: user_id,
sv: security_version,
sid: session_id.to_owned(),
iat: issued_at,
exp: expires_at,
nonce: crate::interceptor::new_request_id(),
rid: request_id.to_owned(),
};
let plaintext = serde_json::to_vec(&claims)
.map_err(|error| InternalError::Envelope(error.to_string()))?;
let ciphertext = crypt::encrypt(&self.encryptor, &plaintext)
.map_err(|error| InternalError::Crypt(error.to_string()))?;
let mac = compute_mac(
&self.mac_key,
&mac_input(
&self.key_id,
&ciphertext,
&self.mac_key_id,
issued_at,
expires_at,
&claims.nonce,
&self.audience,
),
)?;
Ok(proto::internal::v1::IdentityEnvelope {
key_id: self.key_id.clone(),
ciphertext,
mac,
mac_key_id: self.mac_key_id.clone(),
issued_at_unix_ms: issued_at,
expires_at_unix_ms: expires_at,
nonce: claims.nonce,
audience: self.audience.clone(),
request_id: request_id.to_owned(),
})
}
pub fn clock_skew_secs(&self) -> i64 {
self.clock_skew_secs
}
}
/// event 侧:MAC 校验 -> 时间窗 -> nonce 去重 -> 私钥解密 -> claims 校验。
pub struct EnvelopeVerifier {
decryptor: crypt::RsaDecryptor,
mac_key_id: String,
mac_key: Vec<u8>,
clock_skew_secs: i64,
nonce_ttl_secs: u64,
store: NonceStore,
}
impl EnvelopeVerifier {
pub fn new(
key_id: &str,
private_pem: &str,
mac_key_id: &str,
mac_key: &[u8],
clock_skew_secs: u64,
nonce_ttl_secs: u64,
store: NonceStore,
) -> Result<Self, InternalError> {
let pair = crypt::RsaKeyPair::from_private_pem(key_id, private_pem).map_err(|error| {
InternalError::Config(format!("invalid identity private key: {error}"))
})?;
let decryptor = crypt::RsaDecryptor::new(vec![pair])
.map_err(|error| InternalError::Config(format!("invalid identity key set: {error}")))?;
Ok(Self {
decryptor,
mac_key_id: mac_key_id.to_owned(),
mac_key: mac_key.to_vec(),
clock_skew_secs: clock_skew_secs as i64,
nonce_ttl_secs,
store,
})
}
pub async fn verify(
&self,
envelope: &proto::internal::v1::IdentityEnvelope,
) -> Result<VerifiedIdentity, Status> {
verified_identity_impl(self, envelope)
.await
.map_err(InternalError::into_status)
}
}
async fn verified_identity_impl(
verifier: &EnvelopeVerifier,
envelope: &proto::internal::v1::IdentityEnvelope,
) -> Result<VerifiedIdentity, InternalError> {
if envelope.audience != AUDIENCE {
return Err(InternalError::Envelope("audience mismatch".to_string()));
}
if envelope.mac_key_id != verifier.mac_key_id {
return Err(InternalError::Envelope("unknown mac key".to_string()));
}
let now = now_ms();
if envelope.issued_at_unix_ms > now + verifier.clock_skew_secs * 1000 {
return Err(InternalError::Envelope(
"envelope issued in the future".to_string(),
));
}
if envelope.expires_at_unix_ms + verifier.clock_skew_secs * 1000 < now {
return Err(InternalError::Envelope("envelope expired".to_string()));
}
verify_mac(
&verifier.mac_key,
&mac_input(
&envelope.key_id,
&envelope.ciphertext,
&envelope.mac_key_id,
envelope.issued_at_unix_ms,
envelope.expires_at_unix_ms,
&envelope.nonce,
&envelope.audience,
),
&envelope.mac,
)?;
if envelope.nonce.trim().is_empty() || envelope.nonce.len() > 128 {
return Err(InternalError::Envelope("invalid nonce".to_string()));
}
if !verifier
.store
.accept(
&crate::nonce::key(&envelope.audience, &envelope.nonce),
verifier.nonce_ttl_secs,
)
.await?
{
return Err(InternalError::Replay);
}
let claims_json = crypt::decrypt_string(&verifier.decryptor, &envelope.ciphertext)
.map_err(|_| InternalError::Envelope("decryption failed".to_string()))?;
let claims: Claims = serde_json::from_str(&claims_json)
.map_err(|_| InternalError::Envelope("claims are malformed".to_string()))?;
if claims.v != CLAIMS_VERSION {
return Err(InternalError::Envelope(
"unsupported claims version".to_string(),
));
}
if claims.aud != AUDIENCE || claims.uid <= 0 {
return Err(InternalError::Envelope(
"claims audience or user is invalid".to_string(),
));
}
if claims.exp + verifier.clock_skew_secs * 1000 < now {
return Err(InternalError::Envelope("claims expired".to_string()));
}
Ok(VerifiedIdentity {
user_id: claims.uid,
security_version: claims.sv,
session_id: claims.sid,
request_id: if claims.rid.is_empty() {
envelope.request_id.clone()
} else {
claims.rid
},
key_id: envelope.key_id.clone(),
})
}