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
20 changes: 20 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"permissions": {
"allow": [
"WebFetch(domain:www.ietf.org)",
"Bash(git -C /Users/richbarn/Projects/mlspp diff 6a0b31e^..6a0b31e -- include/mls/crypto.h | head -60)",
"Bash(git -C /Users/richbarn/Projects/mlspp show 61e4d76 --stat | head -40)",
"Bash(grep -n \"Interop\" /Users/richbarn/Projects/mlspp/test/*.cpp | head -20)",
"WebFetch(domain:datatracker.ietf.org)",
"WebFetch(domain:github.com)",
"WebFetch(domain:raw.githubusercontent.com)",
"WebFetch(domain:api.github.com)",
"Bash(grep:*)",
"Bash(git checkout:*)",
"Bash(make:*)",
"Bash(./build/lib/hpke/test/hpke_test)",
"Bash(gh api:*)",
"Bash(./build/test/mlspp_test)"
]
}
}
86 changes: 84 additions & 2 deletions include/mls/crypto.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include <functional>
#include <hpke/digest.h>
#include <hpke/hpke.h>
#include <hpke/random.h>
Expand Down Expand Up @@ -254,28 +255,109 @@ struct PublicJWK

struct SignaturePrivateKey
{
/// Generate a new random signature key pair
static SignaturePrivateKey generate(CipherSuite suite);

/// Parse an exportable private key from serialized form
static SignaturePrivateKey parse(CipherSuite suite, const bytes& data);

/// Derive a signature key pair from a secret
static SignaturePrivateKey derive(CipherSuite suite, const bytes& secret);

/// Import from JWK format
static SignaturePrivateKey from_jwk(CipherSuite suite,
const std::string& json_str);

/// Create from an external (possibly non-exportable) key loaded from a URI.
/// Supported URI schemes depend on the crypto backend:
/// - OpenSSL 3.x: "pkcs11:", "file:", provider-specific URIs
/// - OpenSSL 1.1.x: "engine:<engine_id>:<key_id>"
/// - BoringSSL: Use from_external_callback() instead
static SignaturePrivateKey from_external(CipherSuite suite,
const std::string& key_uri);

/// Create from an external key using a signing callback.
/// This is useful for BoringSSL and custom secure enclave integrations.
/// The callback receives data to sign and returns the signature.
static SignaturePrivateKey from_external_callback(
CipherSuite suite,
SignaturePublicKey pub_key,
std::function<bytes(const bytes&)> sign_callback);

SignaturePrivateKey() = default;

bytes data;
/// The corresponding public key
SignaturePublicKey public_key;

/// Sign a message with the given label
bytes sign(const CipherSuite& suite,
const std::string& label,
const bytes& message) const;

/// Check if this key's private material can be exported
bool exportable() const;

/// Set the public key from the private key data (only for exportable keys)
void set_public_key(CipherSuite suite);

/// Export to JWK format (throws if !exportable())
std::string to_jwk(CipherSuite suite) const;

TLS_SERIALIZABLE(data)
/// TLS serialization - throws if key is not exportable
friend tls::ostream& operator<<(tls::ostream& str,
const SignaturePrivateKey& obj)
{
if (!obj.exportable()) {
throw std::runtime_error(
"Cannot serialize non-exportable SignaturePrivateKey");
}
return str << opt::get(obj.data_);
}

friend tls::istream& operator>>(tls::istream& str, SignaturePrivateKey& obj)
{
bytes data;
str >> data;
obj.data_ = std::move(data);
obj.external_key_ = nullptr;
return str;
}

/// Equality comparison based on public keys.
/// For cryptographic keys, the public key uniquely identifies the key pair.
friend bool operator==(const SignaturePrivateKey& lhs,
const SignaturePrivateKey& rhs)
{
return lhs.public_key.data == rhs.public_key.data;
}

friend bool operator!=(const SignaturePrivateKey& lhs,
const SignaturePrivateKey& rhs)
{
return !(lhs == rhs);
}

private:
/// Raw private key data (empty for non-exportable keys)
std::optional<bytes> data_;

/// Handle to external (possibly non-exportable) key
std::unique_ptr<hpke::Signature::ExternalPrivateKey> external_key_;

SignaturePrivateKey(bytes priv_data, bytes pub_data);
SignaturePrivateKey(
std::unique_ptr<hpke::Signature::ExternalPrivateKey> external_key,
CipherSuite suite);

public:
// Copy operations (clone external key if present)
SignaturePrivateKey(const SignaturePrivateKey& other);
SignaturePrivateKey& operator=(const SignaturePrivateKey& other);

// Move operations
SignaturePrivateKey(SignaturePrivateKey&& other) noexcept = default;
SignaturePrivateKey& operator=(SignaturePrivateKey&& other) noexcept =
default;
};

} // namespace MLS_NAMESPACE
42 changes: 42 additions & 0 deletions lib/hpke/include/hpke/signature.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <functional>
#include <memory>
#include <string>

#include <bytes/bytes.h>
#include <namespace.h>
Expand Down Expand Up @@ -40,6 +42,31 @@ struct Signature
virtual std::unique_ptr<PublicKey> public_key() const = 0;
};

/// ExternalPrivateKey represents a private key that may not be exportable.
/// This is used for keys stored in secure enclaves, HSMs, or other secure
/// storage that can perform signing operations but won't reveal key material.
struct ExternalPrivateKey
{
virtual ~ExternalPrivateKey() = default;

/// Clone this external key
virtual std::unique_ptr<ExternalPrivateKey> clone() const = 0;

/// Get the public key corresponding to this private key
virtual std::unique_ptr<PublicKey> public_key() const = 0;

/// Check if the key material can be exported
virtual bool exportable() const = 0;

/// Export the key as a serializable PrivateKey (throws if !exportable())
virtual std::unique_ptr<PrivateKey> to_exportable(
const Signature& sig) const = 0;
};

/// Type for external signing callbacks (used by BoringSSL and custom
/// backends)
using ExternalSignCallback = std::function<bytes(const bytes& data)>;

const ID id;

virtual std::unique_ptr<PrivateKey> generate_key_pair() const = 0;
Expand Down Expand Up @@ -81,6 +108,21 @@ struct Signature
const bytes& sig,
const PublicKey& pk) const = 0;

/// Sign using an external (possibly non-exportable) private key
virtual bytes sign_external(const bytes& data,
const ExternalPrivateKey& sk) const = 0;

/// Load an external private key from a URI (e.g., "pkcs11:...", "engine:...")
/// Returns nullptr if the URI scheme is not supported.
virtual std::unique_ptr<ExternalPrivateKey> load_external_key(
const std::string& uri) const;

/// Create an external key from a signing callback and public key.
/// This is useful for BoringSSL and custom secure enclave integrations.
virtual std::unique_ptr<ExternalPrivateKey> external_key_from_callback(
std::unique_ptr<PublicKey> pub,
ExternalSignCallback callback) const;

static std::unique_ptr<PrivateKey> generate_rsa(size_t bits);

protected:
Expand Down
Loading
Loading