Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:

- name: Run security-focused clippy lints
run: |
cargo clippy --all-targets --all-features -- \
cargo clippy --all-targets --all-features --workspace --exclude openprot-platform-mock -- \
-D warnings \
-W clippy::arithmetic_side_effects \
-W clippy::float_arithmetic \
Expand All @@ -125,7 +125,24 @@ jobs:
-W clippy::panic \
-W clippy::mem_forget \
-W clippy::multiple_unsafe_ops_per_block \
-W clippy::undocumented_unsafe_blocks
-W clippy::undocumented_unsafe_blocks \
-A clippy::assertions_on_constants \
-A clippy::needless_return

- name: Run strict security lints on non-test code
run: |
cargo clippy --lib --bins --workspace --exclude openprot-platform-mock -- \
-D warnings \
-D clippy::arithmetic_side_effects \
-D clippy::float_arithmetic \
-D clippy::indexing_slicing \
-D clippy::unwrap_used \
-D clippy::expect_used \
-D clippy::panic \
-D clippy::mem_forget \
-D clippy::multiple_unsafe_ops_per_block \
-D clippy::undocumented_unsafe_blocks \
-D clippy::assertions_on_constants

- name: Run semgrep security scan
uses: returntocorp/semgrep-action@v1
Expand Down
6 changes: 4 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ repository = "https://github.com/rusty1968/openprot"
edition = "2021"

[workspace.dependencies]
zerocopy = { version = "0.8.27", features = ["derive"] }
zerocopy = { version = "0.8", features = ["derive"] }
zeroize = { version = "1.7", default-features = false, features = ["derive"] }
subtle = { version = "2.5", default-features = false }
subtle = { version = "2", default-features = false }
# Pin to match Hubris ecosystem
rand_core = { version = "0.6", default-features = false }
embedded-hal = "1.0"
4 changes: 2 additions & 2 deletions hal/blocking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ description = "Blocking/synchronous HAL traits for OpenPRoT"
license = "Apache-2.0"

[dependencies]
embedded-hal = "1.0"
zerocopy = { workspace = true }
zeroize = { workspace = true }
subtle = { workspace = true }
rand_core = "0.9"
rand_core = { workspace = true }
embedded-hal = { workspace = true }
182 changes: 178 additions & 4 deletions hal/blocking/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@

use crate::digest::DigestAlgorithm;
use core::fmt::Debug;
use zerocopy::Immutable;
use zerocopy::{FromBytes, IntoBytes};
use zeroize::Zeroize;

/// Trait for converting implementation-specific ECDSA errors into generic error kinds.
///
/// This trait allows HAL implementations to define their own detailed error types
Expand Down Expand Up @@ -348,7 +348,7 @@ pub trait PrivateKey<C: Curve>: Zeroize {
/// - `Ok(())`: The private key is valid
/// - `Err(ErrorKind::WeakKey)`: The key is zero, equal to curve order, or otherwise weak
/// - `Err(ErrorKind::InvalidKeyFormat)`: The key format is invalid
fn validate(&self) -> Result<(), ErrorKind>;
fn validate(&self, curve: &C) -> Result<(), ErrorKind>;
}

/// A trait representing an abstract elliptic curve with associated types for cryptographic operations.
Expand Down Expand Up @@ -579,18 +579,21 @@ pub trait SerializableSignature<C: Curve>: Signature<C> + IntoBytes + FromBytes
/// Trait for ECDSA key generation over a specific elliptic curve.
///
/// This trait enables generation of cryptographically secure ECDSA key pairs
/// using a cryptographic random number generator.
/// using a cryptographic random number generator. Keys can be generated either
/// as standalone objects or stored directly in a key vault for enhanced security.
///
/// # Security Requirements
///
/// - Must use a cryptographically secure random number generator
/// - Generated keys must be uniformly distributed over the valid scalar range
/// - Private keys must be properly zeroized after use
/// - Key vault storage should be preferred for hardware-backed security
///
/// # Example
///
/// ```rust,ignore
/// use openprot_hal_blocking::ecdsa::{EcdsaKeyGen, Curve, ErrorKind};
/// use openprot_hal_blocking::ecdsa::{EcdsaKeyGen, Curve, generate_and_store_keypair};
/// use openprot_hal_blocking::key_vault::{KeyLifecycle, KeyStore};
/// use rand_core::{RngCore, CryptoRng};
///
/// struct MyKeyGenerator;
Expand All @@ -608,6 +611,20 @@ pub trait SerializableSignature<C: Curve>: Signature<C> + IntoBytes + FromBytes
/// unimplemented!()
/// }
/// }
///
/// // Usage example with standalone function:
/// let mut key_gen = MyKeyGenerator;
/// let mut vault = MyKeyVault::new();
/// let mut rng = MyRng::new();
///
/// let public_key = generate_and_store_keypair(
/// &mut key_gen,
/// &mut vault,
/// KeyId::new(42),
/// KeyUsage::SIGNING,
/// KeyMetadata::default(),
/// &mut rng
/// )?;
/// ```
pub trait EcdsaKeyGen<C: Curve>: ErrorType {
/// The type representing the private key for the curve.
Expand All @@ -630,6 +647,76 @@ pub trait EcdsaKeyGen<C: Curve>: ErrorType {
R: rand_core::RngCore + rand_core::CryptoRng;
}

/// Generates an ECDSA key pair and stores the private key in a key vault.
///
/// This function provides integrated key generation and secure storage, ensuring
/// that private keys are immediately stored in a secure vault rather than
/// being exposed in memory. Only the public key is returned to the caller.
///
/// # Parameters
/// - `key_gen`: The key generator implementation
/// - `vault`: The key vault for secure private key storage
/// - `key_id`: Unique identifier for the key in the vault
/// - `usage`: Usage permissions for the stored key
/// - `metadata`: Additional metadata to store with the key
/// - `rng`: A cryptographically secure random number generator
///
/// # Returns
/// The generated public key (private key is securely stored in vault)
///
/// # Security Benefits
/// - Private key is never exposed to caller
/// - Immediate secure storage reduces attack surface
/// - Usage permissions are set atomically with storage
/// - Vault locking mechanisms can be applied immediately
///
/// # Example
/// ```rust,ignore
/// use openprot_hal_blocking::ecdsa::{generate_and_store_keypair, P256};
/// use openprot_hal_blocking::key_vault::{KeyLifecycle, KeyStore};
///
/// let mut key_gen = MyKeyGenerator::new();
/// let mut vault = MyKeyVault::new();
/// let mut rng = MyRng::new();
///
/// let public_key = generate_and_store_keypair::<P256, _, _, _>(
/// &mut key_gen,
/// &mut vault,
/// KeyId::new(42),
/// KeyUsage::SIGNING,
/// KeyMetadata::default(),
/// &mut rng
/// )?;
/// ```
pub fn generate_and_store_keypair<C, G, V, R>(
key_gen: &mut G,
vault: &mut V,
key_id: <V as crate::key_vault::KeyStore>::KeyId,
usage: <V as crate::key_vault::KeyStore>::KeyUsage,
metadata: <V as crate::key_vault::KeyLifecycle>::KeyMetadata,
rng: &mut R,
) -> Result<G::PublicKey, G::Error>
where
C: Curve,
G: EcdsaKeyGen<C>,
R: rand_core::RngCore + rand_core::CryptoRng,
V: crate::key_vault::KeyLifecycle<KeyData = G::PrivateKey> + crate::key_vault::KeyStore,
<V as crate::key_vault::KeyLifecycle>::KeyId: From<<V as crate::key_vault::KeyStore>::KeyId>,
G::Error: From<V::Error>,
{
// Generate key pair
let (private_key, public_key) = key_gen.generate_keypair(rng)?;

// Store private key in vault with metadata
let lifecycle_key_id = <V as crate::key_vault::KeyLifecycle>::KeyId::from(key_id);
vault
.store_key(lifecycle_key_id, private_key, metadata)
.map_err(G::Error::from)?;
vault.set_key_usage(key_id, usage).map_err(G::Error::from)?;

Ok(public_key)
}

/// Trait for ECDSA signing using a digest algorithm.
///
/// This trait provides ECDSA signature generation from message digests.
Expand Down Expand Up @@ -818,6 +905,93 @@ impl Curve for P384 {
type Scalar = [u8; 48];
}

/// A serializable public key for the P384 elliptic curve.
///
/// This implementation provides both coordinate access and serialization
/// capabilities for P384 public keys, supporting the standard 96-byte
/// uncompressed format (48 bytes each for x and y coordinates).
#[derive(Clone, Debug, IntoBytes, FromBytes, Immutable)]
#[repr(C)]
pub struct P384PublicKey {
/// X coordinate (48 bytes for P384)
x: [u8; 48],
/// Y coordinate (48 bytes for P384)
y: [u8; 48],
}

impl P384PublicKey {
/// Create a new P384 public key from raw coordinates
pub fn new(x: [u8; 48], y: [u8; 48]) -> Self {
Self { x, y }
}
}

impl PublicKey<P384> for P384PublicKey {
fn coordinates(
&self,
x_out: &mut <P384 as Curve>::Scalar,
y_out: &mut <P384 as Curve>::Scalar,
) {
// P384::Scalar is [u8; 48], so we can copy directly
*x_out = self.x;
*y_out = self.y;
}

fn from_coordinates(
x: <P384 as Curve>::Scalar,
y: <P384 as Curve>::Scalar,
) -> Result<Self, ErrorKind> {
Ok(Self::new(x, y))
}
}

impl SerializablePublicKey<P384> for P384PublicKey {}

/// A serializable signature for the P384 elliptic curve.
///
/// This implementation provides both signature validation and serialization
/// capabilities for P384 ECDSA signatures, supporting the standard 96-byte
/// format (48 bytes each for r and s components).
#[derive(Clone, Debug, IntoBytes, FromBytes, Immutable)]
#[repr(C)]
pub struct P384Signature {
/// R component (48 bytes for P384)
r: [u8; 48],
/// S component (48 bytes for P384)
s: [u8; 48],
}

impl P384Signature {
/// Create a new P384 signature from r and s components
pub fn new(r: [u8; 48], s: [u8; 48]) -> Self {
Self { r, s }
}
}

impl Signature<P384> for P384Signature {
fn from_coordinates(
r: <P384 as Curve>::Scalar,
s: <P384 as Curve>::Scalar,
) -> Result<Self, ErrorKind> {
// TODO: Add proper signature validation here
// For now, we accept any r,s values but in a real implementation
// we should validate that 1 ≤ r,s < curve_order
Ok(Self::new(r, s))
}

fn coordinates(
&self,
r_out: &mut <P384 as Curve>::Scalar,
s_out: &mut <P384 as Curve>::Scalar,
) {
// P384::Scalar is [u8; 48], so we can copy directly
*r_out = self.r;
*s_out = self.s;
}
}

impl SerializableSignature<P384> for P384Signature {}

/// NIST P-521 elliptic curve marker type.
///
/// This zero-sized type represents the NIST P-521 elliptic curve (secp521r1).
Expand Down
Loading
Loading