Skip to content

Add support for non-exportable signature private keys#461

Closed
bifurcation wants to merge 3 commits intomainfrom
feature/non-exportable-signature-keys
Closed

Add support for non-exportable signature private keys#461
bifurcation wants to merge 3 commits intomainfrom
feature/non-exportable-signature-keys

Conversation

@bifurcation
Copy link
Copy Markdown
Contributor

Summary

  • Add ExternalPrivateKey abstraction in HPKE layer for non-exportable keys
  • Support OpenSSL 3.x OSSL_STORE API for loading keys from URIs (pkcs11:, file:, etc.)
  • Support OpenSSL 1.1.x ENGINE API for external key providers
  • Support callback-based signing for BoringSSL and custom secure enclave integrations
  • Add SignaturePrivateKey::from_external() and from_external_callback() factory methods
  • Add SignaturePrivateKey::exportable() to check if private key data can be extracted
  • Remove TLS serialization from SignaturePrivateKey (non-exportable keys cannot be serialized)

Test plan

  • Existing crypto tests pass (69/69)
  • Callback-based external signing test works for all cipher suites
  • Invalid URI handling tests for OpenSSL 3.x, OpenSSL 1.1.x, and BoringSSL
  • File URI test for OpenSSL 3.x (with optional MLSPP_TEST_KEY_FILE env var)
  • macOS Keychain test using Security.framework SecKey APIs

Notes

  • PKCS#11 integration is left as an exercise for integrators (requires pkcs11-provider or SoftHSM setup)
  • The macOS Keychain test creates a temporary in-memory key (not persisted to Keychain) for test isolation

🤖 Generated with Claude Code

This change enables MLSpp to use signature private keys that cannot be
exported from their secure storage (HSMs, secure enclaves, PKCS#11 tokens).

Key changes:
- Add ExternalPrivateKey abstraction to HPKE Signature interface
- Implement external key loading via OpenSSL 3.x OSSL_STORE API
- Implement external key loading via OpenSSL 1.1.x ENGINE API
- Add callback-based signing for BoringSSL/custom backends
- Update SignaturePrivateKey with from_external() and from_external_callback()
- Remove TLS_SERIALIZABLE from SignaturePrivateKey (non-exportable keys
  cannot be serialized; applications should store key URIs instead)

Tests:
- Callback-based signing test (always runs)
- Invalid URI error handling test (always runs)
- macOS Keychain test using Security.framework (runs on macOS)
- File URI test (OpenSSL 3.x only)

Supported URI schemes:
- OpenSSL 3.x: pkcs11:, file:, and provider-specific URIs
- OpenSSL 1.1.x: engine:<engine_id>:<key_id>
- BoringSSL: Use from_external_callback() with signing callback

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread include/mls/crypto.h Outdated

SignaturePrivateKey() = default;

/// Raw private key data (empty for non-exportable keys)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be optional<bytes> to properly reflect optionality.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, with the change to not being serializable, I think this field can become private.

Comment thread include/mls/crypto.h Outdated
Comment on lines +309 to +312
// Note: SignaturePrivateKey is intentionally NOT TLS serializable.
// Non-exportable keys cannot be serialized, and applications should
// handle key persistence separately (e.g., storing key URIs).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could define serialization/deserialization operators (<< >>) that throw if the key is not serializable.

Comment thread include/mls/crypto.h Outdated

private:
/// Handle to external (possibly non-exportable) key
std::shared_ptr<hpke::Signature::ExternalPrivateKey> external_key_;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be unique_ptr instead of shared_ptr.

Comment thread include/mls/crypto.h Outdated
Comment on lines +332 to +334
SignaturePrivateKey(
std::shared_ptr<hpke::Signature::ExternalPrivateKey> external_key,
bytes pub_data);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SignaturePrivateKey(
std::shared_ptr<hpke::Signature::ExternalPrivateKey> external_key,
bytes pub_data);
SignaturePrivateKey(
std::shared_ptr<hpke::Signature::ExternalPrivateKey> external_key);

Just call external_key->public_key().

bifurcation and others added 2 commits April 10, 2026 09:37
- Change SignaturePrivateKey::data to std::optional<bytes> data_ (private)
- Use unique_ptr instead of shared_ptr for external_key_
- Add clone() method to ExternalPrivateKey for copyability
- Add copy constructor/assignment operator that clone external key
- Add TLS serialization operators that throw for non-exportable keys
- Remove pub_data parameter from external key constructor (derive from external_key->public_key())
- Fix CallbackExternalPrivateKey::public_key() to work by storing serialized bytes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@bifurcation
Copy link
Copy Markdown
Contributor Author

Closing in favor of #424 + #462

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant