/** @file | |
RFC3161 Timestamp Countersignature Verification over OpenSSL. | |
The timestamp is generated by a TimeStamping Authority (TSA) and asserts that a | |
publisher's signature existed before the specified time. The timestamp extends | |
the lifetime of the signature when a signing certificate expires or is later | |
revoked. | |
Copyright (c) 2014 - 2017, Intel Corporation. All rights reserved.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "InternalCryptLib.h" | |
#include <openssl/asn1.h> | |
#include <openssl/asn1t.h> | |
#include <openssl/x509.h> | |
#include <openssl/x509v3.h> | |
#include <openssl/pkcs7.h> | |
// | |
// OID ASN.1 Value for SPC_RFC3161_OBJID ("1.3.6.1.4.1.311.3.3.1") | |
// | |
GLOBAL_REMOVE_IF_UNREFERENCED const UINT8 mSpcRFC3161OidValue[] = { | |
0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x03, 0x03, 0x01 | |
}; | |
/// | |
/// The messageImprint field SHOULD contain the hash of the datum to be | |
/// time-stamped. The hash is represented as an OCTET STRING. Its | |
/// length MUST match the length of the hash value for that algorithm | |
/// (e.g., 20 bytes for SHA-1 or 16 bytes for MD5). | |
/// | |
/// MessageImprint ::= SEQUENCE { | |
/// hashAlgorithm AlgorithmIdentifier, | |
/// hashedMessage OCTET STRING } | |
/// | |
typedef struct { | |
X509_ALGOR *HashAlgorithm; | |
ASN1_OCTET_STRING *HashedMessage; | |
} TS_MESSAGE_IMPRINT; | |
// | |
// ASN.1 Functions for TS_MESSAGE_IMPRINT | |
// | |
GLOBAL_REMOVE_IF_UNREFERENCED | |
DECLARE_ASN1_FUNCTIONS ( | |
TS_MESSAGE_IMPRINT | |
) | |
ASN1_SEQUENCE (TS_MESSAGE_IMPRINT) = | |
{ | |
ASN1_SIMPLE (TS_MESSAGE_IMPRINT, HashAlgorithm, X509_ALGOR), | |
ASN1_SIMPLE (TS_MESSAGE_IMPRINT, HashedMessage, ASN1_OCTET_STRING) | |
} | |
ASN1_SEQUENCE_END (TS_MESSAGE_IMPRINT) | |
IMPLEMENT_ASN1_FUNCTIONS (TS_MESSAGE_IMPRINT) | |
/// | |
/// Accuracy represents the time deviation around the UTC time contained | |
/// in GeneralizedTime of time-stamp token. | |
/// | |
/// Accuracy ::= SEQUENCE { | |
/// seconds INTEGER OPTIONAL, | |
/// millis [0] INTEGER (1..999) OPTIONAL, | |
/// micros [1] INTEGER (1..999) OPTIONAL } | |
/// | |
typedef struct { | |
ASN1_INTEGER *Seconds; | |
ASN1_INTEGER *Millis; | |
ASN1_INTEGER *Micros; | |
} TS_ACCURACY; | |
// | |
// ASN.1 Functions for TS_ACCURACY | |
// | |
GLOBAL_REMOVE_IF_UNREFERENCED | |
DECLARE_ASN1_FUNCTIONS ( | |
TS_ACCURACY | |
) | |
ASN1_SEQUENCE (TS_ACCURACY) = | |
{ | |
ASN1_OPT (TS_ACCURACY, Seconds, ASN1_INTEGER), | |
ASN1_IMP_OPT (TS_ACCURACY, Millis, ASN1_INTEGER, 0), | |
ASN1_IMP_OPT (TS_ACCURACY, Micros, ASN1_INTEGER, 1) | |
} | |
ASN1_SEQUENCE_END (TS_ACCURACY) | |
IMPLEMENT_ASN1_FUNCTIONS (TS_ACCURACY) | |
/// | |
/// The timestamp token info resulting from a successful timestamp request, | |
/// as defined in RFC 3161. | |
/// | |
/// TSTInfo ::= SEQUENCE { | |
/// version INTEGER { v1(1) }, | |
/// policy TSAPolicyId, | |
/// messageImprint MessageImprint, | |
/// -- MUST have the same value as the similar field in | |
/// -- TimeStampReq | |
/// serialNumber INTEGER, | |
/// -- Time-Stamping users MUST be ready to accommodate integers | |
/// -- up to 160 bits. | |
/// genTime GeneralizedTime, | |
/// accuracy Accuracy OPTIONAL, | |
/// ordering BOOLEAN DEFAULT FALSE, | |
/// nonce INTEGER OPTIONAL, | |
/// -- MUST be present if the similar field was present | |
/// -- in TimeStampReq. In that case it MUST have the same value. | |
/// tsa [0] GeneralName OPTIONAL, | |
/// extensions [1] IMPLICIT Extensions OPTIONAL } | |
/// | |
typedef struct { | |
ASN1_INTEGER *Version; | |
ASN1_OBJECT *Policy; | |
TS_MESSAGE_IMPRINT *MessageImprint; | |
ASN1_INTEGER *SerialNumber; | |
ASN1_GENERALIZEDTIME *GenTime; | |
TS_ACCURACY *Accuracy; | |
ASN1_BOOLEAN Ordering; | |
ASN1_INTEGER *Nonce; | |
GENERAL_NAME *Tsa; | |
STACK_OF (X509_EXTENSION) *Extensions; | |
} TS_TST_INFO; | |
// | |
// ASN.1 Functions for TS_TST_INFO | |
// | |
GLOBAL_REMOVE_IF_UNREFERENCED | |
DECLARE_ASN1_FUNCTIONS ( | |
TS_TST_INFO | |
) | |
ASN1_SEQUENCE (TS_TST_INFO) = | |
{ | |
ASN1_SIMPLE (TS_TST_INFO, Version, ASN1_INTEGER), | |
ASN1_SIMPLE (TS_TST_INFO, Policy, ASN1_OBJECT), | |
ASN1_SIMPLE (TS_TST_INFO, MessageImprint, TS_MESSAGE_IMPRINT), | |
ASN1_SIMPLE (TS_TST_INFO, SerialNumber, ASN1_INTEGER), | |
ASN1_SIMPLE (TS_TST_INFO, GenTime, ASN1_GENERALIZEDTIME), | |
ASN1_OPT (TS_TST_INFO, Accuracy, TS_ACCURACY), | |
ASN1_OPT (TS_TST_INFO, Ordering, ASN1_FBOOLEAN), | |
ASN1_OPT (TS_TST_INFO, Nonce, ASN1_INTEGER), | |
ASN1_EXP_OPT (TS_TST_INFO, Tsa, GENERAL_NAME, 0), | |
ASN1_IMP_SEQUENCE_OF_OPT (TS_TST_INFO, Extensions, X509_EXTENSION, 1) | |
} | |
ASN1_SEQUENCE_END (TS_TST_INFO) | |
IMPLEMENT_ASN1_FUNCTIONS (TS_TST_INFO) | |
/** | |
Convert ASN.1 GeneralizedTime to EFI Time. | |
@param[in] Asn1Time Pointer to the ASN.1 GeneralizedTime to be converted. | |
@param[out] SigningTime Return the corresponding EFI Time. | |
@retval TRUE The time conversion succeeds. | |
@retval FALSE Invalid parameters. | |
**/ | |
STATIC | |
BOOLEAN | |
ConvertAsn1TimeToEfiTime ( | |
IN ASN1_TIME *Asn1Time, | |
OUT EFI_TIME *EfiTime | |
) | |
{ | |
CONST CHAR8 *Str; | |
UINTN Index; | |
if ((Asn1Time == NULL) || (EfiTime == NULL)) { | |
return FALSE; | |
} | |
Str = (CONST CHAR8 *)Asn1Time->data; | |
SetMem (EfiTime, sizeof (EFI_TIME), 0); | |
Index = 0; | |
if (Asn1Time->type == V_ASN1_UTCTIME) { | |
/* two digit year */ | |
EfiTime->Year = (Str[Index++] - '0') * 10; | |
EfiTime->Year += (Str[Index++] - '0'); | |
if (EfiTime->Year < 70) { | |
EfiTime->Year += 100; | |
} | |
} else if (Asn1Time->type == V_ASN1_GENERALIZEDTIME) { | |
/* four digit year */ | |
EfiTime->Year = (Str[Index++] - '0') * 1000; | |
EfiTime->Year += (Str[Index++] - '0') * 100; | |
EfiTime->Year += (Str[Index++] - '0') * 10; | |
EfiTime->Year += (Str[Index++] - '0'); | |
if ((EfiTime->Year < 1900) || (EfiTime->Year > 9999)) { | |
return FALSE; | |
} | |
} | |
EfiTime->Month = (Str[Index++] - '0') * 10; | |
EfiTime->Month += (Str[Index++] - '0'); | |
if ((EfiTime->Month < 1) || (EfiTime->Month > 12)) { | |
return FALSE; | |
} | |
EfiTime->Day = (Str[Index++] - '0') * 10; | |
EfiTime->Day += (Str[Index++] - '0'); | |
if ((EfiTime->Day < 1) || (EfiTime->Day > 31)) { | |
return FALSE; | |
} | |
EfiTime->Hour = (Str[Index++] - '0') * 10; | |
EfiTime->Hour += (Str[Index++] - '0'); | |
if (EfiTime->Hour > 23) { | |
return FALSE; | |
} | |
EfiTime->Minute = (Str[Index++] - '0') * 10; | |
EfiTime->Minute += (Str[Index++] - '0'); | |
if (EfiTime->Minute > 59) { | |
return FALSE; | |
} | |
EfiTime->Second = (Str[Index++] - '0') * 10; | |
EfiTime->Second += (Str[Index++] - '0'); | |
if (EfiTime->Second > 59) { | |
return FALSE; | |
} | |
/* Note: we did not adjust the time based on time zone information */ | |
return TRUE; | |
} | |
/** | |
Check the validity of TimeStamp Token Information. | |
@param[in] TstInfo Pointer to the TS_TST_INFO structure. | |
@param[in] TimestampedData Pointer to the data to be time-stamped. | |
@param[in] DataSize Size of timestamped data in bytes. | |
@retval TRUE The TimeStamp Token Information is valid. | |
@retval FALSE Invalid TimeStamp Token Information. | |
**/ | |
STATIC | |
BOOLEAN | |
CheckTSTInfo ( | |
IN CONST TS_TST_INFO *TstInfo, | |
IN CONST UINT8 *TimestampedData, | |
IN UINTN DataSize | |
) | |
{ | |
BOOLEAN Status; | |
TS_MESSAGE_IMPRINT *Imprint; | |
X509_ALGOR *HashAlgo; | |
CONST EVP_MD *Md; | |
EVP_MD_CTX *MdCtx; | |
UINTN MdSize; | |
UINT8 *HashedMsg; | |
// | |
// Initialization | |
// | |
Status = FALSE; | |
HashAlgo = NULL; | |
HashedMsg = NULL; | |
MdCtx = NULL; | |
// | |
// -- Check version number of Timestamp: | |
// The version field (currently v1) describes the version of the time-stamp token. | |
// Conforming time-stamping servers MUST be able to provide version 1 time-stamp tokens. | |
// | |
if ((ASN1_INTEGER_get (TstInfo->Version)) != 1) { | |
return FALSE; | |
} | |
// | |
// -- Check Policies | |
// The policy field MUST indicate the TSA's policy under which the response was produced. | |
// | |
if (TstInfo->Policy == NULL) { | |
/// NOTE: Need to check if the requested and returned policies. | |
/// We have no information about the Requested TSA Policy. | |
return FALSE; | |
} | |
// | |
// -- Compute & Check Message Imprint | |
// | |
Imprint = TstInfo->MessageImprint; | |
HashAlgo = X509_ALGOR_dup (Imprint->HashAlgorithm); | |
Md = EVP_get_digestbyobj (HashAlgo->algorithm); | |
if (Md == NULL) { | |
goto _Exit; | |
} | |
MdSize = EVP_MD_size (Md); | |
HashedMsg = AllocateZeroPool (MdSize); | |
if (HashedMsg == NULL) { | |
goto _Exit; | |
} | |
MdCtx = EVP_MD_CTX_new (); | |
if (MdCtx == NULL) { | |
goto _Exit; | |
} | |
if ((EVP_DigestInit_ex (MdCtx, Md, NULL) != 1) || | |
(EVP_DigestUpdate (MdCtx, TimestampedData, DataSize) != 1) || | |
(EVP_DigestFinal (MdCtx, HashedMsg, NULL) != 1)) | |
{ | |
goto _Exit; | |
} | |
if ((MdSize == (UINTN)ASN1_STRING_length (Imprint->HashedMessage)) && | |
(CompareMem (HashedMsg, ASN1_STRING_get0_data (Imprint->HashedMessage), MdSize) != 0)) | |
{ | |
goto _Exit; | |
} | |
// | |
// -- Check Nonces | |
// | |
if (TstInfo->Nonce != NULL) { | |
// | |
// Nonces is optional, No error if no nonce is returned; | |
// | |
} | |
// | |
// -- Check if the TSA name and signer certificate is matched. | |
// | |
if (TstInfo->Tsa != NULL) { | |
// | |
// Ignored the optional Tsa field checking. | |
// | |
} | |
Status = TRUE; | |
_Exit: | |
X509_ALGOR_free (HashAlgo); | |
EVP_MD_CTX_free (MdCtx); | |
if (HashedMsg != NULL) { | |
FreePool (HashedMsg); | |
} | |
return Status; | |
} | |
/** | |
Verifies the validity of a TimeStamp Token as described in RFC 3161 ("Internet | |
X.509 Public Key Infrastructure Time-Stamp Protocol (TSP)"). | |
If TSToken is NULL, then return FALSE. | |
If TimestampedData is NULL, then return FALSE. | |
@param[in] TSToken Pointer to the RFC3161 TimeStamp Token, which is generated | |
by a TSA and located in the software publisher's SignerInfo | |
structure. | |
@param[in] TokenSize Size of the TimeStamp Token in bytes. | |
@param[in] TsaCert Pointer to a trusted/root TSA certificate encoded in DER. | |
@param[in] CertSize Size of the trusted TSA certificate in bytes. | |
@param[in] TimestampedData Pointer to the data to be time-stamped. | |
@param[in] DataSize Size of timestamped data in bytes. | |
@param[out] SigningTime Return the time of timestamp generation time if the timestamp | |
signature is valid. | |
@retval TRUE The specified timestamp token is valid. | |
@retval FALSE Invalid timestamp token. | |
**/ | |
STATIC | |
BOOLEAN | |
TimestampTokenVerify ( | |
IN CONST UINT8 *TSToken, | |
IN UINTN TokenSize, | |
IN CONST UINT8 *TsaCert, | |
IN UINTN CertSize, | |
IN CONST UINT8 *TimestampedData, | |
IN UINTN DataSize, | |
OUT EFI_TIME *SigningTime | |
) | |
{ | |
BOOLEAN Status; | |
CONST UINT8 *TokenTemp; | |
PKCS7 *Pkcs7; | |
X509 *Cert; | |
CONST UINT8 *CertTemp; | |
X509_STORE *CertStore; | |
BIO *OutBio; | |
UINT8 *TstData; | |
UINTN TstSize; | |
CONST UINT8 *TstTemp; | |
TS_TST_INFO *TstInfo; | |
Status = FALSE; | |
// | |
// Check input parameters | |
// | |
if ((TSToken == NULL) || (TsaCert == NULL) || (TimestampedData == NULL) || | |
(TokenSize > INT_MAX) || (CertSize > INT_MAX) || (DataSize > INT_MAX)) | |
{ | |
return FALSE; | |
} | |
// | |
// Initializations | |
// | |
if (SigningTime != NULL) { | |
SetMem (SigningTime, sizeof (EFI_TIME), 0); | |
} | |
Pkcs7 = NULL; | |
Cert = NULL; | |
CertStore = NULL; | |
OutBio = NULL; | |
TstData = NULL; | |
TstInfo = NULL; | |
// | |
// TimeStamp Token should contain one valid DER-encoded ASN.1 PKCS#7 structure. | |
// | |
TokenTemp = TSToken; | |
Pkcs7 = d2i_PKCS7 (NULL, (const unsigned char **)&TokenTemp, (int)TokenSize); | |
if (Pkcs7 == NULL) { | |
goto _Exit; | |
} | |
// | |
// The timestamp signature (TSA's response) will be one PKCS#7 signed data. | |
// | |
if (!PKCS7_type_is_signed (Pkcs7)) { | |
goto _Exit; | |
} | |
// | |
// Read the trusted TSA certificate (DER-encoded), and Construct X509 Certificate. | |
// | |
CertTemp = TsaCert; | |
Cert = d2i_X509 (NULL, &CertTemp, (long)CertSize); | |
if (Cert == NULL) { | |
goto _Exit; | |
} | |
// | |
// Setup X509 Store for trusted certificate. | |
// | |
CertStore = X509_STORE_new (); | |
if ((CertStore == NULL) || !(X509_STORE_add_cert (CertStore, Cert))) { | |
goto _Exit; | |
} | |
// | |
// Allow partial certificate chains, terminated by a non-self-signed but | |
// still trusted intermediate certificate. Also disable time checks. | |
// | |
X509_STORE_set_flags ( | |
CertStore, | |
X509_V_FLAG_PARTIAL_CHAIN | X509_V_FLAG_NO_CHECK_TIME | |
); | |
X509_STORE_set_purpose (CertStore, X509_PURPOSE_ANY); | |
// | |
// Verifies the PKCS#7 signedData structure, and output the signed contents. | |
// | |
OutBio = BIO_new (BIO_s_mem ()); | |
if (OutBio == NULL) { | |
goto _Exit; | |
} | |
if (!PKCS7_verify (Pkcs7, NULL, CertStore, NULL, OutBio, PKCS7_BINARY)) { | |
goto _Exit; | |
} | |
// | |
// Read the signed contents detached in timestamp signature. | |
// | |
TstData = AllocateZeroPool (2048); | |
if (TstData == NULL) { | |
goto _Exit; | |
} | |
TstSize = BIO_read (OutBio, (void *)TstData, 2048); | |
// | |
// Construct TS_TST_INFO structure from the signed contents. | |
// | |
TstTemp = TstData; | |
TstInfo = d2i_TS_TST_INFO ( | |
NULL, | |
(const unsigned char **)&TstTemp, | |
(int)TstSize | |
); | |
if (TstInfo == NULL) { | |
goto _Exit; | |
} | |
// | |
// Check TS_TST_INFO structure. | |
// | |
Status = CheckTSTInfo (TstInfo, TimestampedData, DataSize); | |
if (!Status) { | |
goto _Exit; | |
} | |
// | |
// Retrieve the signing time from TS_TST_INFO structure. | |
// | |
if (SigningTime != NULL) { | |
SetMem (SigningTime, sizeof (EFI_TIME), 0); | |
Status = ConvertAsn1TimeToEfiTime (TstInfo->GenTime, SigningTime); | |
} | |
_Exit: | |
// | |
// Release Resources | |
// | |
PKCS7_free (Pkcs7); | |
X509_free (Cert); | |
X509_STORE_free (CertStore); | |
BIO_free (OutBio); | |
TS_TST_INFO_free (TstInfo); | |
if (TstData != NULL) { | |
FreePool (TstData); | |
} | |
return Status; | |
} | |
/** | |
Verifies the validity of a RFC3161 Timestamp CounterSignature embedded in PE/COFF Authenticode | |
signature. | |
If AuthData is NULL, then return FALSE. | |
@param[in] AuthData Pointer to the Authenticode Signature retrieved from signed | |
PE/COFF image to be verified. | |
@param[in] DataSize Size of the Authenticode Signature in bytes. | |
@param[in] TsaCert Pointer to a trusted/root TSA certificate encoded in DER, which | |
is used for TSA certificate chain verification. | |
@param[in] CertSize Size of the trusted certificate in bytes. | |
@param[out] SigningTime Return the time of timestamp generation time if the timestamp | |
signature is valid. | |
@retval TRUE The specified Authenticode includes a valid RFC3161 Timestamp CounterSignature. | |
@retval FALSE No valid RFC3161 Timestamp CounterSignature in the specified Authenticode data. | |
**/ | |
BOOLEAN | |
EFIAPI | |
ImageTimestampVerify ( | |
IN CONST UINT8 *AuthData, | |
IN UINTN DataSize, | |
IN CONST UINT8 *TsaCert, | |
IN UINTN CertSize, | |
OUT EFI_TIME *SigningTime | |
) | |
{ | |
BOOLEAN Status; | |
PKCS7 *Pkcs7; | |
CONST UINT8 *Temp; | |
STACK_OF (PKCS7_SIGNER_INFO) *SignerInfos; | |
PKCS7_SIGNER_INFO *SignInfo; | |
UINTN Index; | |
STACK_OF (X509_ATTRIBUTE) *Sk; | |
X509_ATTRIBUTE *Xa; | |
ASN1_OBJECT *XaObj; | |
ASN1_TYPE *Asn1Type; | |
ASN1_OCTET_STRING *EncDigest; | |
UINT8 *TSToken; | |
UINTN TokenSize; | |
// | |
// Input Parameters Checking. | |
// | |
if ((AuthData == NULL) || (TsaCert == NULL)) { | |
return FALSE; | |
} | |
if ((DataSize > INT_MAX) || (CertSize > INT_MAX)) { | |
return FALSE; | |
} | |
// | |
// Register & Initialize necessary digest algorithms for PKCS#7 Handling. | |
// | |
if ((EVP_add_digest (EVP_md5 ()) == 0) || (EVP_add_digest (EVP_sha1 ()) == 0) || | |
(EVP_add_digest (EVP_sha256 ()) == 0) || (EVP_add_digest (EVP_sha384 ()) == 0) || | |
(EVP_add_digest (EVP_sha512 ()) == 0) || ((EVP_add_digest_alias (SN_sha1WithRSAEncryption, SN_sha1WithRSA)) == 0)) | |
{ | |
return FALSE; | |
} | |
// | |
// Initialization. | |
// | |
Status = FALSE; | |
Pkcs7 = NULL; | |
SignInfo = NULL; | |
// | |
// Decode ASN.1-encoded Authenticode data into PKCS7 structure. | |
// | |
Temp = AuthData; | |
Pkcs7 = d2i_PKCS7 (NULL, (const unsigned char **)&Temp, (int)DataSize); | |
if (Pkcs7 == NULL) { | |
goto _Exit; | |
} | |
// | |
// Check if there is one and only one signer. | |
// | |
SignerInfos = PKCS7_get_signer_info (Pkcs7); | |
if (!SignerInfos || (sk_PKCS7_SIGNER_INFO_num (SignerInfos) != 1)) { | |
goto _Exit; | |
} | |
// | |
// Locate the TimeStamp CounterSignature. | |
// | |
SignInfo = sk_PKCS7_SIGNER_INFO_value (SignerInfos, 0); | |
if (SignInfo == NULL) { | |
goto _Exit; | |
} | |
// | |
// Locate Message Digest which will be the data to be time-stamped. | |
// | |
EncDigest = SignInfo->enc_digest; | |
if (EncDigest == NULL) { | |
goto _Exit; | |
} | |
// | |
// The RFC3161 timestamp counterSignature is contained in unauthenticatedAttributes field | |
// of SignerInfo. | |
// | |
Sk = SignInfo->unauth_attr; | |
if (Sk == NULL) { | |
// No timestamp counterSignature. | |
goto _Exit; | |
} | |
Asn1Type = NULL; | |
for (Index = 0; Index < (UINTN)sk_X509_ATTRIBUTE_num (Sk); Index++) { | |
// | |
// Search valid RFC3161 timestamp counterSignature based on OBJID. | |
// | |
Xa = sk_X509_ATTRIBUTE_value (Sk, (int)Index); | |
if (Xa == NULL) { | |
continue; | |
} | |
XaObj = X509_ATTRIBUTE_get0_object (Xa); | |
if (XaObj == NULL) { | |
continue; | |
} | |
if ((OBJ_length (XaObj) != sizeof (mSpcRFC3161OidValue)) || | |
(CompareMem (OBJ_get0_data (XaObj), mSpcRFC3161OidValue, sizeof (mSpcRFC3161OidValue)) != 0)) | |
{ | |
continue; | |
} | |
Asn1Type = X509_ATTRIBUTE_get0_type (Xa, 0); | |
} | |
if (Asn1Type == NULL) { | |
Status = FALSE; | |
goto _Exit; | |
} | |
TSToken = Asn1Type->value.octet_string->data; | |
TokenSize = Asn1Type->value.octet_string->length; | |
// | |
// TimeStamp counterSignature (Token) verification. | |
// | |
Status = TimestampTokenVerify ( | |
TSToken, | |
TokenSize, | |
TsaCert, | |
CertSize, | |
EncDigest->data, | |
EncDigest->length, | |
SigningTime | |
); | |
_Exit: | |
// | |
// Release Resources | |
// | |
PKCS7_free (Pkcs7); | |
return Status; | |
} |