diff --git a/certificate/amd-vcek-v1-Milan-cert_chain.pem b/certificate/amd-vcek-v1-Milan-cert_chain.pem new file mode 100644 index 000000000..148ca4678 --- /dev/null +++ b/certificate/amd-vcek-v1-Milan-cert_chain.pem @@ -0,0 +1,74 @@ +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGYzCCBBKgAwIBAgIDAQAAMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAIC +BQChHDAaBgkqhkiG9w0BAQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBMHsxFDAS +BgNVBAsMC0VuZ2luZWVyaW5nMQswCQYDVQQGEwJVUzEUMBIGA1UEBwwLU2FudGEg +Q2xhcmExCzAJBgNVBAgMAkNBMR8wHQYDVQQKDBZBZHZhbmNlZCBNaWNybyBEZXZp +Y2VzMRIwEAYDVQQDDAlBUkstTWlsYW4wHhcNMjAxMDIyMTcyMzA1WhcNNDUxMDIy +MTcyMzA1WjB7MRQwEgYDVQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDAS +BgNVBAcMC1NhbnRhIENsYXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5j +ZWQgTWljcm8gRGV2aWNlczESMBAGA1UEAwwJQVJLLU1pbGFuMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEA0Ld52RJOdeiJlqK2JdsVmD7FktuotWwX1fNg +W41XY9Xz1HEhSUmhLz9Cu9DHRlvgJSNxbeYYsnJfvyjx1MfU0V5tkKiU1EesNFta +1kTA0szNisdYc9isqk7mXT5+KfGRbfc4V/9zRIcE8jlHN61S1ju8X93+6dxDUrG2 +SzxqJ4BhqyYmUDruPXJSX4vUc01P7j98MpqOS95rORdGHeI52Naz5m2B+O+vjsC0 +60d37jY9LFeuOP4Meri8qgfi2S5kKqg/aF6aPtuAZQVR7u3KFYXP59XmJgtcog05 +gmI0T/OitLhuzVvpZcLph0odh/1IPXqx3+MnjD97A7fXpqGd/y8KxX7jksTEzAOg +bKAeam3lm+3yKIcTYMlsRMXPcjNbIvmsBykD//xSniusuHBkgnlENEWx1UcbQQrs ++gVDkuVPhsnzIRNgYvM48Y+7LGiJYnrmE8xcrexekBxrva2V9TJQqnN3Q53kt5vi +Qi3+gCfmkwC0F0tirIZbLkXPrPwzZ0M9eNxhIySb2npJfgnqz55I0u33wh4r0ZNQ +eTGfw03MBUtyuzGesGkcw+loqMaq1qR4tjGbPYxCvpCq7+OgpCCoMNit2uLo9M18 +fHz10lOMT8nWAUvRZFzteXCm+7PHdYPlmQwUw3LvenJ/ILXoQPHfbkH0CyPfhl1j +WhJFZasCAwEAAaN+MHwwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSFrBrRQ/fI +rFXUxR1BSKvVeErUUzAPBgNVHRMBAf8EBTADAQH/MDoGA1UdHwQzMDEwL6AtoCuG +KWh0dHBzOi8va2RzaW50Zi5hbWQuY29tL3ZjZWsvdjEvTWlsYW4vY3JsMEYGCSqG +SIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0BAQgwDQYJYIZI +AWUDBAICBQCiAwIBMKMDAgEBA4ICAQC6m0kDp6zv4Ojfgy+zleehsx6ol0ocgVel +ETobpx+EuCsqVFRPK1jZ1sp/lyd9+0fQ0r66n7kagRk4Ca39g66WGTJMeJdqYriw +STjjDCKVPSesWXYPVAyDhmP5n2v+BYipZWhpvqpaiO+EGK5IBP+578QeW/sSokrK +dHaLAxG2LhZxj9aF73fqC7OAJZ5aPonw4RE299FVarh1Tx2eT3wSgkDgutCTB1Yq +zT5DuwvAe+co2CIVIzMDamYuSFjPN0BCgojl7V+bTou7dMsqIu/TW/rPCX9/EUcp +KGKqPQ3P+N9r1hjEFY1plBg93t53OOo49GNI+V1zvXPLI6xIFVsh+mto2RtgEX/e +pmMKTNN6psW88qg7c1hTWtN6MbRuQ0vm+O+/2tKBF2h8THb94OvvHHoFDpbCELlq +HnIYhxy0YKXGyaW1NjfULxrrmxVW4wcn5E8GddmvNa6yYm8scJagEi13mhGu4Jqh +3QU3sf8iUSUr09xQDwHtOQUVIqx4maBZPBtSMf+qUDtjXSSq8lfWcd8bLr9mdsUn +JZJ0+tuPMKmBnSH860llKk+VpVQsgqbzDIvOLvD6W1Umq25boxCYJ+TuBoa4s+HH +CViAvgT9kf/rBq1d+ivj6skkHxuzcxbk1xv6ZGxrteJxVH7KlX7YRdZ6eARKwLe4 +AFZEAwoKCQ== +-----END CERTIFICATE----- diff --git a/src/hb_app.erl b/src/hb_app.erl index 4b11a2766..4d7255c51 100644 --- a/src/hb_app.erl +++ b/src/hb_app.erl @@ -8,13 +8,11 @@ -behaviour(application). -export([start/2, stop/1]). --export([attest_key/0]). -include("include/hb.hrl"). start(_StartType, _StartArgs) -> hb:init(), - attest_key(), hb_sup:start_link(), ok = dev_scheduler_registry:start(), _TimestampServer = ar_timestamp:start(), @@ -22,58 +20,3 @@ start(_StartType, _StartArgs) -> stop(_State) -> ok. - -attest_key() -> - W = hb:wallet(), - Addr = ar_wallet:to_address(W), - - % Pad the address to 32 bytes (64 hex characters) for the TPM nonce - Nonce = pad_to_size(Addr, 32), - - % Pad the address to 64 bytes (128 hex characters) for the TEE nonce - TeeNonce = pad_to_size(Addr, 64), - - % Determine tee-technology based on the existence of TEE devices - TeeTech = case os:cmd("test -e /dev/tdx_guest && echo tdx || (test -e /dev/sev_guest && echo sev-snp)") of - "tdx\n" -> "tdx"; - "sev-snp\n" -> "sev-snp"; - _ -> {error, "No TEE device found"} - end, - - % Proceed if a valid TEE technology is found - case TeeTech of - {error, _} -> {error, "Required TEE device not found"}; - _ -> - Cmd = lists:flatten(io_lib:format("sudo gotpm attest --key AK --nonce ~s --tee-nonce ~s --tee-technology ~s", [Nonce, TeeNonce, TeeTech])), - CommandResult = os:cmd(Cmd), - case is_list(CommandResult) of - true -> - % If CommandResult is a list of integers, convert it to binary - BinaryResult = list_to_binary(CommandResult), - ?event(BinaryResult), - Signed = ar_bundles:sign_item( - #tx{ - tags = [ - {<<"Type">>, <<"TEE-Attestation">>}, - {<<"Address">>, hb_util:id(Addr)} - ], - data = BinaryResult - }, - W - ), - ?event(Signed), - hb_client:upload(Signed), - ok; - false -> - {error, "Unexpected output format from gotpm attest command"} - end - end. - -% Pads an address to the specified byte size (in hex characters) -pad_to_size(Addr, SizeInBytes) -> - HexAddr = binary:encode_hex(Addr), - RequiredLength = SizeInBytes * 2, % Convert bytes to hex characters - Padding = RequiredLength - byte_size(HexAddr), - lists:duplicate(Padding, $0) ++ HexAddr. - -%% internal functions diff --git a/src/sec.erl b/src/sec.erl new file mode 100644 index 000000000..cfb734270 --- /dev/null +++ b/src/sec.erl @@ -0,0 +1,105 @@ +%%%--------------------------------------------------------------------- +%%% Module: sec +%%%--------------------------------------------------------------------- +%%% Purpose: +%%% This module handles the generation and verification of attestation +%%% reports using both TPM and SEV-SNP. It combines attestation reports +%%% from both technologies into a single binary and provides functionality +%%% to verify the combined reports. +%%% +%%% It uses the `sec_tpm` and `sec_tee` modules to interact with the TPM +%%% hardware and SEV-SNP for generating and verifying attestation reports. +%%%--------------------------------------------------------------------- +%%% Exports +%%%--------------------------------------------------------------------- +%%% generate_attestation(Nonce) +%%% Generates a combined attestation report using the provided nonce. +%%% It generates attestation reports from both TPM and SEV-SNP, calculates +%%% their sizes, creates a header containing the sizes, and combines them +%%% into a single binary. +%%% +%%% verify_attestation(AttestationBinary) +%%% Verifies the provided attestation binary by extracting the TPM and +%%% SEV-SNP reports, verifying them using their respective verification +%%% methods, and then combining the results into a single binary. +%%%--------------------------------------------------------------------- + +-module(sec). +-export([generate_attestation/1, verify_attestation/1]). + +-include("include/ao.hrl"). + +-ao_debug(print). + +%% Generate attestation based on the provided nonce (both TPM and SEV-SNP) +generate_attestation(Nonce) -> + ?c({"Generating TPM attestation..."}), + + case sec_tpm:generate_attestation(Nonce) of + {ok, TPMAttestation} -> + ?c({"TPM attestation generated, size:", byte_size(TPMAttestation)}), + + ?c({"Generating SEV-SNP attestation..."}), + + case sec_tee:generate_attestation(Nonce) of + {ok, TEEAttestation} -> + ?c({"SEV-SNP attestation generated, size:", byte_size(TEEAttestation)}), + + %% Calculate sizes of the two attestation binaries + TPMSize = byte_size(TPMAttestation), + TEESize = byte_size(TEEAttestation), + + %% Create the header containing the sizes + Header = <>, + ?c({"Header created, TPMSize:", TPMSize, "TEESize:", TEESize}), + + %% Combine the header with the two attestation binaries + CombinedAttestation = <
>, + ?c({"Combined attestation binary created, total size:", byte_size(CombinedAttestation)}), + + {ok, CombinedAttestation}; + + {error, Reason} -> + ?c({"Error generating SEV-SNP attestation:", Reason}), + {error, Reason} + end; + + {error, Reason} -> + ?c({"Error generating TPM attestation:", Reason}), + {error, Reason} + end. + +%% Verify attestation report based on the provided binary (both TPM and SEV-SNP) +verify_attestation(AttestationBinary) -> + ?c("Verifying attestation..."), + + %% Extract the header (size info) and the attestation binaries + <> = AttestationBinary, + ?c({"Header extracted, TPMSize:", TPMSize, "TEESize:", TEESize}), + + %% Extract the TPM and SEV-SNP attestation binaries based on their sizes + <> = Rest, + ?c({"Extracted TPM and SEV-SNP attestation binaries"}), + + %% Verify TPM attestation + case sec_tpm:verify_attestation(TPMAttestation) of + {ok, _TPMVerification} -> + ?c({"TPM attestation verification completed"}), + + %% Verify SEV-SNP attestation + case sec_tee:verify_attestation(TEEAttestation) of + {ok, _TEEVerification} -> + ?c({"SEV-SNP attestation verification completed"}), + + %% Return success if both verifications succeeded + {ok, "Verified"}; + + {error, Reason} -> + ?c({"Error verifying SEV-SNP attestation:", Reason}), + {error, Reason} + end; + + {error, Reason} -> + ?c({"Error verifying TPM attestation:", Reason}), + {error, Reason} + end. diff --git a/src/sec_helpers.erl b/src/sec_helpers.erl new file mode 100644 index 000000000..4c93293f1 --- /dev/null +++ b/src/sec_helpers.erl @@ -0,0 +1,30 @@ +-module(sec_helpers). +-export([write_to_file/2, read_file/1, run_command/1]). + +-include("include/ao.hrl"). +-ao_debug(print). + +%% Helper function to write data to a file +write_to_file(FilePath, Data) -> + case file:write_file(FilePath, Data) of + ok -> ?c({"Written data to file", FilePath}); + {error, Reason} -> ?c({"Failed to write to file", FilePath, Reason}) + end. + +%% Helper function to read a file +read_file(FilePath) -> + ?c({"Reading file", FilePath}), + case file:read_file(FilePath) of + {ok, Data} -> {FilePath, binary:bin_to_list(Data)}; + {error, Reason} -> {error, Reason} + end. + +%% Generalized function to run a shell command and optionally apply a success function +%% When SuccessFun is provided, it is called upon successful execution +run_command(Command) -> + ?c({"Executing command", Command}), + Output = os:cmd(Command ++ " 2>&1"), + case Output of + "" -> {ok, []}; % Empty output interpreted as success if no output is expected + _ -> {ok, Output} % Return output for further inspection + end. diff --git a/src/sec_tee.erl b/src/sec_tee.erl new file mode 100644 index 000000000..666db1bbf --- /dev/null +++ b/src/sec_tee.erl @@ -0,0 +1,210 @@ +%%%--------------------------------------------------------------------- +%%% Module: sec_tee +%%%--------------------------------------------------------------------- +%%% Purpose: +%%% This module handles SEV-SNP attestation and verification processes. +%%% It generates attestation reports, retrieves necessary certificates, +%%% and verifies the attestation against AMD's root of trust using the +%%% snpguest and OpenSSL commands. +%%%--------------------------------------------------------------------- +%%% Exports +%%%--------------------------------------------------------------------- +%%% generate_attestation(Nonce) +%%% Generates an attestation report and retrieves certificates. +%%% Returns a binary with the attestation report and public key. +%%% +%%% verify_attestation(AttestationBinary) +%%% Verifies the attestation report against the VCEK certificate +%%% and AMD root of trust. +%%%--------------------------------------------------------------------- + +-module(sec_tee). +-export([ + generate_attestation/1, + verify_attestation/1 +]). +-include("include/ao.hrl"). +-ao_debug(print). + +%% Define the file paths +-define(ROOT_DIR, "/tmp/tee"). +-define(REQUEST_FILE, ?ROOT_DIR ++ "/request-file.txt"). +-define(REPORT_FILE, ?ROOT_DIR ++ "/report.bin"). +-define(CERT_CHAIN_FILE, ?ROOT_DIR ++ "/cert_chain.pem"). +-define(VCEK_FILE, ?ROOT_DIR ++ "/vcek.pem"). + +%% Define the commands +-define(SNP_GUEST_REPORT_CMD, "snpguest report " ++ ?REPORT_FILE ++ " " ++ ?REQUEST_FILE). +-define(SNP_GUEST_CERTIFICATES_CMD, "snpguest certificates PEM " ++ ?ROOT_DIR). +-define(VERIFY_VCEK_CMD, "openssl verify --CAfile " ++ ?CERT_CHAIN_FILE ++ " " ++ ?VCEK_FILE). +-define(VERIFY_REPORT_CMD, "snpguest verify attestation " ++ ?ROOT_DIR ++ " " ++ ?REPORT_FILE). + +%% Temporarily hard-code the VCEK download command +-define(DOWNLOAD_VCEK_CMD, "curl --proto \'=https\' --tlsv1.2 -sSf https://kdsintf.amd.com/vcek/v1/Milan/cert_chain -o " ++ ?CERT_CHAIN_FILE). + +%% Generate attestation, request certificates, download VCEK, and upload a transaction +generate_attestation(Nonce) -> + % Check if the root directory exists, and create it if not + case filelib:is_dir(?ROOT_DIR) of + true -> ok; + false -> file:make_dir(?ROOT_DIR) + end, + + % Debug: Print starting attestation generation + ?c("Starting attestation generation..."), + + % Generate request file and attestation report + ?c("Generating request file with nonce..."), + generate_request_file(Nonce), + ?c("Generating attestation report..."), + generate_attestation_report(), + + % Request certificates, download VCEK, and upload the attestation + ?c("Fetching certificates from host memory..."), + fetch_certificates(), + ?c("Downloading VCEK root of trust certificate..."), + download_vcek_cert(), + + % Debug: Print reading the attestation report and public key + ?c("Reading the attestation report and public key..."), + + % Ensure that read_file returns the binary data as expected + {_, ReportData} = sec_helpers:read_file(?REPORT_FILE), + {_, PublicKeyData} = sec_helpers:read_file(?VCEK_FILE), + + % Ensure the read data is in binary format (already handled by read_file) + ReportBin = list_to_binary(ReportData), + PublicKeyBin = list_to_binary(PublicKeyData), + + % Get sizes of the individual files (in binary) + ReportSize = byte_size(ReportBin), + PublicKeySize = byte_size(PublicKeyBin), + + % Debug: Print the sizes of the files + ?c({"Report size", ReportSize}), + ?c({"Public key size", PublicKeySize}), + + % Create a binary header with the sizes and offsets + Header = <>, + + % Create a binary with both the report and public key data concatenated after the header + AttestationBinary = <
>, + + % Debug: Print the final binary data size + ?c({"Generated attestation binary size", byte_size(AttestationBinary)}), + + % Return the binary containing the attestation data + {ok, AttestationBinary}. + + +%% Helper to generate the request file with the padded address and nonce +generate_request_file(Nonce) -> + RequestFile = ?REQUEST_FILE, + NonceHex = binary_to_list(binary:encode_hex(Nonce)), + % Debug: Print the nonce + ?c({"Nonce in hex", NonceHex}), + case sec_helpers:write_to_file(RequestFile, NonceHex) of + {"Written data to file", FilePath} when FilePath == RequestFile -> + ?c({"Request file written successfully", RequestFile}), + ok; + {error, Reason} -> + ?c({"Failed to write request file", RequestFile, Reason}), + {error, failed_to_write_request_file} + end. + + +% Helper to generate the attestation report +generate_attestation_report() -> + Command = ?SNP_GUEST_REPORT_CMD, + ?c({"Running command to generate attestation report", Command}), + case sec_helpers:run_command(Command) of + {ok, _} -> + ?c("SEV-SNP report generated successfully"), + ok; + {error, Reason} -> + ?c({"Failed to generate SEV-SNP report", Reason}), + {error, failed_to_generate_report} + end. + +%% Verify the attestation report using snpguest and VCEK certificate +verify_attestation(AttestationBinary) -> + % Extract the header (size info) + <> = AttestationBinary, + + % Extract the individual components using the sizes from the header + <> = Rest, + <> = Rest1, + + % Debug: Print the extracted components + ?c({"Extracted report data", ReportData}), + ?c({"Extracted public key data", PublicKeyData}), + + % Write the components to temporary files (if needed for verification) + sec_helpers:write_to_file(?REPORT_FILE, ReportData), + sec_helpers:write_to_file(?VCEK_FILE, PublicKeyData), + + % Verify the VCEK certificate + ?c("Verifying VCEK certificate..."), + case sec_helpers:run_command(?VERIFY_VCEK_CMD) of + {ok, CertOutput} -> + TrimmedOutput = string:trim(CertOutput), + ?c({"VCEK certificate verification output", TrimmedOutput}), + ExpectedOutput = ?VCEK_FILE ++ ": OK", % Compute outside the guard + if + TrimmedOutput =:= ExpectedOutput -> + ?c("VCEK certificate signature verified successfully"), + verify_attestation_report(); + true -> + ?c({"VCEK signature verification failed", CertOutput}), + {error, invalid_signature} + end; + {error, Reason} -> + ?c({"Failed to verify VCEK signature", Reason}), + {error, verification_failed} + end. + +%% Verify the attestation report +verify_attestation_report() -> + Command = ?VERIFY_REPORT_CMD, + ?c({"Running command to verify attestation report", Command}), + case sec_helpers:run_command(Command) of + {ok, Output} -> + ?c({"Attestation verification result", Output}), + case string:find(Output, "VEK signed the Attestation Report!", leading) of + nomatch -> + ?c("Attestation verification failed"), + {error, verification_failed}; + _ -> + ?c("Attestation verified successfully"), + {ok, Output} + end; + {error, Reason} -> + ?c({"Failed to verify attestation", Reason}), + {error, verification_failed} + end. + +%% Fetch certificates from host memory and store as PEM files +fetch_certificates() -> + Command = ?SNP_GUEST_CERTIFICATES_CMD, + ?c({"Fetching SEV-SNP certificates from host memory", Command}), + case sec_helpers:run_command(Command) of + {ok, _} -> + ?c("Certificates fetched successfully"), + ok; + {error, Reason} -> + ?c({"Failed to fetch certificates", Reason}), + {error, failed_to_fetch_certificates} + end. + +%% Download VCEK root of trust certificate !!TEMPORARY!! +download_vcek_cert() -> + Command = ?DOWNLOAD_VCEK_CMD, + ?c({"Downloading VCEK root of trust certificate", Command}), + case sec_helpers:run_command(Command) of + {ok, _} -> + ?c("VCEK root of trust certificate downloaded successfully"), + ok; + {error, Reason} -> + ?c({"Failed to download VCEK certificate", Reason}), + {error, failed_to_download_cert} + end. diff --git a/src/sec_tpm.erl b/src/sec_tpm.erl new file mode 100644 index 000000000..4a7208bda --- /dev/null +++ b/src/sec_tpm.erl @@ -0,0 +1,176 @@ +%%%--------------------------------------------------------------------- +%%% Module: sec_tpm +%%%--------------------------------------------------------------------- +%%% Purpose: +%%% This module handles TPM-based attestation and key management processes. +%%% It generates attestation reports, creates and loads TPM keys, and +%%% verifies attestation reports using the TPM hardware. +%%%--------------------------------------------------------------------- +%%% Exports +%%%--------------------------------------------------------------------- +%%% setup_keys() +%%% Sets up the primary and attestation keys on the TPM. +%%% +%%% generate_attestation(Nonce) +%%% Generates an attestation report using a provided nonce and returns +%%% the attestation binary containing the quote, signature, and public key. +%%% +%%% verify_attestation(AttestationBinary) +%%% Verifies the attestation report by comparing the quote with the +%%% signature using TPM2's verification commands. +%%%--------------------------------------------------------------------- + +-module(sec_tpm). +-export([setup_keys/0, generate_attestation/1, verify_attestation/1]). + +-include("include/ao.hrl"). +-ao_debug(print). + +%% Define the file paths +-define(ROOT_DIR, "/tmp/tpm"). +-define(QUOTE_MSG_FILE, ?ROOT_DIR ++ "/quote.msg"). +-define(QUOTE_SIG_FILE, ?ROOT_DIR ++ "/quote.sig"). +-define(AK_PUB_FILE, ?ROOT_DIR ++ "/ak.pub"). +-define(AK_PRIV_FILE, ?ROOT_DIR ++ "/ak.priv"). +-define(AK_CTX_FILE, ?ROOT_DIR ++ "/ak.ctx"). +-define(PRIMARY_CTX_FILE, ?ROOT_DIR ++ "/primary.ctx"). + +%% Define the TPM2 commands using the defined file paths +-define(TPM2_CREATEPRIMARY_CMD, "tpm2_createprimary -C e -g sha256 -G rsa -c " ++ ?PRIMARY_CTX_FILE). +-define(TPM2_CREATE_CMD, "tpm2_create -C " ++ ?PRIMARY_CTX_FILE ++ " -G rsa -u " ++ ?AK_PUB_FILE ++ " -r " ++ ?AK_PRIV_FILE). +-define(TPM2_LOAD_CMD, "tpm2_load -C " ++ ?PRIMARY_CTX_FILE ++ " -u " ++ ?AK_PUB_FILE ++ " -r " ++ ?AK_PRIV_FILE ++ " -c " ++ ?AK_CTX_FILE). +-define(TPM2_READPUBLIC_CMD, "tpm2_readpublic -c " ++ ?AK_CTX_FILE ++ " -o " ++ ?AK_PUB_FILE ++ " -f pem"). +-define(TPM2_QUOTE_CMD, "tpm2_quote -Q --key-context " ++ ?AK_CTX_FILE ++ " -l sha256:0,1 --message " ++ ?QUOTE_MSG_FILE ++ " --signature " ++ ?QUOTE_SIG_FILE ++ " --qualification "). +-define(TPM2_VERIFY_CMD, "tpm2_verifysignature -c " ++ ?AK_CTX_FILE ++ " -g sha256 -m " ++ ?QUOTE_MSG_FILE ++ " -s " ++ ?QUOTE_SIG_FILE). + +%% Generate an attestation using the provided nonce (as a string) +generate_attestation(Nonce) -> + % Check if the root directory exists, and create it if not + case filelib:is_dir(?ROOT_DIR) of + true -> ok; + false -> file:make_dir(?ROOT_DIR) + end, + + % Setup the keys + setup_keys(), + + % Use the address in hex format as the nonce + NonceHex = binary_to_list(binary:encode_hex(Nonce)), + Command = ?TPM2_QUOTE_CMD ++ NonceHex, + ?c({"Running command", Command}), + case sec_helpers:run_command(Command) of + {ok, _} -> + % Read the quote.msg, quote.sig, and ak.pub files + {_, QuoteData} = sec_helpers:read_file(?QUOTE_MSG_FILE), + {_, SignatureData} = sec_helpers:read_file(?QUOTE_SIG_FILE), + {_, AkPubData} = sec_helpers:read_file(?AK_PUB_FILE), + + % Ensure the read data is in binary format + QuoteBin = list_to_binary(QuoteData), + SignatureBin = list_to_binary(SignatureData), + AkPubBin = list_to_binary(AkPubData), + + % Get sizes of the individual files (in binary) + QuoteSize = byte_size(QuoteBin), + SignatureSize = byte_size(SignatureBin), + AkPubSize = byte_size(AkPubBin), + + % Create a binary header with the sizes and offsets + Header = <>, + + % Create a binary with all three files' data concatenated after the header + AttestationBinary = <
>, + + % Log the data (if you need to debug) + ?c({"Attestation generated, binary data:", AttestationBinary}), + + % Return the binary containing the header and files + {ok, AttestationBinary}; + {error, Reason} -> + ?c({"Error generating attestation", Reason}), + {error, Reason} + end. + +%% Verify the attestation using the AttestationBinary +verify_attestation(AttestationBinary) -> + % Extract the header (size info) + <> = AttestationBinary, + + % Extract the individual components using the sizes from the header + <> = Rest, + <> = Rest1, + <> = Rest2, + + % Write the components to temporary files (if needed for verification) + sec_helpers:write_to_file(?QUOTE_MSG_FILE, QuoteData), + sec_helpers:write_to_file(?QUOTE_SIG_FILE, SignatureData), + sec_helpers:write_to_file(?AK_PUB_FILE, AkPubData), + + % Run the TPM verification command using the files + CommandVerify = ?TPM2_VERIFY_CMD, + ?c({"Running command", CommandVerify}), + case sec_helpers:run_command(CommandVerify) of + {ok, VerificationMessage} -> + {ok, VerificationMessage}; + {error, {failed, _}} = Error -> + ?c({"Verification failed", Error}), + Error + end. + + +%% Set up primary and attestation keys +setup_keys() -> + ?c("Starting key setup..."), + create_primary_key(), + create_attestation_key(). + +create_primary_key() -> + CommandPrimary = ?TPM2_CREATEPRIMARY_CMD, + ?c({"Running command", CommandPrimary}), + case sec_helpers:run_command(CommandPrimary) of + {ok, _} -> + ?c("Primary key created successfully"), + create_attestation_key(); + {error, Reason} -> + ?c({"Error creating primary key", Reason}), + {error, Reason} + end. + +create_attestation_key() -> + CommandCreateAK = ?TPM2_CREATE_CMD, + ?c({"Running command", CommandCreateAK}), + case sec_helpers:run_command(CommandCreateAK) of + {ok, _} -> + ?c("Attestation key created successfully"), + load_attestation_key(); + {error, Reason} -> + ?c({"Error creating attestation key", Reason}), + {error, Reason} + end. + +load_attestation_key() -> + CommandLoadAK = ?TPM2_LOAD_CMD, + ?c({"Running command", CommandLoadAK}), + case sec_helpers:run_command(CommandLoadAK) of + {ok, _} -> + ?c("Attestation key loaded successfully"), + export_ak_public_key(); + {error, Reason} -> + ?c({"Error loading attestation key", Reason}), + {error, Reason} + end. + +%% Helper to export the AK public key +export_ak_public_key() -> + Command = ?TPM2_READPUBLIC_CMD, + ?c({"Running command", Command}), + case sec_helpers:run_command(Command) of + {ok, _} -> + ?c("AK public key exported successfully"), + ok; + {error, Reason} -> + ?c({"Error exporting AK public key", Reason}), + {error, Reason} + end. + +