/** @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.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "InternalCryptLib.h" #include #include #include #include #include // // 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_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; }