use base64::{engine, Engine};
use base64ct::LineEnding;
use p256::{
ecdsa::{SigningKey, VerifyingKey},
SecretKey,
};
use crate::{atomic_jwt::prelude::ES256KeyPair, WebPushError};
use super::key::VapidKey;
pub struct VapidKeyGenerator {
secret_key: SecretKey,
verifying_key: VerifyingKey,
}
impl VapidKeyGenerator {
pub fn new() -> Result<Self, WebPushError> {
let secret_key = SecretKey::random(&mut rand::thread_rng());
let signing_key = SigningKey::from(secret_key.clone());
let verifying_key = VerifyingKey::from(&signing_key);
Ok(Self {
secret_key,
verifying_key,
})
}
pub fn secret_key_base64(&self) -> String {
engine::general_purpose::URL_SAFE_NO_PAD.encode(self.secret_key.to_bytes())
}
pub fn public_key_uncompressed(&self) -> Vec<u8> {
self.verifying_key
.to_encoded_point(false)
.to_bytes()
.to_vec()
}
pub fn public_key_base64(&self) -> String {
engine::general_purpose::URL_SAFE_NO_PAD.encode(&self.public_key_uncompressed())
}
pub fn secret_key_bytes(&self) -> Vec<u8> {
self.secret_key.to_bytes().to_vec()
}
pub fn secret_key_to_pem(&self) -> Result<String, WebPushError> {
let sec1_bytes = self
.secret_key
.to_sec1_pem(LineEnding::LF)
.map_err(|_| WebPushError::InvalidSecretKey)?;
Ok(sec1_bytes.to_string())
}
pub fn from_pem(pem_str: &str) -> Result<Self, WebPushError> {
let secret_key =
SecretKey::from_sec1_pem(pem_str).map_err(|_| WebPushError::InvalidSecretKey)?;
let signing_key = SigningKey::from(secret_key.clone());
let verifying_key = VerifyingKey::from(&signing_key);
Ok(Self {
secret_key,
verifying_key,
})
}
pub fn from_base64(encoded: &str) -> Result<Self, WebPushError> {
let decoded = engine::general_purpose::URL_SAFE_NO_PAD
.decode(encoded)
.map_err(|_| WebPushError::InvalidSecretKey)?;
let secret_key =
SecretKey::from_slice(&decoded).map_err(|_| WebPushError::InvalidSecretKey)?;
let signing_key = SigningKey::from(secret_key.clone());
let verifying_key = VerifyingKey::from(&signing_key);
Ok(Self {
secret_key,
verifying_key,
})
}
pub fn to_es256_keypair(&self) -> ES256KeyPair {
ES256KeyPair::from_bytes(&self.secret_key_bytes()).expect("Valid P-256 key")
}
pub fn to_vapid_key(&self) -> VapidKey {
VapidKey::new(self.to_es256_keypair())
}
}
#[cfg(test)]
mod tests {
use super::*;
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
#[test]
fn test_generate_new_vapid_key() {
let generator = VapidKeyGenerator::new().expect("Failed to create generator");
let public_key = generator.public_key_uncompressed();
assert_eq!(public_key.len(), 65);
assert_eq!(public_key[0], 4);
let secret_key = generator.secret_key_bytes();
assert_eq!(secret_key.len(), 32);
}
#[test]
fn test_base64_encoding_decoding() {
let generator = VapidKeyGenerator::new().expect("Failed to create generator");
let secret_base64 = generator.secret_key_base64();
let public_base64 = generator.public_key_base64();
let decoded_secret = URL_SAFE_NO_PAD
.decode(&secret_base64)
.expect("Failed to decode secret");
assert_eq!(decoded_secret, generator.secret_key_bytes());
let decoded_public = URL_SAFE_NO_PAD
.decode(&public_base64)
.expect("Failed to decode public");
assert_eq!(decoded_public, generator.public_key_uncompressed());
}
#[test]
fn test_from_base64() {
let original = VapidKeyGenerator::new().expect("Failed to create generator");
let secret_base64 = original.secret_key_base64();
let restored =
VapidKeyGenerator::from_base64(&secret_base64).expect("Failed to restore from base64");
assert_eq!(original.secret_key_bytes(), restored.secret_key_bytes());
assert_eq!(
original.public_key_uncompressed(),
restored.public_key_uncompressed()
);
assert_eq!(original.public_key_base64(), restored.public_key_base64());
}
#[test]
fn test_invalid_base64() {
let result = VapidKeyGenerator::from_base64("invalid-base64!");
assert!(result.is_err());
let result = VapidKeyGenerator::from_base64("aGVsbG8="); assert!(result.is_err());
}
#[test]
fn test_pem_encoding_decoding() {
let original = VapidKeyGenerator::new().expect("Failed to create generator");
let pem = original.secret_key_to_pem().expect("Failed to create PEM");
assert!(pem.contains("-----BEGIN EC PRIVATE KEY-----"));
assert!(pem.contains("-----END EC PRIVATE KEY-----"));
let restored = VapidKeyGenerator::from_pem(&pem).expect("Failed to restore from PEM");
assert_eq!(original.secret_key_bytes(), restored.secret_key_bytes());
assert_eq!(
original.public_key_uncompressed(),
restored.public_key_uncompressed()
);
}
#[test]
fn test_vapid_key_conversion() {
let generator = VapidKeyGenerator::new().expect("Failed to create generator");
let vapid_key = generator.to_vapid_key();
assert_eq!(generator.public_key_uncompressed(), vapid_key.public_key());
}
#[test]
fn test_known_test_vector() {
let known_private_base64 = "IQ9Ur0ykXoHS9gzfYX0aBjy9lvdrjx_PFUXmie9YRcY";
let known_public_base64 = "BMjQIp55pdbU8pfCBKyXcZjlmER_mXt5LqNrN1hrXbdBS5EnhIbMu3Au-RV53iIpztzNXkGI56BFB1udQ8Bq_H4";
let generator = VapidKeyGenerator::from_base64(known_private_base64)
.expect("Failed to create from known private key");
assert_eq!(generator.public_key_base64(), known_public_base64);
}
#[test]
fn test_generate() {
let generator = VapidKeyGenerator::new().expect("Failed to create generator");
let secret_base64 = generator.secret_key_base64();
let public_base64 = generator.public_key_base64();
println!("Secret key: {}", secret_base64);
println!("Public key: {}", public_base64);
}
}