speck-core 0.2.0

Secure runtime package manager for MMU-less microcontrollers
Documentation
//! Cryptographic primitives for module signing
//! 
//! Uses Ed25519 for compact, fast signatures suitable for embedded systems.

use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey, Signature as DalekSignature};
use rand::rngs::OsRng;
use sha2::{Sha256, Digest};
use alloc::vec::Vec;
use alloc::string::{String, ToString};
use core::convert::TryInto;

use crate::error::{Error, Result};
use crate::format::Module;

/// Size of Ed25519 public key in bytes
pub const PUBLIC_KEY_SIZE: usize = 32;

/// Size of Ed25519 secret key in bytes
pub const SECRET_KEY_SIZE: usize = 32;

/// Size of Ed25519 signature in bytes
pub const SIGNATURE_SIZE: usize = 64;

/// Ed25519 public key wrapper with additional metadata
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PublicKey {
    inner: VerifyingKey,
}

impl PublicKey {
    /// Create from raw bytes
    pub fn from_bytes(bytes: &[u8; PUBLIC_KEY_SIZE]) -> Result<Self> {
        VerifyingKey::from_bytes(bytes)
            .map(|inner| Self { inner })
            .map_err(|e| Error::crypto(format!("invalid public key: {}", e)))
    }
    
    /// Export as raw bytes
    pub fn to_bytes(&self) -> [u8; PUBLIC_KEY_SIZE] {
        self.inner.to_bytes()
    }
    
    /// Compute fingerprint (SHA256 truncated to 16 bytes for display)
    pub fn fingerprint(&self) -> [u8; 16] {
        let mut hasher = Sha256::new();
        hasher.update(self.to_bytes());
        let result = hasher.finalize();
        let mut fp = [0u8; 16];
        fp.copy_from_slice(&result[..16]);
        fp
    }
    
    /// Format fingerprint as hexadecimal string
    pub fn fingerprint_hex(&self) -> String {
        hex::encode(self.fingerprint())
    }
    
    /// Verify a signature on a message
    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<()> {
        self.inner.verify(message, &signature.inner)
            .map_err(|_| Error::VerificationFailed {
                details: "signature verification failed".into(),
            })
    }
}

impl AsRef<VerifyingKey> for PublicKey {
    fn as_ref(&self) -> &VerifyingKey {
        &self.inner
    }
}

/// Ed25519 signature wrapper
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Signature {
    inner: DalekSignature,
}

impl Signature {
    /// Create from raw bytes
    pub fn from_bytes(bytes: &[u8; SIGNATURE_SIZE]) -> Result<Self> {
        Ok(Self {
            inner: DalekSignature::from_bytes(bytes),
        })
    }
    
    /// Export as raw bytes
    pub fn to_bytes(&self) -> [u8; SIGNATURE_SIZE] {
        self.inner.to_bytes()
    }
}

/// Ed25519 key pair for signing
#[derive(Debug)]
pub struct KeyPair {
    signing: SigningKey,
}

impl KeyPair {
    /// Generate a new random key pair using OS RNG
    pub fn generate() -> Self {
        let mut csprng = OsRng;
        let signing = SigningKey::generate(&mut csprng);
        Self { signing }
    }
    
    /// Load from secret key seed (32 bytes)
    pub fn from_seed(seed: &[u8; SECRET_KEY_SIZE]) -> Self {
        let signing = SigningKey::from_bytes(seed);
        Self { signing }
    }
    
    /// Export secret key seed
    pub fn to_seed(&self) -> [u8; SECRET_KEY_SIZE] {
        self.signing.to_bytes()
    }
    
    /// Get the public key
    pub fn public(&self) -> PublicKey {
        PublicKey {
            inner: self.signing.verifying_key(),
        }
    }
    
    /// Sign a message
    pub fn sign(&self, message: &[u8]) -> Signature {
        Signature {
            inner: self.signing.sign(message),
        }
    }
    
    /// Sign a module in-place, updating its headers
    pub fn sign_module(&self, module: &mut Module) {
        let signature = self.sign(&module.code);
        module.signature = signature.to_bytes();
        module.public_key = self.public().to_bytes();
        module.header.flags |= format::FLAG_SIGNED;
    }

    /// Verify a module using embedded public key
    pub fn verify_module(module: &Module) -> Result<()> {
        if module.header.flags & format::FLAG_SIGNED == 0 {
            return Err(Error::VerificationFailed {
                details: "module is not signed".into(),
            });
        }
        
        let public_key = PublicKey::from_bytes(&module.public_key)?;
        let signature = Signature::from_bytes(&module.signature)?;
        
        public_key.verify(&module.code, &signature)
    }
}

impl Clone for KeyPair {
    fn clone(&self) -> Self {
        Self::from_seed(&self.to_seed())
    }
}

use crate::format;

/// Parse a key from hex string or file path
#[cfg(feature = "std")]
pub fn parse_key(source: &str) -> Result<KeyPair> {
    use std::fs;
    use std::path::Path;
    
    // Try reading as file first
    if Path::new(source).exists() {
        let contents = fs::read_to_string(source)
            .map_err(|e| Error::crypto(format!("failed to read key file: {}", e)))?;
        return parse_key_hex(&contents);
    }
    
    // Otherwise treat as hex string
    parse_key_hex(source)
}

fn parse_key_hex(hex_str: &str) -> Result<KeyPair> {
    let cleaned: Vec<u8> = hex_str.chars()
        .filter(|c| c.is_ascii_hexdigit())
        .collect::<String>()
        .as_bytes()
        .chunks(2)
        .map(|chunk| {
            u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap()
        })
        .collect();
    
    if cleaned.len() != SECRET_KEY_SIZE {
        return Err(Error::crypto(format!(
            "invalid key length: expected {} bytes, got {}", 
            SECRET_KEY_SIZE, cleaned.len()
        )));
    }
    
    let mut seed = [0u8; SECRET_KEY_SIZE];
    seed.copy_from_slice(&cleaned);
    Ok(KeyPair::from_seed(&seed))
}