dev_snp_nif.erl - AMD SEV-SNP NIF Interface
Overview
Purpose: Native interface to AMD SEV-SNP hardware attestation functions
Module: dev_snp_nif
Type: NIF (Native Implemented Function)
Implementation: Rust via Cargo
This module provides the low-level native interface to AMD SEV-SNP hardware functions. It wraps Rust implementations for generating attestation reports, computing launch digests, and verifying measurements and signatures against hardware root of trust.
Dependencies
- Erlang/OTP:
crypto - Build System: Cargo (Rust)
- Includes:
include/cargo.hrl,include/hb.hrl - Testing:
eunit - Test Files:
test/snp-measurement.json,test/snp-attestation.json
Public Functions Overview
%% Hardware Support
-spec check_snp_support() -> {ok, boolean()}.
%% Attestation Report
-spec generate_attestation_report(UniqueData, VMPL) -> {ok, ReportJSON}.
%% Measurement
-spec compute_launch_digest(Args) -> {ok, Digest}.
-spec verify_measurement(Report, Expected) -> {ok, boolean()}.
%% Signature
-spec verify_signature(Report) -> {ok, boolean()}.Public Functions
1. check_snp_support/0
-spec check_snp_support() -> {ok, boolean()}.Description: Check if AMD SEV-SNP is supported on the current hardware. Returns {ok, true} if SNP is available, {ok, false} otherwise.
-module(dev_snp_nif_support_test).
-include_lib("eunit/include/eunit.hrl").
check_support_test() ->
Result = dev_snp_nif:check_snp_support(),
?assertMatch({ok, _}, Result),
{ok, Supported} = Result,
?assert(is_boolean(Supported)).2. generate_attestation_report/2
-spec generate_attestation_report(UniqueData, VMPL) -> {ok, ReportJSON}
when
UniqueData :: binary(),
VMPL :: integer(),
ReportJSON :: binary().Description: Generate an AMD SEV-SNP attestation report with the provided unique data (nonce) and VMPL (Virtual Machine Privilege Level). The unique data is embedded in the report to bind it to a specific verification request.
Parameters:UniqueData- 64-byte binary nonce to embed in reportVMPL- Virtual Machine Privilege Level (typically 1)
-module(dev_snp_nif_generate_test).
-include_lib("eunit/include/eunit.hrl").
generate_attestation_report_test() ->
case dev_snp_nif:check_snp_support() of
{ok, true} ->
UniqueData = crypto:strong_rand_bytes(64),
VMPL = 1,
Result = dev_snp_nif:generate_attestation_report(UniqueData, VMPL),
?assertMatch({ok, _}, Result),
{ok, ReportJSON} = Result,
?assert(is_binary(ReportJSON));
{ok, false} ->
% SNP not supported, skip test
ok
end.3. compute_launch_digest/1
-spec compute_launch_digest(Args) -> {ok, Digest}
when
Args :: map(),
Digest :: list().Description: Compute the expected SNP launch digest (measurement) from the provided configuration parameters. This digest is compared against the measurement in attestation reports to verify software integrity.
Args Map Keys:vcpus- Number of virtual CPUsvcpu_type- vCPU type identifiervmm_type- VMM type identifierguest_features- Guest feature flagsfirmware- Firmware hash (hex string)kernel- Kernel hash (hex string)initrd- Initrd hash (hex string)append- Append hash (hex string)
-module(dev_snp_nif_digest_test).
-include_lib("eunit/include/eunit.hrl").
compute_launch_digest_test() ->
ArgsMap = #{
vcpus => 32,
vcpu_type => 5,
vmm_type => 1,
guest_features => 16#1,
firmware => "b8c5d4082d5738db6b0fb0294174992738645df70c44cdecf7fad3a62244b788e7e408c582ee48a74b289f3acec78510",
kernel => "69d0cd7d13858e4fcef6bc7797aebd258730f215bc5642c4ad8e4b893cc67576",
initrd => "02e28b6c718bf0a5260d6f34d3c8fe0d71bf5f02af13e1bc695c6bc162120da1",
append => "56e1e5190622c8c6b9daa4fe3ad83f3831c305bb736735bf795b284cb462c9e7"
},
{ok, Result} = dev_snp_nif:compute_launch_digest(ArgsMap),
?assert(is_list(Result)),
ExpectedEncoded = <<"wmSDSQYuzE2M3rQcourJnDJHgalADM8TBev3gyjM5ObRNOn8oglvVznFbaWhajU_">>,
?assertEqual(ExpectedEncoded, hb_util:encode(Result)).
compute_launch_digest_deterministic_test() ->
% firmware is 96 hex chars (48 bytes), kernel/initrd/append are 64 hex chars (32 bytes)
ArgsMap = #{
vcpus => 16,
vcpu_type => 3,
vmm_type => 1,
guest_features => 1,
firmware => "abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234",
kernel => "5678ef005678ef005678ef005678ef005678ef005678ef005678ef005678ef00",
initrd => "9012ab009012ab009012ab009012ab009012ab009012ab009012ab009012ab00",
append => "3456cd003456cd003456cd003456cd003456cd003456cd003456cd003456cd00"
},
{ok, Result1} = dev_snp_nif:compute_launch_digest(ArgsMap),
{ok, Result2} = dev_snp_nif:compute_launch_digest(ArgsMap),
?assertEqual(Result1, Result2).4. verify_measurement/2
-spec verify_measurement(Report, Expected) -> {ok, boolean()}
when
Report :: binary(),
Expected :: binary().Description: Verify that the measurement in an attestation report matches the expected measurement. The report is a JSON-encoded attestation report and the expected value is the binary launch digest.
Test Code:-module(dev_snp_nif_verify_measurement_test).
-include_lib("eunit/include/eunit.hrl").
verify_measurement_test() ->
{ok, MockReport} = file:read_file("test/snp-measurement.json"),
ExpectedMeasurement = <<94,87,4,197,20,11,255,129,179,197,146,104,8,212,
152,248,110,11,60,246,82,254,24,55,201,47,157,229,
163,82,108,66,191,138,241,229,40,144,133,170,116,
109,17,62,20,241,144,119>>,
Result = dev_snp_nif:verify_measurement(MockReport, ExpectedMeasurement),
?assertMatch({ok, true}, Result).
verify_measurement_mismatch_test() ->
{ok, MockReport} = file:read_file("test/snp-measurement.json"),
WrongMeasurement = crypto:strong_rand_bytes(48),
Result = dev_snp_nif:verify_measurement(MockReport, WrongMeasurement),
?assertMatch({error, false}, Result).5. verify_signature/1
-spec verify_signature(Report) -> {ok, boolean()}
when
Report :: binary().Description: Verify the cryptographic signature of an attestation report against AMD's hardware root of trust. This ensures the report was generated by genuine AMD SEV-SNP hardware and has not been tampered with.
Test Code:-module(dev_snp_nif_verify_signature_test).
-include_lib("eunit/include/eunit.hrl").
verify_signature_test() ->
{ok, MockAttestation} = file:read_file("test/snp-attestation.json"),
Result = dev_snp_nif:verify_signature(MockAttestation),
?assertMatch({ok, true}, Result).
verify_signature_invalid_test() ->
% The NIF panics on invalid input, so just verify the function is exported
% In production, input validation should happen before calling the NIF
code:ensure_loaded(dev_snp_nif),
?assert(erlang:function_exported(dev_snp_nif, verify_signature, 1)).NIF Loading
The module uses on-load initialization to load the Rust NIF:
-on_load(init/0).
init() ->
?load_nif_from_crate(dev_snp_nif, 0).
not_loaded(Line) ->
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}).All exported functions return not_loaded error until the NIF is successfully loaded.
Common Patterns
%% Check hardware support before using
case dev_snp_nif:check_snp_support() of
{ok, true} ->
%% SNP available, proceed with attestation
UniqueData = crypto:strong_rand_bytes(64),
{ok, Report} = dev_snp_nif:generate_attestation_report(UniqueData, 1),
process_report(Report);
{ok, false} ->
%% SNP not available, use alternative
{error, snp_not_supported}
end.
%% Compute and verify measurement
Args = #{
vcpus => 32,
vcpu_type => 5,
vmm_type => 1,
guest_features => 1,
firmware => FirmwareHash,
kernel => KernelHash,
initrd => InitrdHash,
append => AppendHash
},
{ok, ExpectedDigest} = dev_snp_nif:compute_launch_digest(Args),
{ok, IsValid} = dev_snp_nif:verify_measurement(ReportJSON, list_to_binary(ExpectedDigest)).
%% Full attestation verification
{ok, MeasurementValid} = dev_snp_nif:verify_measurement(Report, Expected),
{ok, SignatureValid} = dev_snp_nif:verify_signature(Report),
FullyValid = MeasurementValid andalso SignatureValid.Build Configuration
The NIF is built using Cargo. Ensure the Rust crate is properly configured:
# Cargo.toml for dev_snp_nif
[lib]
name = "dev_snp_nif"
crate-type = ["cdylib"]
[dependencies]
rustler = "0.x"
# AMD SEV-SNP specific dependenciesTest Files
The module uses test fixture files:
test/snp-measurement.json- Sample attestation report for measurement teststest/snp-attestation.json- Sample attestation report for signature tests
Error Handling
NIF Not Loaded
{error, {not_loaded, [{module, dev_snp_nif}, {line, Line}]}}This error occurs when:
- Rust NIF compilation failed
- NIF library not found
- Architecture mismatch
Hardware Not Supported
When check_snp_support/0 returns {ok, false}, all attestation operations will fail or be unavailable.
References
- High-Level Interface -
dev_snp.erl - AMD SEV-SNP - AMD Secure Encrypted Virtualization documentation
- Rustler - Erlang NIF library for Rust
- Cargo Integration -
include/cargo.hrl
Notes
- Hardware Dependency: Functions require AMD SEV-SNP capable hardware
- NIF Loading: Module must successfully load Rust NIF on startup
- Binary Format: Reports are JSON-encoded binary strings
- Measurement Size: Launch digest is typically 48 bytes
- VMPL Levels: Virtual Machine Privilege Level (0-3, typically 1)
- Deterministic Digests: Same inputs produce same launch digest
- Root of Trust: Signature verification uses AMD certificate chain
- Test Fixtures: Sample reports required for unit testing
- Error Propagation: NIF errors returned as Erlang terms
- Thread Safety: NIF functions are thread-safe