Marvell LiquidSecurity Software

Key Attestation

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:

  • Attestation file (attest.dat) – You can use the Marvell utility (Cfm2Util) or the tools from the Cloud HSM Provider to retrieve the attestation data.
  • Attestation certificates – The attestation certificates along with their relevant certificate chains. You may have a certificate chain that leads up to the Marvell CA, and another one that leads up to the CA of the Cloud HSM Provider.
  • Public key – You must export the public key; the public key attributes are then verified with the parsed attestation data (see Verifying a Public Key).

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.

python verify_attest.py part.crt attest.dat

If the attestation is successful, the output will look as below.

python verify_attest.py part.crt attest.dat
************************************************************
Usage: ./verify_attest.py <partition.cert> <attestation.dat>
************************************************************
Signature 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
0x0000: 02
0x0100: 00
0x0001: 01
0x0002: 00
0x0104: 01
0x0105: 00
0x0106: 01
0x0107: 00
0x0108: 00
0x010a: 01
0x0163: 01
0x0103: 00
0x0162: 01
0x0003: 63617669756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x0102: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x0161: 00000200
0x0173: ebf8c2
0x1003: baf99b5bbb885149e191c1b107f10638cb34c1390f050e2abb9ed23692c7510b
0x1000: 0000000000000000
0x0120: a20e7c2db60828fb705a35e8377a535c498c1981e967bccc4e3bfb0377316dc19fd251b9656c39df295dee7e4bc5c85ff47e8a0adaeff866cf8dc8b18a9588df08689a524fc891782462e8b55568c41e6efeebdce53645fbd1a74a200cc746388fce21d159102ee2193b1df981d134e925ade65bf2bf74bf87afe99944f03cf6c1701ae58b58fa80473c5801db9e0d8e6c568391dfedc1ca8e24543fb8223d0d3b6fa0565b668a9eb68e8bbca286e84c91591e85480f3d2dc542e31455de358c78704b689abb01bd7575dc1346e4b75fdd708f5af4988aae260387faed9c7521fb419f40d3c0799436e0d81af17967bd3802a73cb5f001f056f1533067cd6047
0x0121: 00000800
0x0122: 010001
Private Key Attestation
0x0000: 03
0x0100: 00
0x0001: 01
0x0002: 01
0x0104: 00
0x0105: 01
0x0106: 00
0x0107: 01
0x0108: 01
0x010a: 00
0x0163: 01
0x0103: 01
0x0162: 01
0x0003: 63617669756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x0102: 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x0161: 000004c1
0x0173: ebf8c2
0x1003: baf99b5bbb885149e191c1b107f10638cb34c1390f050e2abb9ed23692c7510b
0x1000: 0000000000000000
0x0120: a20e7c2db60828fb705a35e8377a535c498c1981e967bccc4e3bfb0377316dc19fd251b9656c39df295dee7e4bc5c85ff47e8a0adaeff866cf8dc8b18a9588df08689a524fc891782462e8b55568c41e6efeebdce53645fbd1a74a200cc746388fce21d159102ee2193b1df981d134e925ade65bf2bf74bf87afe99944f03cf6c1701ae58b58fa80473c5801db9e0d8e6c568391dfedc1ca8e24543fb8223d0d3b6fa0565b668a9eb68e8bbca286e84c91591e85480f3d2dc542e31455de358c78704b689abb01bd7575dc1346e4b75fdd708f5af4988aae260387faed9c7521fb419f40d3c0799436e0d81af17967bd3802a73cb5f001f056f1533067cd6047
0x0121: 00000800
0x0122: 010001
0x80000000: 00000280

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.
0x0100 OBJ_ATTR_KEY_TYPE Subclass 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).
0x0103 OBJ_ATTR_SENSITIVE Always true (1) 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.
0x0163 OBJ_ATTR_LOCAL Indicates if key was generated on HSM.
0x0162 OBJ_ATTR_EXTRACTABLE Indicates if key can be extracted.
  • To see if a key is extractable, examine the 0x0162 field:
grep '0x0162:' parsed.dat
  • To determine the key type, examine the 0x0100 field. Key types are enumerated in the PCKS#11 standard with the prefix CKK_*.
grep '0x0100:' parsed.dat
  • To determine if a key was imported, examine the 0x0163 field. The attribute is true for keys created using the HSM, and false for imported keys.
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.
****************************************

 

Attestation Details

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.

  • The data part contains the response header and attributes (attributes can be optional based on the command).
  • The signature part contains the RSA signature of the data part.
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 section in Flag Usage).

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 attestedResponse and 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;

 

Structures Used in Attestation

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;