Verifying an Intel® EPID Signature

This walkthrough of the verifysig example shows you how to use SDK APIs to verify an Intel® EPID 2.0 signature. Verifysig is built during the SDK build.

To verify an Intel® EPID 1.1 signature see the example code in verifysig11.c. For information on Intel® EPID 1.1 speciifc APIs see EPID 1.1 support.


First, we include headers so we have access to needed declarations.

#include "src/verifysig.h"
#include <stdlib.h>
#include "util/buffutil.h"
#include "util/envutil.h"

The utility headers are used by verifysig for logging and buffer management. The epid/verifier/api.h header provides access to the core verifier APIs, and the epid/common/file_parser.h header provides an API for parsing buffers formatted according to the various IoT Intel® EPID binary file formats.


We define a stub function responsible for checking that the CA certificate is authorized by the root CA.

bool IsCaCertAuthorizedByRootCa(void const* data, size_t size) {
(void)data;
(void)size;
return true;
}

IsCaCertAuthorizedByRootCa is called from main.c to validate the CA certificate before calling Verify. In an actual implementation, you need to provide an implementation to validate the issuing CA certificate with the CA root certificate before using it in parse functions.


We use Verify to verify an Intel® EPID signature. Verify is a wrapper function that isolates SDK API functionality for the purpose of this walkthrough.

EpidStatus Verify(EpidSignature const* sig, size_t sig_len, void const* msg,
size_t msg_len, void const* basename, size_t basename_len,
void const* signed_priv_rl, size_t signed_priv_rl_size,
void const* signed_sig_rl, size_t signed_sig_rl_size,
void const* signed_grp_rl, size_t signed_grp_rl_size,
VerifierRl const* ver_rl, size_t ver_rl_size,
void const* signed_pub_key, size_t signed_pub_key_size,
EpidCaCertificate const* cacert, HashAlg hash_alg,
VerifierPrecomp* verifier_precomp,
bool verifier_precomp_is_input) {

The Verify parameters were either sent by the verifier to the member, or they were part of the member's configuration. The exceptions are the sig and sig_len parameters, which we use to input the signature to be verified.

The verifier might send the message to the member or there may be another mechanism to choose the message, but the way the message is communicated is outside the scope of the Intel® EPID scheme.

We use the parameters verifier_precomp and verifier_precomp_is_input to pass in a pre-computation blob if provided. We can use the pre-computation blob to increase performance when verifying signatures repeatedly with the same group public key.

The member and the verifier agree on the message, basename, hash algorithm, and SigRL that the verifier uses for verification.

Warning
The use of a name-based signature creates a platform unique pseudonymous identifier. Because it reduces the member's privacy, the user should be notified when it is used and should have control over its use.


Next we do basic variable setup.

EpidStatus result = kEpidErr;
VerifierCtx* ctx = NULL;
PrivRl* priv_rl = NULL;
SigRl* sig_rl = NULL;
GroupRl* grp_rl = NULL;
do {
GroupPubKey pub_key = {0};

We create pointers to resources to be allocated and we use the do {} while(0) idiom so that we can reliably free resources on return from Verify. We also allocate the group public key on the stack.


Next, we authenticate and extract the group public key using EpidParseGroupPubKeyFile.

result = EpidParseGroupPubKeyFile(signed_pub_key, signed_pub_key_size,
cacert, &pub_key);
if (kEpidNoErr != result) {
break;
}

EpidParseGroupPubKeyFile takes a buffer containing a group public key in issuer binary format and validates that the public key is signed by the private key that corresponds to the provided CA certificate, reading the key into pub_key in the process.


Next, we create a verifier context using EpidVerifierCreate.

&pub_key, verifier_precomp_is_input ? verifier_precomp : NULL, &ctx);
if (kEpidNoErr != result) {
break;
}

If a pre-computation blob is provided to the top level application, we use it. Otherwise, we pass in NULL.


Then we serialize pre-computed verifier data using EpidVerifierWritePrecomp.

result = EpidVerifierWritePrecomp(ctx, verifier_precomp);
if (kEpidNoErr != result) {
break;
}

The serialized verifier pre-computation blob can be used to greatly increase performance of EpidVerifierCreate in future sessions if the same group public key is used.


We use EpidVerifierSetHashAlg to indicate the hash algorithm used for verification, which should be the same algorithm that the member used when signing.

result = EpidVerifierSetHashAlg(ctx, hash_alg);
if (kEpidNoErr != result) {
break;
}

After the hash algorithm is set, future calls to EpidVerify will use the same algorithm.


We use EpidVerifierSetBasename to indicate the basename used for verification, which should be the same one that the member used when signing.

result = EpidVerifierSetBasename(ctx, basename, basename_len);
if (kEpidNoErr != result) {
break;
}

After the basename is set, future calls to EpidVerify will use the same basename.


Before we verify a signature, we have to configure revocation lists so that we can check to see if a signer's group membership has been revoked.

Note
Configured revocation lists are referenced directly by the verifier until a new revocation list is set or the verifier is destroyed. Until the verifier is destroyed, we do not modify the revocation lists.


We set the private key based revocation list using EpidVerifierSetPrivRl.

if (signed_priv_rl) {
// authenticate and determine space needed for RL
size_t priv_rl_size = 0;
result = EpidParsePrivRlFile(signed_priv_rl, signed_priv_rl_size, cacert,
NULL, &priv_rl_size);
if (kEpidSigInvalid == result) {
// authentication failure
break;
}
if (kEpidNoErr != result) {
break;
}
priv_rl = AllocBuffer(priv_rl_size);
if (!priv_rl) {
result = kEpidMemAllocErr;
break;
}
// fill the rl
result = EpidParsePrivRlFile(signed_priv_rl, signed_priv_rl_size, cacert,
priv_rl, &priv_rl_size);
if (kEpidNoErr != result) {
break;
}
// set private key based revocation list
result = EpidVerifierSetPrivRl(ctx, priv_rl, priv_rl_size);
if (kEpidNoErr != result) {
break;
}
} // if (signed_priv_rl)

We use EpidParsePrivRlFile to:

  • extract the private key based revocation list
  • validate that the revocation list was signed by the private key corresponding to the provided CA certificate
  • validate that the size of the input buffer is correct
  • determine the required size of the revocation list output buffer

To determine the required priv_rl buffer size, we provide a null pointer for the output buffer when calling EpidParsePrivRlFile.

After we find out the required size of the priv_rl buffer, we allocate memory for it. Then we fill the buffer using EpidParsePrivRlFile.


Next, we set the signature based revocation list using EpidVerifierSetSigRl.

if (signed_sig_rl) {
// authenticate and determine space needed for RL
size_t sig_rl_size = 0;
result = EpidParseSigRlFile(signed_sig_rl, signed_sig_rl_size, cacert,
NULL, &sig_rl_size);
if (kEpidSigInvalid == result) {
// authentication failure
break;
}
if (kEpidNoErr != result) {
break;
}
sig_rl = AllocBuffer(sig_rl_size);
if (!sig_rl) {
result = kEpidMemAllocErr;
break;
}
// fill the rl
result = EpidParseSigRlFile(signed_sig_rl, signed_sig_rl_size, cacert,
sig_rl, &sig_rl_size);
if (kEpidNoErr != result) {
break;
}
// set signature based revocation list
result = EpidVerifierSetSigRl(ctx, sig_rl, sig_rl_size);
if (kEpidNoErr != result) {
break;
}
} // if (signed_sig_rl)

We use EpidParseSigRlFile to:

  • extract the signature based revocation list
  • validate that the revocation list was signed by the private key corresponding to the provided CA certificate
  • validate that the size of the input buffer is correct
  • determine the required size of the revocation list output buffer

To determine the required sig_rl buffer size, we provide a null pointer for the output buffer when calling EpidParseSigRlFile.

After we find out the required size of the sig_rl buffer, we allocate memory for it. Then we fill the buffer using EpidParseSigRlFile.


Next, we set the group based revocation list using EpidVerifierSetGroupRl.

if (signed_grp_rl) {
// authenticate and determine space needed for RL
size_t grp_rl_size = 0;
result = EpidParseGroupRlFile(signed_grp_rl, signed_grp_rl_size, cacert,
NULL, &grp_rl_size);
if (kEpidSigInvalid == result) {
// authentication failure
break;
}
if (kEpidNoErr != result) {
break;
}
grp_rl = AllocBuffer(grp_rl_size);
if (!grp_rl) {
result = kEpidMemAllocErr;
break;
}
// fill the rl
result = EpidParseGroupRlFile(signed_grp_rl, signed_grp_rl_size, cacert,
grp_rl, &grp_rl_size);
if (kEpidNoErr != result) {
break;
}
// set group revocation list
result = EpidVerifierSetGroupRl(ctx, grp_rl, grp_rl_size);
if (kEpidNoErr != result) {
break;
}
} // if (signed_grp_rl)

We use EpidParseGroupRlFile to:

  • extract the group based revocation list
  • validate that the revocation list was signed by the private key corresponding to the provided CA certificate
  • validate that the size of the input buffer is correct
  • determine the required size of the revocation list output buffer

To determine the required grp_rl buffer size, we provide a null pointer for the output buffer when calling EpidParseGroupRlFile.

After we find out the required size of the grp_rl buffer, we allocate memory for it. Then we fill the buffer using EpidParseGroupRlFile.


Next, we set the verifier blacklist using EpidVerifierSetVerifierRl.

if (ver_rl) {
// set verifier based revocation list
result = EpidVerifierSetVerifierRl(ctx, ver_rl, ver_rl_size);
if (kEpidNoErr != result) {
break;
}
}

The verifier is responsible for ensuring that the verifier revocation list is authorized. Validating it is outside the scope of this example.


Next, we use EpidVerify to verify that the Intel® EPID signature was created by a valid member of a group in good standing.

result = EpidVerify(ctx, sig, sig_len, msg, msg_len);
if (kEpidNoErr != result) {
break;
}

Finally, we clean up and exit.

} while (0);
// delete verifier
if (priv_rl) free(priv_rl);
if (sig_rl) free(sig_rl);
if (grp_rl) free(grp_rl);
return result;
}

If we made it past verification without an error, we set the return code appropriately and fall out of the do-while loop. If there was an error earlier, all breaks in the do-while loop bring us to this point with an error status.

Then we free the allocated resources. EpidVerifierDelete deletes the verifier context.

After deleting the verifier context, we can also delete the revocation lists.

We return from Verify with the success or error status.


This concludes the verifysig walkthrough. Now you should be able to verify an Intel® EPID signature using the SDK APIs.

To learn more about the SDK APIs see the API Reference. To learn more about the Intel® EPID Scheme see Introduction to the Intel® EPID Scheme in the documentation.