When you create a key on a Marvell HSM, you can optionally request an attestation statement to provide evidence that the key is HSM-protected. The statement is a token that is cryptographically signed directly by the physical hardware and can be verified by the user.
This page provides instructions for verifying the attestation statement and the protected state of the key. It also includes details on how to parse the attestation data to examine key properties or to verify the authenticity of the public key.
Prerequisites
Before you can parse the attestation, you need to retrieve the following required items:
You can use either the Cloud HSM Provider or the Marvell tools to accomplish these tasks.
Verifying an Attestation
After completing the Prerequisites, run the verify_attest.py script to verify whether the attestation is successful or not. The script takes the certificate file and the attestation file as arguments.
#python3 verify_attest.py part.crt attest.dat
Examples below are showing attestation verification using Marvell LiquidSecurity FW version 2.x and 3.x, respectively:
If the attestation is successful, the output will look as below.
#python3 verify_attest.py partition_cert_2.x.pem attest_2.x.dat
*************************************************************************
Usage: ./verify_attest.py <partition.cert> <attestation.dat>
*************************************************************************
RSA signature with raw padding verified
Signature verification passed!
#python3 verify_attest.py partition_cert_3.x.pem attest_3.x.dat
*************************************************************************
Usage: ./verify_attest.py <partition.cert> <attestation.dat>
*************************************************************************
RSA signature with PKCS1 padding verified
Signature verification passed!
If attestation fails, the following output is returned:
#python verify_attest.py part.crt attest.dat
************************************************************
Usage: ./verify_attest.py <partition.cert> <attestation.dat>
************************************************************
Signature failed!
Parsing an Attestation
There are two different scripts for parsing attestations corresponding to the two attestation formats:
Version_1 format (parse_v1.py) – Used when the firmware version is earlier than 3.2.
Version_2 format (parse_v2.py) – Used when the firmware version is 3.2 or later.
Note: The script can only parse an attestation blob that contains one format. You must use the script for your firmware version.
To parse the data, run the script for your format version. For example, for Version_2 format:
python parse_v2.py attest.dat
If the attestation file is for an RSA key, the output will be similar to the following:
Public Key Attestation |
To verify key properties, send the output to a file; for example:
python parse_v2_attest.py attest.dat > parsed.dat
You can then examine the .dat file contents to determine key properties. Number-Attribute mapping is provided in the following table:
NUMBER | ATTRIBUTE NAME | DESCRIPTION |
---|---|---|
0x0000 | OBJ_ATTR_CLASS | Class type of the key. |
0x0001 | OBJ_ATTR_TOKEN | Identifies the key as a token key. |
0x0002 | OBJ_ATTR_PRIVATE | Indicates if this is a shared key or a private key (for symmetric or asymmetric keys). |
0x0003 | OBJ_ATTR_LABEL | Key description. |
0x0086 | OBJ_ATTR_TRUSTED | The key can be trusted for the application that it was created. |
0x0100 | OBJ_ATTR_KEY_TYPE | Subclass type of the key. |
0x0102 | OBJ_ATTR_ID | Key identifier. |
0x0103 | OBJ_ATTR_SENSITIVE | Always true for keys generated on HSM. |
0x0104 | OBJ_ATTR_ENCRYPT | Indicates if key can be used to encrypt data for operations like RSA_Encrypt. Not applicable to EC keys. |
0x0105 | OBJ_ATTR_DECRYPT | Indicates if key can be used to decrypt data for operations like RSA_Decrypt. Not applicable to EC keys. |
0x0106 | OBJ_ATTR_WRAP | Indicates if key can be used to wrap other keys. |
0x0107 | OBJ_ATTR_UNWRAP | Indicates if key can be used to unwrap other keys. |
0x0108 | OBJ_ATTR_SIGN | Indicates if key can be used for signing operations. |
0x010A | OBJ_ATTR_VERIFY | Indicates if key can be used for verifying operations. |
0x010C | OBJ_ATTR_DERIVE | Indicates if key supports key derivation (i.e. if other keys can be derived from this one). |
0x0120 | OBJ_ATTR_MODULUS | RSA key modulus value. |
0x0121 | OBJ_ATTR_MODULUS_BITS | RSA key size in bits. |
0x0122 | OBJ_ATTR_PUBLIC_EXPONENT | RSA key public exponent value. |
0x0161 | OBJ_ATTR_VALUE_LEN | Length in bytes of any value. |
0x0162 | OBJ_ATTR_EXTRACTABLE | Indicates if key can be extracted. |
0x0163 | OBJ_ATTR_LOCAL | Indicates if key was generated locally |
0x0164 | OBJ_ATTR_NEVER_EXTRACTABLE | Indicates if key can never be extracted. |
0x0165 | OBJ_ATTR_ALWAYS_SENSITIVE | Indicates if key has always had the OBJ_ATTR_SENSITIVE attribute set. |
0x0173 | OBJ_ATTR_KCV | Key Check Value. |
0x1000 | OBJ_EXT_ATTR1 | Extended Attribute #1 |
0x1003 | OBJ_ATTR_EKCV | Extended Key Check Value. |
0x0210 | OBJ_ATTR_WRAP_WITH_TRUSTED | Indicates if key can only be wrapped with a wrapping key that has OBJ_ATTR_TRUSTED set. |
0x80000002 | OBJ_ATTR_SPLITTABLE | Indicates if key can be split into multiple parts. |
0x80000003 | OBJ_ATTR_IS_SPLIT | Indicate if it is part of the key split. |
0x80000174 | OBJ_ATTR_ENCRYPT_KEY_MECHANISMS | Indicate if key supports encryption. |
0x80000175 | OBJ_ATTR_DECRYPT_KEY_MECHANISMS | Indicate if key supports decryption. |
0x80000176 | OBJ_ATTR_SIGN_KEY_MECHANISMS | Indicate if key supports signing. |
0x80000177 | OBJ_ATTR_VERIFY_KEY_MECHANISMS | Indicate if key supports signature verification. |
0x80000178 | OBJ_ATTR_WRAP_KEY_MECHANISMS | Indicate if key supports key wrapping. |
0x80000179 | OBJ_ATTR_UNWAP_KEY_MECHANISMS | Indicate if key supports key unwrapping. |
0x80000180 | OBJ_ATTR_DERIVE_KEY_MECHANISMS | Indicate if key supports key derivation. |
grep '0x0162:' parsed.dat
grep '0x0100:' parsed.dat
grep '0x0163:' parsed.dat
Verifying a Public Key
For asymmetric keys, you can verify that the generated key pair matches the public key by comparing the Key Checksum Value (KCV) and Extended KCV (EKCV) of the public key with what is present in the parsed attestation data (the KCV is 3 bytes of the SHA1 hash digest of the modulus; the EKCV is derived using SHA256-based HKDF extract with 32 zero bytes as salt). The steps to do this are described below.
1. Extract the public key out of the HSM.
You can use either the Cloud HSM Provider or the Marvell tools to extract the public key. If you are using the Marvell tools directly, then you can use Cfm2Util and extract the key as in the following example:
./Cfm2Util -p PARTITION_1 singlecmd loginHSM -u CU -p user123 -s crypto_user exportPubKey -k 55 -out public.pem
Application is bound to the partition with name: PARTITION_1
SDK Version: 3.02
Session handle: 28181408 : 0x1ae03a0
Current FIPS mode is: 2
Cfm2LoginHSM returned: 0x00 : HSM Return: SUCCESS
Command: exportPubKey -k 55 -out public.pem
PEM formatted public key is written to public.pem
Cfm2ExportPubKey returned: 0x00 : HSM Return: SUCCESS
2. Parse the attestation data and run the verify_pubkey.py script to verify the public key.
python parse_v2.py attestation.dat > parse.dat
python verify_pubkey.py parse.dat public_key.pem
Result
****************************************
Public key matched with attestation.
****************************************
This section describes the attestation format and how it is used by the application.
Attestation Response Format
When attestation is enabled, after the response the attestation response buffer for a command has two parts: the data and the signature.
Header | Attributes (optional) | RSA Sign |
---|
Attestation Response Format
Attributes and Attestation Flags
The following flags have been defined to control the response attestation and attribute fetching. These flags should be sent to firmware in the request header in order to control the response. These flags control the attribute fetching along with attestation of both header and attributes, only the header, or only the attributes.
KEY_GEN_FLAG_GET_ATTR – Fetches the attributes as part of the response.
KEY_GEN_FLAG_GET_ATTEST – Gets RSA sign of the data as part of the response. This sign is obtained by signing the response header along with attributes (if fetched).
KEY_GEN_FLAG_EXCLUDE_HEADER – Excludes the response header and gets only the sign of the attributes. This flag is valid only if KEY_GEN_FLAG_GET_ATTEST is set. If attribute flag (KEY_GEN_FLAG_GET_ATTR) is not specified and KEY_GEN_FLAG_EXCLUDE_HEADER flag is set, attestation will fail as there is no data to sign.
Flag Usage
In the genKeyArgs structure, the flags field can be set with flags as shown below.
genKeyArgs keyArgs;
keyArgs.flags = KEY_GEN_FLAG_GET_ATTR;
Only attributes will be fetched, along with response header.
keyArgs.flags = KEY_GEN_FLAG_GET_ATTEST;
Only attestation of response header will be performed as there are no attributes.
keyArgs.flags = KEY_GEN_FLAG_GET_ATTR | KEY_GEN_FLAG_GET_ATTEST;
Attributes are fetched along with response header, and both of them will be attested together.
keyArgs.flags = KEY_GEN_FLAG_GET_ATTR | KEY_GEN_FLAG_GET_ATTEST | KEY_GEN_FLAG_EXCLUDE_HEADER;
Attributes are fetched as part of response, but only attributes will be attested, excluding the response header.
keyArgs.flags = KEY_GEN_FLAG_GET_ATTEST | KEY_GEN_FLAG_EXCLUDE_HEADER;
This is invalid, and attestation will fail as there is no data to attest.
keyArgs.flags = KEY_GEN_FLAG_EXCLUDE_HEADER; //
This won’t impact on the response because the attestation flag is not set. EXCLUDE_HEADER is valid only when the ATTEST flag is set.
genKeyArgs is passed to the key generation API, and then inside the API, the attest_flags field of the request header will be assigned with the genKeyArgs flags field.
Parsing the Attribute Buffer
To fetch attributes, the genKeyArgs flags field should be set to KEY_GEN_FLAG_GET_ATTR. The attribute buffer, which should have a size MTU/2 (4500 bytes), must be passed.
genKeyArgs has keyArgs structure. The pAttrObj field of this structure should point to the attribute buffer, and the ulAttrLen field should be assigned with the buffer size.
The response contains the response header, key handles as described below, and attributes that are in pAttrObj.
Interpreting the Attributes in pAttrBuf
Asymmetric key-pair generation example
The attribute buffer data format is as follows:
typedef struct {
Uint16 usObjectVersion;
Uint16 usFlags;
Uint16 usKey1Offset;
Uint16 usKey2Offset;
Object key1_tlv[0]; //place holder for key 1
Object key2_tlv[0]; //place holder for key 2
} TLVKeyInfo;
usObjectVersion = Object version. It will be 1 because this is the first version.
usFlags = Flags set during the request in the request header.
usKey1Offset = Offset to the attributes of first key in attribute buffer (pAttrObj).
usKey2Offset = Offset to attributes of second key in attribute buffer (pAttObj).
TLVKeyInfo *keyinfo = (TLVKeyInfo *) (pAttrObj); // Typecasting pAttrBuf to TLVKeyInfo
uint8_t * key_1 = pAttrObj + betoh46(keyinfo->usKey1Offset); // Pointing to key1 attributes.
Betoh46 is required because the response is received from firmware in big-endian format.
Instead of the above steps, you can use the GET_KEY1_ATTR macro (defined in cavium_wrappers.h).
uint8_t * key1 = GET_KEY1_ATTR(pAttrObj);
Similarly, to get key2 attributes, you can add usKey2Offset to pAttrObj buffer, or use the GET_KEY2_ATTR macro.
uint8_t *key2 = GET_KEY2_ATTR(pAttrObj);
After you get to the attributes of the key, traverse the buffer and find each attribute. Refer to the dumpAttribute function in Cfm2Util.c to learn about traversal.
Note: The above is also true for symmetric key generation, except that the attribute buffer contains key attributes for only one key.
ParkObject
The structure parkOpArgs is used as input structure to parkObject (see Structures Used in Attestation).
The following fields must be populated and passed as part of the input to handle the parkObject response.
flags, input flags, which are discussed above (see Flag Usage).
pParkedObject should point to attribute buffer of size 6000 bytes, after getting the response this buffer contains encrypted parked key data and its attributes (auth data).
ulParkObjectLen is size of attribute buffer
ParkObject response
The response structure of parkObject is shown below:
typedef struct {
ResponseHeader header;
Uint32 ulObjectLen;
Uint8 ucOptionalKeyInfo[0];
Uint8 ucOptionalAttestation[0];
} ParkObjectResponse_v2;
ulObjectLen is the total length of the parked blob, which is the same as ulParkObjLen (see Structures Used in Attestation).
To get the attributes from pParkedObject, follow the procedure described for key generation (see the genKeyArgs).
Uint8_t * parked_blob = GET_KEY1_ATTR (pParkedObject);
This parked blob will contain encrypted key data and auth data (attributes) in TLV format.
With attestation
The attestation response buffer size must be equal to MTU (9000 bytes) and must be passed in the request along with its size. The pAttestResponse field should point to the attestation buffer, and the attestedLength field should be equal to buffer size.
The flags field of the input structure should be set with a flag based on the requirement.
Case 1
flags = KEY_GEN_FLAG_GET_ATTEST
Attest header; attestation response buffer contains header and sign, as shown in the figure below.
Header | Signature |
---|
Attestation Response Format
Parsing the attestation response buffer using external fields:
attestedLength field of input structure will be updated with total attestation response buffer length. To get the sign, add (attestedLength – RSA_SIGN_SIZE) to attestResponse buffer. RSA_SIGN_SIZE is 256.
uint8_t * sign = (Uint8 *) attestedResponse + attestedLength – RSA_SIGN_SIZE.
Parsing the attestation response buffer using fields in header:
In the case of generateKeyPair, the header included in the above diagram is GenerateKeyPairResponse_v2.
typedef struct {
uint32_t ulResponseCode;
uint32_t ulFlags;
uint32_t ulTotalSize;
uint32_t ulBufferSize;
} ResponseHeader;
ResponseHeader is a field in the GenerateKeyPairResponse_v2 structure (see Structures Used in Attestation).
If you look at the above structure, it includes the fields ulTotalSize and ulBufferSize. These fields can be used as offset within the attestation response buffer to get the attributes and sign. Typecast the attestation buffer to GenerateKeyPairResponse_v2 and get the values of ulTotalSize and ulBufferSize.
ulTotalSize is total size of attestation response buffer. This is equal to attestedLength value which was mentioned above.
ulBufferSIze is total size of attributes. In this case as attributes are not fetched, it will be 0.
GenerateKeyPairResponse_v2 * resp = ( GenerateKeyPairResponse_v2 *) attestedResponse;
Uint32 ulTotalSize = betoh32(resp->header.ulTotalSize);
Uint8_t * sign = (Uint8 *)attestedResponse + ulTotalSize - RSA_SIGN_SIZE;
Case 2
flags = KEY_GEN_FLAG_GET_ATTR | KEY_GEN_FLAG_GET_ATTEST
If these flags are set, along with attestedResponse and attestLength fields, you need to set the pAttrObj and ulAttrLen fields of the input structure. pAttrObj has an attribute buffer of size 4500 bytes, and ulAttrLen is the size of the buffer.
This operation will fetch the attributes along with the response header. Attestation will be performed on header and attributes together.
Header | Attributes | RSA Sign |
---|
Attestation Response Format
Parsing the attestation response format:
Attributes are present in both the attestedResponse and pAttrObj buffers. You can parse any of them to get the attributes. To parse the attributes from pAttrObj, use the method described in Parsing the Attribute Buffer because pAttrObj itself can be parsed directly. To fetch the signature, use attestedLength or ulTotalSize, as detailed in Case 1.
To get the attribute buffer from attestedResponse buffer, follow the below steps.
Example for key pair generation:
GenerateKeyPairResponse_v2 * resp = ( GenerateKeyPairResponse_v2 *) attestedResponse;
Uint32 ulTotalSize = betoh32(resp->header.ulTotalSize);
Uint32 ulBufferSize = betoh32(resp->header.ulBufferSize);
uint8_t * pAttrObj = (uint8_t*)attestedResponse + sizeof (GenerateKeyPairResponse_v2);
The obtained attribute buffer can be parsed as described in Parsing the Attribute Buffer.
Similarly, sign can be fetched as:
uint8_t * sign = (uint8_t *) attestedResponse + sizeof (GenerateKeyPairResponse_v2) + ulBufferSIze;
Case 3
flags = KEY_GEN_FLAG_GET_ATTR | KEY_GEN_FLAG_GET_ATTEST |
KEY_GEN_FLAG_EXCLUDE_HEADER;
If these flags are set, attestation will be performed only on the attributes, excluding the header. The header won’t be present in the attestation response buffer, as shown below.
Attributes | Signature |
---|
Attestation Response Format
Parsing the attestation response buffer:
As the header is not available, header fields cannot be used as offset. The only way to parse the buffer is by using the external fields of attestedLength of attestedResponseand ulAttrLen of pAttrObj.
uint8_t *Obj = (uint8_t *) attestedResponse or pAttrObj can be directly used as it also has the attributes.
Getting the sign:
uint8_t * sign = (uint8_t *) attestedResponse + ulAttrLen;
typedef struct {
Uint32 flags;
Uint32 ulSessionHandle;
Uint32 ulMech;
Uint8 *pAttestedResponse;
Uint32 attestedLength;
…..
…...
union {
keyArgs key;
struct {
keyArgs pubkey;
keyArgs privkey;
};
};
} genKeyArgs;
typedef struct {
Uint64 ulKeyHandle;
…....
Uint8 * pAttrObj; ///< key attributes
Uint32 ulAttrLen; ///< key attributes total length
} keyArgs;
ResponseHeader header;
Uint64 ulPublicKey;
Uint64 ulPrivateKey;
Uint8 ucOptionalKeyInfo[0];
Uint8 ucOptionalAttestation[0];
} GenerateKeyPairResponse_v2;
typedef struct {
Uint32 flags;
Uint32 ulSessionHandle;
union {
Uint32 ulFlags;
struct {
Uint32 ulForceInsert:1; ///< Force override the existing key handle if
it matches the object handle on unpark operation
Uint32 ephemeralStorage:1;
};
};
Uint64 ulObjectHandle; ///< key handle of parkable key on park operation and object handle to be assigned
for unparked object on unpark operation
Uint64 ulParkingKeyHandle;
Uint8 * pParkedObject; ///< parked object data
Uint32 ulParkedObjectLen; ///< length of parked object data
Uint8 * pAttestedResponse; ///< attestation response
Uint32 attestedLength; ///< length of attestation response
Uint32 request_id;
} parkOpArgs, unparkOpArgs;