This is a Rust implementation of the OpenSSL Provider for Azure Managed HSM, converted from the original C implementation in src_provider.
| Platform | Build Script | Test Script | Output |
|---|---|---|---|
| Windows | winbuild.bat |
runtest.bat |
akv_provider.dll |
| Ubuntu/Linux | ./ubuntubuild.sh |
./runtest.sh |
libakv_provider.so |
| Docker | docker-test.sh |
(runs tests in container) | libakv_provider.so |
Common:
- Rust toolchain (1.70+)
- Azure CLI (
az) - OpenSSL 3.x command-line tools
Windows:
- Visual Studio Build Tools
- Git (for vcpkg if setting up OpenSSL locally)
Ubuntu/Linux:
- OpenSSL development headers (
libssl-dev)
# Ubuntu/Debian
sudo apt-get install openssl libssl-devFor a clean, reproducible build and test environment:
cd src_provider_rust
./docker-test.shThis builds the provider in a Ubuntu 22.04 container and runs the full test suite. See DOCKER.md for complete Docker documentation.
The easiest way to build and deploy the provider:
winbuild.batThat's it! This single command will:
- ✅ Check for Rust toolchain
- ✅ Detect or install OpenSSL dependencies
- ✅ Configure Visual Studio environment
- ✅ Build the provider in release mode
- ✅ Deploy to OpenSSL modules directory
- ✅ Ready to test with
runtest.bat
Options:
--debug- Build in debug mode instead of release--skip-deps- Skip dependency checks (faster for rebuilds)
After building with winbuild.bat, simply run:
runtest.batruntest.bat- Run all tests (fast, uses environment variable authentication)runtest.bat /VALIDATE- Run with full Azure HSM validation (slower)runtest.bat /NOENV- Use DefaultAzureCredential instead of environment variableruntest.bat /VALIDATE /NOENV- Full validation with Azure SDK authentication
If you want to pre-install OpenSSL before running winbuild.bat:
.\bootstrap_openssl.ps1The bootstrap script will:
- Clone and bootstrap vcpkg locally
- Install OpenSSL static libraries (
x64-windows-static) - Take ~5-10 minutes on first run
Then build with winbuild.bat (which detects the installed OpenSSL automatically).
If you prefer to build with cargo directly:
# Set OpenSSL location
$env:OPENSSL_DIR = "Q:\src\AzureKeyVaultManagedHSMEngine\src_provider_rust\vcpkg_installed\x64-windows-static"
$env:OPENSSL_STATIC = "1"
# Build
cargo build --release
# Deploy manually
copy target\release\akv_provider.dll C:\OpenSSL\lib\ossl-modules\The compiled provider DLL will be at: target/release/akv_provider.dll
Build and deploy on Ubuntu/Linux:
./ubuntubuild.shThis will:
- ✅ Check for Rust toolchain
- ✅ Verify OpenSSL 3.x installation
- ✅ Build the provider in release mode
- ✅ Deploy to OpenSSL modules directory (
/usr/lib/x86_64-linux-gnu/ossl-modules/) - ✅ Ready to test with
./runtest.sh
Options:
--debug- Build in debug mode instead of release--skip-deps- Skip dependency checks (faster for rebuilds)
After building with ubuntubuild.sh, simply run:
./runtest.sh./runtest.sh- Run all tests (fast, uses environment variable authentication)./runtest.sh --validate- Run with full Azure HSM validation (slower)./runtest.sh --noenv- Use DefaultAzureCredential instead of environment variable./runtest.sh --validate --noenv- Full validation with Azure SDK authentication
# Build
cargo build --release
# Deploy manually (requires sudo)
sudo cp target/release/libakv_provider.so /usr/lib/x86_64-linux-gnu/ossl-modules/akv_provider.soThe compiled provider library will be at: target/release/libakv_provider.so
Both platforms support the same test suite covering:
- RSA PS256/RS256 signing roundtrip
- RSA OAEP decrypt roundtrip
- EC ES256 signing roundtrip
- X.509 CSR generation and verification
- Self-signed certificate generation
- AES key wrap/unwrap
The --noenv//NOENV flag tests the Azure SDK DefaultAzureCredential authentication chain (Managed Identity → Azure CLI → Azure PowerShell) instead of using the AZURE_CLI_ACCESS_TOKEN environment variable.
After building and deploying, verify the provider is loadable:
openssl list -providers -provider akv_provider -provider defaultsrc/lib.rs- Main library entry point and OpenSSL provider initializationsrc/provider.rs- Provider core functionality and URI parsing (fromakv_provider.c)src/store.rs- Store loader for loading keys from Azure (store functions fromakv_provider.c)src/dispatch.rs- OpenSSL dispatch tables and algorithm definitionssrc/keymgmt.rs- Key management operations (fromakv_keymgmt.c,akv_keymgmt_aes.c)src/signature.rs- Signature operations for RSA and EC (fromakv_signature.c)src/cipher.rs- AES cipher operations (fromakv_cipher.c,akv_cipher_aes.c)src/auth.rs- Azure authentication (environment variable + DefaultAzureCredential)src/logging.rs- Logging utilities (fromakv_logging.c)src/base64.rs- Base64 encoding/decoding (frombase64.c)src/http_client.rs- HTTP client for Azure Key Vault API (fromcurl.c)Cargo.toml- Rust dependencies and build configurationbuild.rs- Build script for OpenSSL bindingswinbuild.bat- Windows build and deploy scriptruntest.bat- Windows comprehensive test suiteubuntubuild.sh- Ubuntu/Linux build and deploy scriptruntest.sh- Ubuntu/Linux comprehensive test suite
- Added Azure SDK Authentication: Implemented DefaultAzureCredential support with automatic fallback
- Environment variable authentication (fast path, <1ms)
- DefaultAzureCredential fallback (Managed Identity → Azure CLI → Azure PowerShell)
- Smart
AccessToken::acquire()method checks env var first, then falls back to SDK - Added
azure_core,azure_identity, andtokiodependencies
- Enhanced Test Suite: Added
/NOENVflag toruntest.batfor testing Azure SDK authentication - Performance Optimization: Environment variable authentication is checked first to avoid SDK overhead
- Reworked RSA/ECDSA verification to call the low-level
EVP_PKEY_verify*APIs directly, preventing OpenSSL from double hashing pre-digested data. - Normalized RSA modulus/exponent byte order during import/export so Azure big-endian material is reversed exactly once before reaching
OSSL_PARAM. - Added the
foreign-typesdependency and new OpenSSL FFI bindings required for the verification path. - Checked in the shared
testOpenssl.cnfused by the test harness so CSR/signing scenarios run without manual setup. runtest.bat /SKIPVALIDATIONnow succeeds for RSA (RS256/PS256), ECDSA, CSR, and certificate flows; AES wrap/unwrap still requires implementation.
-
Provider Core (
provider.rs)ProviderContextstructure (corresponds toAKV_PROVIDER_CTX)AkvKeystructure (corresponds toAKV_KEY)AkvAesKeystructure for symmetric keys- URI parsing functions:
parse_uri_keyvalue()- Parseakv:vault=X,name=Y,version=Zformatparse_uri_simple()- Parsemanagedhsm:vault:keynameformatparse_uri()- Try both formats
- Helper functions for case-insensitive string operations
-
Authentication (
auth.rs)AccessTokenstructure for Azure authenticationfrom_env()- Fast path using AZURE_CLI_ACCESS_TOKEN environment variablefrom_default_credential()- Azure SDK DefaultAzureCredential with Tokio runtimeacquire()- Smart method: env var first, DefaultAzureCredential fallback- Support for Managed Identity, Azure CLI, and Azure PowerShell authentication
-
Store Loader (
store.rs)StoreContextstructure (corresponds toAKV_STORE_CTX)- Store C FFI functions:
akv_store_open()- Open store from URIakv_store_attach()- Attach to BIO (not supported)akv_store_settable_ctx_params()- Get settable paramsakv_store_set_ctx_params()- Set params (no-op)akv_store_load()- Load key (skeleton, needs Azure API integration)akv_store_eof()- Check if exhaustedakv_store_close()- Close and free context
-
Dispatch Tables (
dispatch.rs)OsslDispatchstructure matching OpenSSL's definitionOsslAlgorithmstructure matching OpenSSL's definitionAKV_STORE_FUNCTIONS- Store loader dispatch tableAKV_STORE_ALGS- Store algorithm tableAKV_DISPATCH_TABLE- Main provider dispatch tablequery_operation_impl()- Operation query implementation
-
Main Provider (
lib.rs)OSSL_provider_init()- Provider initialization entry pointakv_teardown()- Provider cleanupakv_get_params()- Get provider parameters (skeleton)akv_gettable_params()- Get gettable parameters (skeleton)akv_query_operation()- Query operationsossl_prov_is_running()- Provider status check
-
Base64 Utilities (
base64.rs)- URL-safe base64 encoding/decoding
- Standard base64 encoding/decoding
- Unit tests
-
Logging (
logging.rs)init_logging()- Initialize env_logger- Logging macros
-
HTTP Client Skeleton (
http_client.rs)AkvHttpClientstructure- POST and GET method skeletons
-
OSSL_PARAM Handling
- Implement proper OSSL_PARAM structures in Rust
- Add parameter get/set functions
- Update
akv_get_params()to return actual provider info
-
Store Loader - Azure Integration (
store.rs)- Implement
GetAccessTokenFromEnv()equivalent - Add HTTP calls to Azure Key Vault API
- Implement key type detection
- Handle AES key loading (symmetric)
- Handle RSA/EC key loading (asymmetric with public key material)
- Call object callback with proper OSSL_PARAM arrays
- Implement
-
HTTP Client (
http_client.rs)- Implement actual Azure Key Vault API calls:
AkvGetKey()- Get public key materialAkvGetKeyType()- Get key type and sizeAkvSign()- Sign operationAkvDecrypt()- RSA decryptAkvEncrypt()- RSA encryptAkvWrap()- AES key wrapAkvUnwrap()- AES key unwrap
- Implement actual Azure Key Vault API calls:
-
Key Management (
keymgmt.rs)- Implement RSA KEYMGMT dispatch functions
- Implement EC KEYMGMT dispatch functions
- Implement AES KEYMGMT dispatch functions
- Add to dispatch table
-
Signature Operations (
signature.rs)- Implement RSA signature dispatch functions
- Implement ECDSA signature dispatch functions
- Add signing logic with Azure API
- Add to dispatch table
-
Cipher Operations (
cipher.rs)- Implement RSA asymmetric cipher dispatch functions
- Implement AES key wrap/unwrap dispatch functions
- Add encryption/decryption logic with Azure API
- Add to dispatch table
-
Error Handling
- Implement proper OpenSSL error reporting
- Add comprehensive error types
- Map Rust errors to OpenSSL error codes
-
Memory Management
- Ensure proper cleanup of all allocated resources
- Add reference counting where needed
- Verify no memory leaks
-
Testing
- Unit tests for all modules
- Integration tests with mock HSM
- Test URI parsing edge cases
- Test error conditions
-
Documentation
- Add rustdoc comments to all public functions
- Create examples
- Document architecture decisions
-
Optimization
- Performance profiling
- Reduce allocations where possible
- Optimize hot paths
-
Build System
- Windows build script
- Automated deployment
- CI/CD integration
-
Install Rust (if not already installed)
# Download and run rustup-init.exe from https://rustup.rs/ # Or use winget: winget install Rustlang.Rustup
-
Install OpenSSL Development Libraries
- The project uses vcpkg (already configured)
- OpenSSL headers will be found via vcpkg
The provider's
OSSL_STOREpath (used by every example to load HSM keys viamanagedhsm:<vault>:<key>URIs) triggers a bug in OpenSSL 3.0.2 — the version shipped with Ubuntu 22.04 / WSL2 default. Symptom:RSA object callback failed (returned 0)orEC object callback failed.Fixed upstream in OpenSSL 3.0.7 (openssl#18221). Use Ubuntu 24.04+ (ships OpenSSL 3.0.13) or build OpenSSL >= 3.0.7 from source. All example
*.shscripts sourcecheck-openssl.shand abort early if the host OpenSSL is too old.Windows is unaffected — the provider DLL statically links its own bundled OpenSSL via vcpkg.
# Navigate to the project directory
cd q:\src\AzureKeyVaultManagedHSMEngine\src_provider_rust
# Check code without building (fast)
cargo check
# Build debug version
cargo build
# Build release version (optimized)
cargo build --release
# Run tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Build documentation
cargo doc --openThe compiled library will be located at:
- Debug:
target/debug/akv_provider.dll - Release:
target/release/akv_provider.dll
Copy the compiled DLL to your OpenSSL modules directory:
# Check your OpenSSL modules directory
openssl version -a | findstr MODULESDIR
# Copy the provider DLL (example path, adjust to your MODULESDIR)
copy target\release\akv_provider.dll "C:\OpenSSL\lib\ossl-modules\"The provider supports two authentication methods:
Set the access token directly via environment variable:
# Get access token from Azure CLI
$token = (az account get-access-token --output json --tenant 72f988bf-86f1-41af-91ab-2d7cd011db47 --resource https://managedhsm.azure.net | ConvertFrom-Json).accessToken
$env:AZURE_CLI_ACCESS_TOKEN = $tokenNote: runtest.bat automatically acquires and sets this token for you.
If no environment variable is set, the provider automatically falls back to Azure SDK's DefaultAzureCredential, which tries:
- Managed Identity - For Azure VMs, App Service, Functions
- Azure CLI - Uses
az logincredentials - Azure PowerShell - Uses
Connect-AzAccountcredentials
No configuration needed - just ensure you're logged in with az login or Connect-AzAccount.
Performance Note: DefaultAzureCredential has ~2-3 seconds overhead per operation due to runtime initialization. Use environment variable authentication for best performance.
AZURE_MANAGEDHSM_URL- Your Azure Managed HSM URL (optional, parsed from URI)AKV_LOG_LEVEL- Log level (0=Error, 1=Info, 2=Debug, 3=Trace)AKV_LOG_FILE- Log file path (optional, e.g.,.\logs\akv_provider.log)RUST_LOG- Rust logging filter (e.g.,akv_provider=debug,reqwest=warn)
The provider uses reqwest with native-tls for HTTPS connections to Azure Managed HSM. Certificate validation works as follows:
- Uses SChannel (Windows' native TLS implementation)
- Certificate validation uses the Windows Certificate Store
- Trusts certificates from:
Trusted Root Certification Authorities(system store)Intermediate Certification Authorities
- Uses OpenSSL via native-tls (system OpenSSL)
- Certificate validation uses the system CA certificates (typically
/etc/ssl/certs/)
When the provider makes HTTPS calls to Azure Managed HSM, OpenSSL performs TLS handshake and certificate verification. During this process, OpenSSL attempts to import the server's TLS certificate public keys - and it queries all loaded providers, including our AKV provider.
Without proper handling, this creates a circular dependency:
- Provider needs to call Azure HSM API (HTTPS)
- OpenSSL verifies TLS certificate
- OpenSSL tries to import certificate keys via our provider
- Our provider tries to call Azure HSM API → infinite loop
Note: This issue was discovered during Ubuntu/Linux testing. On Windows, SChannel handles TLS separately from OpenSSL, so the circular dependency doesn't occur. On Linux, native-tls uses the system OpenSSL for TLS, which queries all loaded providers including ours.
The Fix (see keymgmt.rs line ~690): The provider rejects keys that don't have HSM metadata (vault name, key name). In akv_keymgmt_import_common():
// Only accept imports for keys that were loaded via our store (have HSM metadata).
// Keys without metadata are foreign keys (e.g., TLS certificate chains) that should
// be handled by the default provider. This prevents circular dependencies when
// our provider makes HTTPS calls to Azure - the TLS certificate verification
// must use the default provider, not us.
if !akv_key_has_private(key) {
return 0; // Reject - let default provider handle
}When OpenSSL queries our provider to import TLS certificate keys, we return 0 (failure) because those keys don't have HSM metadata. This allows the default provider to handle TLS certificate verification properly.
- Azure Managed HSM uses certificates signed by DigiCert (or similar public CA)
- The DigiCert root CA is pre-installed in the Windows Certificate Store / Linux CA bundle
- The TLS library automatically validates the full certificate chain:
- Server presents:
*.managedhsm.azure.net→ DigiCert Intermediate → DigiCert Root - System checks the root is in the Trusted Root store ✅
- Server presents:
The HTTP client uses secure defaults:
- ✅ Certificate validation is enabled
- ✅ Hostname verification is enabled
- ✅ Uses system CA certificates
No additional configuration is required - the provider automatically trusts Azure's publicly-signed certificates through the operating system's certificate store.
-
Memory Safety: Rust's ownership system eliminates many memory-related bugs
- No manual malloc/free
- Automatic cleanup via Drop trait
- Borrow checker prevents use-after-free
-
Error Handling: Using Result<T, E> instead of error codes
- Explicit error propagation with
?operator - Type-safe error handling
- No silent failures
- Explicit error propagation with
-
Type Safety: Strong type system prevents many common errors
- No void* casting unless in FFI boundary
- Compile-time type checking
- Pattern matching for exhaustive case handling
-
Dependencies: Using crates instead of manual implementations
reqwestfor HTTP instead of libcurlserde_jsonfor JSON instead of json-cbase64crate instead of custom base64azure_coreandazure_identityfor Azure SDK authenticationtokioasync runtime for Azure SDK integration- Less code to maintain
-
Testing: Built-in test framework
- Unit tests colocated with code
- Integration tests in
tests/directory cargo testto run all tests
-
Module System: Clear separation of concerns
- Each module in separate file
- Public/private visibility control
- No header files needed
To continue the conversion from C to Rust:
- Pick a component from the "To Be Implemented" list
- Read the C implementation in
src_provider/ - Implement in Rust following the patterns established
- Add tests to verify functionality
- Update this README with progress
// 1. Read C implementation (e.g., akv_signature.c)
// 2. Create Rust equivalent in signature.rs
pub fn rsa_sign(key_name: &str, digest: &[u8]) -> Result<Vec<u8>, String> {
// Implementation
}
// 3. Add FFI wrapper for OpenSSL
#[no_mangle]
pub unsafe extern "C" fn akv_rsa_sign(...) -> c_int {
// Call rust_rsa_sign and handle errors
}
// 4. Add to dispatch table in dispatch.rs
// 5. Add unit tests
#[cfg(test)]
mod tests {
#[test]
fn test_rsa_sign() {
// Test implementation
}
}This work is on the rust-conversion branch. To switch back to main:
git checkout mainWhen converting C code to Rust:
- Maintain the same functionality and API surface
- Use Rust idioms and best practices (avoid unnecessary
unsafe) - Add comprehensive tests for new implementations
- Document public APIs with rustdoc comments
- Update this README with progress
MIT License - Copyright (c) Microsoft Corporation
The following Azure resources are used for testing:
| Resource | Value | Description |
|---|---|---|
| HSM Vault Name | ManagedHSMOpenSSLEngine |
Azure Managed HSM instance |
| HSM URL | https://ManagedHSMOpenSSLEngine.managedhsm.azure.net |
Full HSM endpoint URL |
| Key Name | Key Type | Algorithm Support | Description |
|---|---|---|---|
myrsakey |
RSA-3072 | RS256, RS384, RS512, PS256, PS384, PS512 | RSA signing and encryption |
ecckey |
EC P-256 | ES256 | ECDSA signing |
myaeskey |
AES-256 | A256KW | AES key wrap/unwrap |
Keys can be referenced using either format:
# Simple format (recommended)
managedhsm:<vault>:<keyname>
managedhsm:ManagedHSMOpenSSLEngine:myrsakey
# With version
managedhsm:ManagedHSMOpenSSLEngine:myrsakey?version=<version>
# Key-value format
akv:vault=ManagedHSMOpenSSLEngine,name=myrsakey,version=<version># Required: Access token for Azure Managed HSM
export AZURE_CLI_ACCESS_TOKEN=$(az account get-access-token --resource https://managedhsm.azure.net --query accessToken -o tsv)
# Optional: Default vault (if not specified in URI)
export AKV_DEFAULT_VAULT=ManagedHSMOpenSSLEngine
# Optional: Logging
export AKV_PROVIDER_LOG=/tmp/akv.log
export RUST_LOG=akv_provider=debug# Set up environment
export OPENSSL_CONF=/path/to/testOpenssl.cnf
export AZURE_CLI_ACCESS_TOKEN=$(az account get-access-token --resource https://managedhsm.azure.net --query accessToken -o tsv)
# Generate CSR with RSA key
openssl req -new -key "managedhsm:ManagedHSMOpenSSLEngine:myrsakey" -subj "/CN=Test" -out test.csr
# Generate CSR with EC key
openssl req -new -key "managedhsm:ManagedHSMOpenSSLEngine:ecckey" -subj "/CN=Test" -out test-ec.csr
# List available keys in HSM
az keyvault key list --hsm-name ManagedHSMOpenSSLEngine --query "[].{name:name, kty:kty}" -o table