proofmode 0.9.0

Capture, share, and preserve verifiable photos and videos
Documentation
use p256::ecdsa::{
    signature::{Signer as P256Signer, Verifier},
    Signature, SigningKey, VerifyingKey,
};
use p256::elliptic_curve::rand_core::OsRng;
use std::sync::Arc;

use super::error::{Result, SignError};
use super::signer::Signer;
use super::types::Platform;

/// Local ES256 signer using P-256 ECDSA keys.
/// Supports per-platform key pairs loaded from PEM files or generated randomly.
#[derive(Clone)]
pub struct LocalES256Signer {
    ios_signing_key: Arc<SigningKey>,
    android_signing_key: Arc<SigningKey>,
    ios_public_key_bytes: Vec<u8>,
    android_public_key_bytes: Vec<u8>,
}

impl LocalES256Signer {
    /// Create a new signer by loading keys from PEM files.
    /// Falls back to random key generation if files don't exist.
    pub fn new(
        ios_key_path: Option<&std::path::Path>,
        android_key_path: Option<&std::path::Path>,
    ) -> Result<Self> {
        let ios_signing_key = match ios_key_path {
            Some(path) if path.exists() => Self::load_key_from_pem(path)?,
            _ => SigningKey::random(&mut OsRng),
        };

        let android_signing_key = match android_key_path {
            Some(path) if path.exists() => Self::load_key_from_pem(path)?,
            _ => SigningKey::random(&mut OsRng),
        };

        let ios_public_key_bytes = ios_signing_key
            .verifying_key()
            .to_encoded_point(false)
            .as_bytes()
            .to_vec();

        let android_public_key_bytes = android_signing_key
            .verifying_key()
            .to_encoded_point(false)
            .as_bytes()
            .to_vec();

        Ok(Self {
            ios_signing_key: Arc::new(ios_signing_key),
            android_signing_key: Arc::new(android_signing_key),
            ios_public_key_bytes,
            android_public_key_bytes,
        })
    }

    /// Create a signer with randomly generated keys (for testing).
    pub fn random() -> Self {
        let ios_signing_key = SigningKey::random(&mut OsRng);
        let android_signing_key = SigningKey::random(&mut OsRng);

        let ios_public_key_bytes = ios_signing_key
            .verifying_key()
            .to_encoded_point(false)
            .as_bytes()
            .to_vec();

        let android_public_key_bytes = android_signing_key
            .verifying_key()
            .to_encoded_point(false)
            .as_bytes()
            .to_vec();

        Self {
            ios_signing_key: Arc::new(ios_signing_key),
            android_signing_key: Arc::new(android_signing_key),
            ios_public_key_bytes,
            android_public_key_bytes,
        }
    }

    /// Create a signer from raw PEM key strings.
    pub fn from_pem(ios_pem: &str, android_pem: &str) -> Result<Self> {
        let ios_signing_key = Self::parse_pem_key(ios_pem)?;
        let android_signing_key = Self::parse_pem_key(android_pem)?;

        let ios_public_key_bytes = ios_signing_key
            .verifying_key()
            .to_encoded_point(false)
            .as_bytes()
            .to_vec();

        let android_public_key_bytes = android_signing_key
            .verifying_key()
            .to_encoded_point(false)
            .as_bytes()
            .to_vec();

        Ok(Self {
            ios_signing_key: Arc::new(ios_signing_key),
            android_signing_key: Arc::new(android_signing_key),
            ios_public_key_bytes,
            android_public_key_bytes,
        })
    }

    fn load_key_from_pem(path: &std::path::Path) -> Result<SigningKey> {
        let pem = std::fs::read_to_string(path).map_err(|e| {
            SignError::InvalidKey(format!("Failed to read {}: {}", path.display(), e))
        })?;
        Self::parse_pem_key(&pem)
    }

    fn parse_pem_key(pem: &str) -> Result<SigningKey> {
        use p256::pkcs8::DecodePrivateKey;
        use p256::SecretKey;

        // Try PKCS8 first, then SEC1 (EC PRIVATE KEY) format
        if let Ok(key) = SigningKey::from_pkcs8_pem(pem) {
            return Ok(key);
        }

        let secret_key = SecretKey::from_sec1_pem(pem)
            .map_err(|e| SignError::InvalidKey(format!("Failed to parse PEM key: {}", e)))?;
        Ok(SigningKey::from(secret_key))
    }

    fn key_for_platform(&self, platform: &Platform) -> &SigningKey {
        match platform {
            Platform::Ios => &self.ios_signing_key,
            Platform::Android => &self.android_signing_key,
        }
    }
}

impl Signer for LocalES256Signer {
    fn sign(&self, platform: &Platform, data: &[u8]) -> Result<Vec<u8>> {
        let key = self.key_for_platform(platform);
        let signature: Signature = key.sign(data);
        // IEEE P1363 format (raw r||s bytes) for C2PA/COSE compatibility
        Ok(signature.to_bytes().to_vec())
    }

    fn public_key(&self, platform: &Platform) -> Result<Vec<u8>> {
        Ok(match platform {
            Platform::Ios => self.ios_public_key_bytes.clone(),
            Platform::Android => self.android_public_key_bytes.clone(),
        })
    }

    fn verify(&self, platform: &Platform, data: &[u8], signature: &[u8]) -> Result<()> {
        let public_key_bytes = match platform {
            Platform::Ios => &self.ios_public_key_bytes,
            Platform::Android => &self.android_public_key_bytes,
        };

        let verifying_key = VerifyingKey::from_sec1_bytes(public_key_bytes)
            .map_err(|e| SignError::InvalidKey(format!("Failed to parse public key: {}", e)))?;

        let sig = if signature.len() == 64 {
            // IEEE P1363 format (raw r||s)
            Signature::from_slice(signature).map_err(|e| {
                SignError::InvalidSignature(format!("Invalid signature format: {}", e))
            })?
        } else {
            // Try DER format
            Signature::from_der(signature).map_err(|e| {
                SignError::InvalidSignature(format!("Invalid signature format: {}", e))
            })?
        };

        verifying_key.verify(data, &sig).map_err(|e| {
            SignError::VerificationFailed(format!("Signature verification failed: {}", e))
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_random_signer_sign_and_verify() {
        let signer = LocalES256Signer::random();
        let data = b"hello world";

        for platform in &[Platform::Ios, Platform::Android] {
            let signature = signer.sign(platform, data).unwrap();
            assert!(!signature.is_empty());
            assert_eq!(signature.len(), 64); // P1363 format

            signer.verify(platform, data, &signature).unwrap();
        }
    }

    #[test]
    fn test_public_key_retrieval() {
        let signer = LocalES256Signer::random();

        let ios_key = signer.public_key(&Platform::Ios).unwrap();
        let android_key = signer.public_key(&Platform::Android).unwrap();

        assert!(!ios_key.is_empty());
        assert!(!android_key.is_empty());
        // Uncompressed EC point is 65 bytes (0x04 prefix + 32 bytes x + 32 bytes y)
        assert_eq!(ios_key.len(), 65);
        assert_eq!(android_key.len(), 65);
        // Different keys for different platforms
        assert_ne!(ios_key, android_key);
    }

    #[test]
    fn test_verify_wrong_data_fails() {
        let signer = LocalES256Signer::random();
        let data = b"hello world";
        let wrong_data = b"wrong data";

        let signature = signer.sign(&Platform::Ios, data).unwrap();
        let result = signer.verify(&Platform::Ios, wrong_data, &signature);
        assert!(result.is_err());
    }

    #[test]
    fn test_verify_wrong_platform_fails() {
        let signer = LocalES256Signer::random();
        let data = b"hello world";

        let signature = signer.sign(&Platform::Ios, data).unwrap();
        let result = signer.verify(&Platform::Android, data, &signature);
        assert!(result.is_err());
    }
}