Generating an Intel® EPID Signature

This walkthrough of the signmsg example shows you how to use SDK APIs to generate an Intel® EPID signature. Signmsg is built during the SDK build.


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

#include <stdlib.h>
#include <string.h>
#include "src/signmsg.h"
#include "src/prng.h"
#include "util/envutil.h"
#include "util/stdtypes.h"
#include "util/buffutil.h"

The prng.h header provides access to a pseudo-random number generator needed for signing, while the utility headers are used by signmsg for logging and buffer management. The epid/member/api.h header provides access to the core member APIs. 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 SignMsg. 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.


The core signing functionality is contained in SignMsg.

EpidStatus SignMsg(void const* msg, size_t msg_len, void const* basename,
size_t basename_len, unsigned char const* signed_sig_rl,
size_t signed_sig_rl_size,
unsigned char const* signed_pubkey,
size_t signed_pubkey_size, unsigned char const* priv_key_ptr,
size_t privkey_size, HashAlg hash_alg,
MemberPrecomp* member_precomp, bool member_precomp_is_input,
EpidSignature** sig, size_t* sig_len,
EpidCaCertificate const* cacert) {

The SignMsg parameters are either received by the member, or they are part of the member's configuration. The exceptions are the sig and sig_len parameters, which are used to output the signature.

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 member_precomp and member_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 knows the group public key and the member private key.

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


Next we do basic variable setup and argument checking.

void* prng = NULL;
MemberCtx* member = NULL;
SigRl* sig_rl = NULL;
do {
GroupPubKey pub_key = {0};
PrivKey priv_key = {0};
size_t sig_rl_size = 0;
if (!sig) {
break;
}

We create pointers to resources to be allocated and use the do {} while(0) idiom so that we can reliably free resources on return from SignMsg.

We create variables on the stack to hold the group public key and member private key.

Finally we check to make sure that sig is a vaild pointer.


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

sts = EpidParseGroupPubKeyFile(signed_pubkey, signed_pubkey_size, cacert,
&pub_key);
if (kEpidNoErr != sts) {
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, extracting the key in the process.


We authenticate and extract the signed SigRL using EpidParseSigRlFile.

if (signed_sig_rl) {
// authenticate and determine space needed for SigRl
sts = EpidParseSigRlFile(signed_sig_rl, signed_sig_rl_size, cacert, NULL,
&sig_rl_size);
if (kEpidSigInvalid == sts) {
// authentication failure
break;
}
if (kEpidNoErr != sts) {
break;
}
sig_rl = AllocBuffer(sig_rl_size);
if (!sig_rl) {
break;
}
// fill the SigRl
sts = EpidParseSigRlFile(signed_sig_rl, signed_sig_rl_size, cacert,
sig_rl, &sig_rl_size);
if (kEpidSigInvalid == sts) {
// authentication failure
break;
}
if (kEpidNoErr != sts) {
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 output buffer size, we provide a null pointer for the output buffer when calling EpidParseSigRlFile. This updates sig_rl_size with the required size of the output buffer.

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


Next, we fill the member private key.

if (privkey_size == sizeof(PrivKey)) {
priv_key = *(PrivKey*)priv_key_ptr;
} else if (privkey_size == sizeof(CompressedPrivKey)) {
sts = EpidDecompressPrivKey(&pub_key, (CompressedPrivKey*)priv_key_ptr,
&priv_key);
if (kEpidNoErr != sts) {
break;
}
} else {
sts = kEpidErr;
break;
} // if (privkey_size == sizeof(PrivKey))

If the member private key is compressed, then we decompress it using EpidDecompressPrivKey before it can be passed to the member APIs. To determine if the member private key is compressed, we check if it is the known size of a compressed key.

If the key size is not the size of a known format, we return an error.


Next, we create a pseudo-random number generator.

sts = PrngCreate(&prng);
if (kEpidNoErr != sts) {
break;
}

Warning
This pseudo-random number generator is included only for demonstration, and should not be used in production code as a source of secure random data. For security, prng should be a cryptographically secure random number generator.


Now that the inputs have been prepared, we create a member context using EpidMemberCreate.

sts = EpidMemberCreate(&pub_key, &priv_key,
member_precomp_is_input ? member_precomp : NULL,
PrngGen, prng, &member);
if (kEpidNoErr != sts) {
break;
}

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


We serialize pre-computed member data using EpidMemberWritePrecomp.

sts = EpidMemberWritePrecomp(member, member_precomp);
if (kEpidNoErr != sts) {
break;
}

The serialized member pre-computation blob can be used to greatly increase performance of EpidMemberCreate in future sessions if the same member private key is used.


Next, if a basename is specified, we register it with EpidRegisterBaseName so that the member can use it.

if (0 != basename_len) {
sts = EpidRegisterBaseName(member, basename, basename_len);
if (kEpidNoErr != sts) {
break;
}
}

In a typical use case, to prevent loss of privacy, the member keeps a list of basenames that correspond to authorized verifiers. The member signs a message with a basename only if the basename is in the member's basename list.

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.


Then we set the hash algorithm to be used by the member using EpidMemberSetHashAlg.

sts = EpidMemberSetHashAlg(member, hash_alg);
if (kEpidNoErr != sts) {
break;
}

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


Next, we sign the message, generating an Intel® EPID signature.

*sig_len = EpidGetSigSize(sig_rl);
*sig = AllocBuffer(*sig_len);
if (!*sig) {
break;
}
// sign message
sts = EpidSign(member, msg, msg_len, basename, basename_len, sig_rl,
sig_rl_size, *sig, *sig_len);
if (kEpidNoErr != sts) {
break;
}

To create a signature, first we find out the required size of the signature using EpidGetSigSize. Then we allocate a buffer for the signature and fill the buffer using EpidSign.

It is important to compute signature size after loading sig_rl because the signature size varies with the size of the SigRL.


Finally, we clean up and exit.

sts = kEpidNoErr;
} while (0);
PrngDelete(&prng);
EpidMemberDelete(&member);
if (sig_rl) free(sig_rl);
return sts;
}

If we made it past signing 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.

Next, we free the allocated resources. EpidMemberDelete deletes an existing member context.

We return from SignMsg with the success or error status.


This concludes the signmsg walkthrough. Now you should be able to generate an Intel® EPID signature that proves a member's group membership to a verifier without revealing the member's identity.

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.