DEV Community

Cover image for How I Used TPM for Key Encryption in Rust (Using Windows APIs)
tsuruko
tsuruko

Posted on • Edited on

How I Used TPM for Key Encryption in Rust (Using Windows APIs)

I implemented TPM-based key wrapping on Windows using the windows-sys crate.
(Linux version: How I Used TPM for Key Encryption in Rust on Linux (Hardware TPM & vTPM))

At first, I tried key wrapping with tss-esapi, but since it's designed for Linux, using it on Windows would have required a more complex setup.
So I chose windows-sys for this implementation.

In this article, I’ll walk you through the key-wrapping implementation and explain how it works!

Table of Contents


Cargo.toml

[dependencies]
windows-sys = { version = "0.61", features = [
    "Win32_Security_Cryptography",
    "Win32_Foundation",
] }
rand = "0.9"
zeroize = "1.8"
Enter fullscreen mode Exit fullscreen mode

Implementation

This crate uses FFI, so you'll need to call the Windows APIs inside an unsafe block.

use std::{ffi::OsStr, os::windows::ffi::OsStrExt, ptr};

use windows_sys::{
    core::HRESULT,
    Win32::{
        Foundation::NTE_BAD_KEYSET, 
        Security::Cryptography::*,
    },
};
use zeroize::Zeroizing;

type TpmResult<T> = Result<T, HRESULT>;

const KEY_NAME: &str = "RSA_KEY";

fn to_utf16(s: &str) -> Vec<u16> {
    let mut utf16: Vec<u16> = OsStr::new(s).encode_wide().collect();
    utf16.push(0);

    utf16
}

struct KeyWrapper {
    hprov: NCRYPT_PROV_HANDLE,
    hkey: NCRYPT_KEY_HANDLE,
}

impl Drop for KeyWrapper {
    fn drop(&mut self) {
        // Free retrieved provider and key handles
        unsafe {
            if self.hkey != 0 {
                NCryptFreeObject(self.hkey);
            }

            if self.hprov != 0 {
                NCryptFreeObject(self.hprov);
            }
        }
    }
}

// The target key is 32 bytes (AES-256), which is generally recommended

// The status is an i32 (HRESULT) value, 
// and 0 means the function was successful

impl KeyWrapper {
    fn open(key_name: &str) -> TpmResult<Self> {
        let hprov: NCRYPT_PROV_HANDLE = Self::open_provider()?;
        let key_name_utf16 = to_utf16(key_name);

        let hkey = match Self::retrieve_key(hprov, key_name_utf16) {
            Ok(h) => h,
            Err(e) => {
                unsafe {
                    NCryptFreeObject(hprov);
                }
                return Err(e);
            }
        };

        Ok(Self { hprov, hkey })
    }

    fn open_provider() -> TpmResult<NCRYPT_PROV_HANDLE> {
        let mut hprov: NCRYPT_PROV_HANDLE = 0;

        let status =
            unsafe { NCryptOpenStorageProvider(&mut hprov, MS_PLATFORM_CRYPTO_PROVIDER, 0) };
        if status != 0 {
            eprintln!("Failed to open the storage provider");
            return Err(status);
        }

        Ok(hprov)
    }

    fn retrieve_key(hprov: NCRYPT_PROV_HANDLE, key_name: Vec<u16>) -> TpmResult<NCRYPT_KEY_HANDLE> {
        let mut hkey: NCRYPT_KEY_HANDLE = 0;

        let status = unsafe { NCryptOpenKey(hprov, &mut hkey, key_name.as_ptr(), 0, 0) };
        match status {
            0 => Ok(hkey),
            NTE_BAD_KEYSET => {
                // If no key with that name exists
                println!("");
                Self::create_rsa_key(hprov, key_name)
            }
            _ => {
                eprintln!("Failed to retrieve the key");
                Err(status)
            }
        }
    }

    fn create_padding_info() -> BCRYPT_OAEP_PADDING_INFO {
        BCRYPT_OAEP_PADDING_INFO {
            pszAlgId: BCRYPT_SHA256_ALGORITHM,
            pbLabel: ptr::null_mut(),
            cbLabel: 0,
        }
    }

    fn create_rsa_key(
        hprov: NCRYPT_PROV_HANDLE,
        key_name: Vec<u16>,
    ) -> TpmResult<NCRYPT_KEY_HANDLE> {
        let mut hkey: NCRYPT_KEY_HANDLE = 0;

        let status = unsafe {
            // Create a persistent RSA key with the given name
            NCryptCreatePersistedKey(
                hprov,
                &mut hkey,
                BCRYPT_RSA_ALGORITHM,
                key_name.as_ptr(),
                0,
                0,
            )
        };
        if status != 0 {
            eprintln!("Failed to create the RSA key");
            return Err(status);
        }

        // Finalize the key for use
        let status = unsafe { NCryptFinalizeKey(hkey, 0) };
        if status != 0 {
            eprintln!("Failed to finalize the key");
            unsafe {
                NCryptFreeObject(hkey);
            }
            return Err(status);
        }

        Ok(hkey)
    }

    fn wrap_key(&self, target_key: &[u8]) -> TpmResult<Vec<u8>> {
        let padding_info = Self::create_padding_info();
        let mut size: u32 = 0;

        // Zeroize the local copy on drop
        let target_key = Zeroizing::new(target_key.to_vec());

        // Get the required output size if needed
        let status = unsafe {
            NCryptEncrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                ptr::null_mut(),
                0,
                &mut size,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("Failed to get the required output size");
            return Err(status);
        }

        let mut wrapped_key = vec![0u8; size as usize];

        // Wrap the target key
        let status = unsafe {
            NCryptEncrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                wrapped_key.as_mut_ptr(),
                size,
                &mut size,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("Failed to wrap the key");
            return Err(status);
        }

        // Truncate to the actual size
        wrapped_key.truncate(size as usize);

        Ok(wrapped_key)
    }

    fn unwrap_key(&self, target_key: &[u8]) -> TpmResult<Vec<u8>> {
        let padding_info = Self::create_padding_info();
        let mut size: u32 = 0;

        // Get the required output size if needed
        let status = unsafe {
            NCryptDecrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                ptr::null_mut(),
                0,
                &mut size,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("Failed to get the required output size");
            return Err(status);
        }

        let mut unwrapped_key = vec![0u8; size as usize];

        // Unwrap the target key
        let status = unsafe {
            NCryptDecrypt(
                self.hkey,
                target_key.as_ptr(),
                target_key.len() as u32,
                (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
                unwrapped_key.as_mut_ptr(),
                size,
                &mut size,
                NCRYPT_PAD_OAEP_FLAG,
            )
        };
        if status != 0 {
            eprintln!("Failed to unwrap the key");
            return Err(status);
        }

        // Truncate to the actual size
        unwrapped_key.truncate(size as usize);

        Ok(unwrapped_key)
    }
}
Enter fullscreen mode Exit fullscreen mode

The retrieved provider and key handles need to be freed at the end.
In this implementation, the cleanup is handled in Drop.

I also checked the RSA key length used by the TPM provider with NCryptGetProperty().
In my environment, the default was 2048 bits, and the supported range was 1024 to 2048 bits.

This may vary depending on the environment, so if you want to check it on your own system, see the Checking and Setting Key Properties section.


Try running it in the main function:

fn main() {
    let key: [u8; 32] = rand::random();
    println!("Original key: {:?}", key);

    let key_wrapper = match KeyWrapper::open(KEY_NAME) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Error code: {e}");
            return;
        }
    };

    let wrapped_key = match key_wrapper.wrap_key(&key) {
        Ok(k) => {
            println!("Wrapped key: {:?}", k);
            k
        }
        Err(e) => {
            eprintln!("Error code: {e}");
            return;
        }
    };

    match key_wrapper.unwrap_key(&wrapped_key) {
        Ok(k) => println!("Unwrapped key: {:?}", k),
        Err(e) => eprintln!("Error code: {e}"),
    }
}

Enter fullscreen mode Exit fullscreen mode

If you want to delete the key, you can add delete_key() to KeyWrapper and call it:

fn delete_key(&mut self) -> TpmResult<()> {
    let status = unsafe {
        // Delete the key
        NCryptDeleteKey(self.hkey, 0)
    };
    if status != 0 {
        eprintln!("Failed to delete the key");
        return Err(status);
    }

    // NCryptDeleteKey() automatically releases the key handle
    self.hkey = 0;

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Error Codes

This is a list of the main error codes, their corresponding constants, and descriptions.

Code (i32) Code (hex) Name Description
0 0x00000000 ERROR_SUCCESS Operation succeeded
-2146893802 0x80090006 NTE_BAD_KEYSET The key does not exist or is not registered with the TPM
-2146893809 0x8009000F NTE_EXISTS A key with the same name already exists
-2146893786 0x80090026 NTE_INVALID_HANDLE The handle is invalid (provider or key)
-2146893785 0x80090027 NTE_INVALID_PARAMETER One or more parameters are invalid
-2146893810 0x8009000E NTE_NO_MEMORY Not enough memory to complete the operation
-2146893808 0x80090010 NTE_PERM Access denied or operation not permitted
-2146893792 0x80090020 NTE_FAIL An internal error occurred
-2146893783 0x80090029 NTE_NOT_SUPPORTED The algorithm or option is not supported
-2146893815 0x80090009 NTE_BAD_FLAGS Invalid flags specified in dwFlags
-2146893784 0x80090028 NTE_BUFFER_TOO_SMALL Output buffer is too small
-2147479534 0x80090032 NTE_INVALID_STATE Object is in an invalid state (e.g. not finalized)
-2146893816 0x80090008 NTE_BAD_ALGID Invalid algorithm identifier
-2146893814 0x8009000A NTE_BAD_TYPE The key or object type is invalid

Explanation

I referred to the official Microsoft documentation to choose the right functions and understand each parameter, and I used the windows-sys docs to check the parameter types.

References:

In this section, I'll explain each function and parameter used in the implementation, based on these official documents.


1. Open the TPM Provider

First, you need to open the provider using NCryptOpenStorageProvider().

Microsoft Documentation:

windows-sys Documentation:

For phprovider, pass a variable (&mut usize) to receive the provider handle.

For pszprovidername, pass the alias of a key storage provider to load.
If you pass std::ptr::null(), the default key storage provider is loaded.

This implementation uses MS_PLATFORM_CRYPTO_PROVIDER, but if a TPM isn’t available, you can use MS_KEY_STORAGE_PROVIDER instead.

The dwflags parameter is reserved, so it should always be set to 0 for now.

let mut hprov: NCRYPT_PROV_HANDLE = 0;

let status =
    unsafe { NCryptOpenStorageProvider(&mut hprov, MS_PLATFORM_CRYPTO_PROVIDER, 0) };
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:

If this function fails, do not use the provider handle in other functions.
It’s described in the Microsoft documentation:


2. Create a Persistent Key (If It Doesn't Exist)

NCryptCreatePersistedKey() can create a persistent key with a given name.

Microsoft Documentation:

windows-sys Documentation:

For phkey, pass a variable (&mut usize) to receive the key handle.

For pszalgid, pass a null-terminated UTF-16 string that specifies the cryptographic algorithm.

In most cases, you specify one of the CNG Algorithm Identifiers, but you can also specify algorithm identifiers that are registered independently by the provider.
This implementation uses BCRYPT_RSA_ALGORITHM.

For pszkeyname, pass a pointer to a null-terminated UTF-16 string containing the key name.
If the parameter is set to std::ptr::null(), this function will create an ephemeral key that won't be stored.

fn to_utf16(s: &str) -> Vec<u16> {
    let mut utf16: Vec<u16> = OsStr::new(s).encode_wide().collect();
    utf16.push(0);

    utf16
}
Enter fullscreen mode Exit fullscreen mode

encode_wide() converts an &OsStr to UTF-16.
Since pszkeyname needs a null-terminated string, a trailing 0 is appended.

For dwlegacykeyspec, pass a legacy identifier that specifies the key type.

In typical CNG key creation, pass 0, since the other values are for CryptoAPI(CAPI)/CSP compatibility.
If you set it to anything other than 0, it may fail with an error (NTE_NOT_SUPPORTED).

For dwflags, pass flags that modify the behavior of this function.
If no flags are needed, pass 0.


let mut hkey: NCRYPT_KEY_HANDLE = 0;

let status = unsafe {
    // Create a persistent RSA key with the given name
    NCryptCreatePersistedKey(
        hprov,
        &mut hkey,
        BCRYPT_RSA_ALGORITHM,
        key_name.as_ptr(),
        0,
        0,
    )
};
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:


After creating the key, you can use NCryptSetProperty() to configure its properties.
Details about setting the key length are covered in the Checking and Setting Key Properties section.

After creating the key, you need to call NCryptFinalizeKey() to use it.

Microsoft Documentation:

windows-sys Documentation:

For dwflags, pass flags that modify the behavior of this function.
If no flags are needed, pass 0.

NCRYPT_SILENT_FLAG prevents the user interface from being displayed, but it is usually not needed for the TPM-based processing in this example.

let status = unsafe { NCryptFinalizeKey(hkey, 0) };
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:


3. Get a Key from the Provider

If the key already exists, you can retrieve it from the provider using NCryptOpenKey().

Microsoft Documentation:

windows-sys Documentation:

These parameters are mostly the same as those of NCryptCreatePersistedKey().

For dwflags, pass flags that modify the behavior of this function.
If no flags are needed, pass 0.


fn retrieve_key(hprov: NCRYPT_PROV_HANDLE, key_name: Vec<u16>) -> TpmResult<NCRYPT_KEY_HANDLE> {
    let mut hkey: NCRYPT_KEY_HANDLE = 0;

    let status = unsafe { NCryptOpenKey(hprov, &mut hkey, key_name.as_ptr(), 0, 0) };
    match status {
        0 => Ok(hkey),
        NTE_BAD_KEYSET => {
            // If no key with that name exists
            println!("");
            Self::create_rsa_key(hprov, key_name)
        }
        _ => {
            eprintln!("Failed to retrieve the key");
            Err(status)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

If no key with the specified name exists, NCryptOpenKey() returns NTE_BAD_KEYSET.

Some of the return codes:


4. Get the Encrypted Data Size

You need to get the size of the encrypted data before encryption.
If you already know it, you can skip this step.

Microsoft Documentation:

windows-sys Documentation:

For pbinput, pass a pointer to the buffer to encrypt.

For cdinput, pass the size (in bytes) of the data (pbinput).

For ppaddinginfo, pass a pointer to a BCRYPT_OAEP_PADDING_INFO struct that contains padding information.
If you don’t pass NCRYPT_PAD_OAEP_FLAG in dwflags, or if you’re using a symmetric key, pass std::ptr::null().

For pboutput, pass a pointer to the output buffer for the encrypted data.
If you only want to get the encrypted data size, pass std::ptr::null_mut().

For cboutput, pass the buffer size (pboutput).
If pboutput is std::ptr::null_mut(), this parameter is ignored.
However, in my test, passing a non-zero value caused an NTE_INVALID_PARAMETER error.

For pcbresult, pass a variable (&mut u32) that receives the output size for pboutput.
If pboutput is std::ptr::null_mut(), this parameter receives the required output size.

For dwflags, pass a flag that modifies the behavior of this function.
The allowed flags depend on the key type specified by hkey.

NCRYPT_PAD_OAEP_FLAG is recommended for modern security.
When using this flag, you need to pass a pointer to a BCRYPT_OAEP_PADDING_INFO struct in ppaddinginfo.
For symmetric keys, pass 0.

let padding_info = Self::create_padding_info();
let mut size: u32 = 0;

// Zeroize the local copy on drop
let target_key = Zeroizing::new(target_key.to_vec());

// Get the required output size if needed
let status = unsafe {
    NCryptEncrypt(
        self.hkey,
        target_key.as_ptr(),
        target_key.len() as u32,
        (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
        ptr::null_mut(),
        0,
        &mut size,
        NCRYPT_PAD_OAEP_FLAG,
    )
};
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:

BCRYPT_OAEP_PADDING_INFO

Microsoft Documentation:

windows-sys Documentation:

For pszAlgId, pass the hashing algorithm used to create the padding.
You can find the available hash algorithms in the CNG Algorithm Identifiers.

For pbLabel, pass a pointer to a buffer that contains label data used for OAEP padding.
If you don't need it, pass std::ptr::null_mut().

For cbLabel, pass the size of the pbLabel buffer.
If pbLabel is std::ptr::null_mut(), pass 0.

fn create_padding_info() -> BCRYPT_OAEP_PADDING_INFO {
    BCRYPT_OAEP_PADDING_INFO {
        pszAlgId: BCRYPT_SHA256_ALGORITHM,
        pbLabel: ptr::null_mut(),
        cbLabel: 0,
    }
}
Enter fullscreen mode Exit fullscreen mode

This code uses BCRYPT_SHA256_ALGORITHM as the hash algorithm.


5. Wrap the Key

Finally, wrap the key using NCryptEncrypt().

let mut wrapped_key = vec![0u8; size as usize];

// Wrap the target key
let status = unsafe {
    NCryptEncrypt(
        self.hkey,
        target_key.as_ptr(),
        target_key.len() as u32,
        (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
        wrapped_key.as_mut_ptr(),
        size,
        &mut size,
        NCRYPT_PAD_OAEP_FLAG,
    )
};
Enter fullscreen mode Exit fullscreen mode

To get the encrypted data, prepare a buffer with the required size, and pass a pointer to that buffer in pboutput.
If you queried the size with this function, allocate the buffer using the size stored in the variable you passed to pcbresult.

See the previous section for an explanation of NCryptEncrypt().

Lastly, truncate the output buffer to the actual size written, as returned in pcbresult.

wrapped_key.truncate(size as usize);
Enter fullscreen mode Exit fullscreen mode

Unwrap the Key

Decrypting the wrapped key is almost the same as encryption — just replace NCryptEncrypt() with NCryptDecrypt().

fn unwrap_key(&self, target_key: &[u8]) -> TpmResult<Vec<u8>> {
    let padding_info = Self::create_padding_info();
    let mut size: u32 = 0;

    // Get the required output size if needed
    let status = unsafe {
        NCryptDecrypt(
            self.hkey,
            target_key.as_ptr(),
            target_key.len() as u32,
            (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
            ptr::null_mut(),
            0,
            &mut size,
            NCRYPT_PAD_OAEP_FLAG,
        )
    };
    if status != 0 {
        eprintln!("Failed to get the required output size");
        return Err(status);
    }

    let mut unwrapped_key = vec![0u8; size as usize];

    // Unwrap the target key
    let status = unsafe {
        NCryptDecrypt(
            self.hkey,
            target_key.as_ptr(),
            target_key.len() as u32,
            (&padding_info as *const BCRYPT_OAEP_PADDING_INFO).cast(),
            unwrapped_key.as_mut_ptr(),
            size,
            &mut size,
            NCRYPT_PAD_OAEP_FLAG,
        )
    };
    if status != 0 {
        eprintln!("Failed to unwrap the key");
        return Err(status);
    }

    // Truncate to the actual size
    unwrapped_key.truncate(size as usize);

    Ok(unwrapped_key)
}
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:


Checking and Setting Key Properties

To check an object's properties, use NCryptGetProperty().

Microsoft documentation:

windows-sys documentation:

For hobject, pass the handle of the target object.
To retrieve key properties, pass a key handle.

For pszproperty, pass the property identifier you want to retrieve.
You can find a list of identifiers in Key Storage Property Identifiers.
If you want to check supported key lengths, the default length, and related values, pass NCRYPT_LENGTHS_PROPERTY.

For pboutput, pass a pointer to the buffer that receives the property value.
If you only want to get the required buffer size, pass std::ptr::null_mut().

For cboutput, pass the size of the output buffer (pboutput) in bytes.
If pboutput is std::ptr::null_mut(), pass 0.

For pcbresult, pass a pointer to a variable (&mut u32) that receives either the number of bytes actually written or the required buffer size.

For dwflags, pass flags that modify the behavior of this function.
If no flags are needed, pass 0.


fn check_key_lengths(&self) -> TpmResult<()> {
    let mut supported_lengths = NCRYPT_SUPPORTED_LENGTHS {
        dwMinLength: 0,
        dwMaxLength: 0,
        dwIncrement: 0,
        dwDefaultLength: 0,
    };
    let mut size: u32 = 0;

    let status = unsafe {
        NCryptGetProperty(
            self.hkey,
            NCRYPT_LENGTHS_PROPERTY,
            (&mut supported_lengths as *mut NCRYPT_SUPPORTED_LENGTHS).cast(),
            size_of::<NCRYPT_SUPPORTED_LENGTHS>() as u32,
            &mut size,
            0,
        )
    };
    if status != 0 {
        eprintln!("Failed to retrieve the key property");
        return Err(status);
    }

    println!(
        "Default length: {} bits, Supported range: {} to {} bits",
        supported_lengths.dwDefaultLength,
        supported_lengths.dwMinLength,
        supported_lengths.dwMaxLength,
    );

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

To retrieve the current key length, specify NCRYPT_LENGTH_PROPERTY as the property identifier.

fn check_current_key_length(&self) -> TpmResult<()> {
    let mut cur_key_length: u32 = 0;
    let mut size: u32 = 0;

    let status = unsafe {
        NCryptGetProperty(
            self.hkey,
            NCRYPT_LENGTH_PROPERTY,
            (&mut cur_key_length as *mut u32).cast(),
            size_of::<u32>() as u32,
            &mut size,
            0,
        )
    };
    if status != 0 {
        eprintln!("Failed to retrieve the key property");
        return Err(status);
    }

    println!("Current key length: {cur_key_length} bits");

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:


To set an object's properties, use NCryptSetProperty().
For a newly created key, the property must be set before calling NCryptFinalizeKey().

Microsoft documentation:

windows-sys documentation:

For pszproperty, pass the property identifier you want to set.
If you want to set the key length, pass NCRYPT_LENGTH_PROPERTY.

For pbinput, pass a pointer to a buffer that contains the new property value.

For cbinput, pass the size of the buffer passed to pbinput in bytes.

For dwflags, pass flags that modify the behavior of this function.
If no flags are needed, pass 0.


fn set_key_length(hkey: NCRYPT_KEY_HANDLE, length: u32) -> TpmResult<()> {
    let status = unsafe {
        NCryptSetProperty(
            hkey,
            NCRYPT_LENGTH_PROPERTY,
            (&length as *const u32).cast(),
            size_of::<u32>() as u32,
            NCRYPT_PERSIST_FLAG,
        )
    };
    if status != 0 {
        eprintln!("Failed to set the key property");
        return Err(status);
    }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Some of the return codes:


Delete Registered Key

You can delete the registered key using NCryptDeleteKey().

Microsoft Documentation:

windows-sys Documentation:

For dwflags, pass a flag that modifies the behavior of this function.
The only supported value is NCRYPT_SILENT_FLAG.
If no flags are needed, pass 0.

let status = unsafe {
    // Delete the key
    NCryptDeleteKey(self.hkey, 0)
};
Enter fullscreen mode Exit fullscreen mode

If this function succeeds, the key handle is freed automatically.
It’s described in the Microsoft documentation:

Some of the return codes:


Final Thoughts

At first, since I didn’t know much about C++, I struggled to read the function signatures.
But through this implementation, I came to understand them better, and I learned how TPM works.

Thanks for reading!

Top comments (0)