| //======================================================================== |
| // |
| // SignatureHandler.cc |
| // |
| // This file is licensed under the GPLv2 or later |
| // |
| // Copyright 2015, 2016 André Guerreiro <aguerreiro1985@gmail.com> |
| // Copyright 2015 André Esser <bepandre@hotmail.com> |
| // Copyright 2015, 2016, 2018, 2019 Albert Astals Cid <aacid@kde.org> |
| // Copyright 2015 Markus Kilås <digital@markuspage.com> |
| // Copyright 2017 Sebastian Rasmussen <sebras@gmail.com> |
| // Copyright 2017 Hans-Ulrich Jüttner <huj@froreich-bioscientia.de> |
| // Copyright 2018 Chinmoy Ranjan Pradhan <chinmoyrp65@protonmail.com> |
| // Copyright 2018 Oliver Sander <oliver.sander@tu-dresden.de> |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include "SignatureHandler.h" |
| #include "goo/gmem.h" |
| #include <secmod.h> |
| #include <keyhi.h> |
| #include <secder.h> |
| |
| #include <dirent.h> |
| #include <Error.h> |
| |
| static void shutdownNss() |
| { |
| if (NSS_Shutdown() != SECSuccess) { |
| fprintf(stderr, "NSS_Shutdown failed: %s\n", PR_ErrorToString(PORT_GetError(), PR_LANGUAGE_I_DEFAULT)); |
| } |
| } |
| |
| unsigned int SignatureHandler::digestLength(SECOidTag digestAlgId) |
| { |
| switch(digestAlgId){ |
| case SEC_OID_SHA1: |
| return 20; |
| case SEC_OID_SHA256: |
| return 32; |
| case SEC_OID_SHA384: |
| return 48; |
| case SEC_OID_SHA512: |
| return 64; |
| default: |
| printf("ERROR: Unrecognized Hash ID\n"); |
| return 0; |
| } |
| } |
| |
| char *SignatureHandler::getSignerName() |
| { |
| if (!CMSSignerInfo || !NSS_IsInitialized()) |
| return nullptr; |
| |
| CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); |
| |
| if (!cert) |
| return nullptr; |
| |
| return CERT_GetCommonName(&cert->subject); |
| } |
| |
| const char * SignatureHandler::getSignerSubjectDN() |
| { |
| if (!CMSSignerInfo) |
| return nullptr; |
| |
| CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); |
| if (!cert) |
| return nullptr; |
| return cert->subjectName; |
| } |
| |
| HASH_HashType SignatureHandler::getHashAlgorithm() |
| { |
| if (hash_context && hash_context->hashobj) |
| { |
| return hash_context->hashobj->type; |
| } |
| return HASH_AlgNULL; |
| } |
| |
| time_t SignatureHandler::getSigningTime() |
| { |
| PRTime sTime; // time in microseconds since the epoch |
| |
| if (NSS_CMSSignerInfo_GetSigningTime (CMSSignerInfo, &sTime) != SECSuccess) |
| return 0; |
| |
| return static_cast<time_t>(sTime/1000000); |
| } |
| |
| X509CertificateInfo::EntityInfo SignatureHandler::getEntityInfo(CERTName *entityName) const |
| { |
| X509CertificateInfo::EntityInfo info; |
| |
| if (!entityName) |
| return info; |
| |
| char *dn = CERT_NameToAscii(entityName); |
| if (dn) { |
| info.distinguishedName = dn; |
| PORT_Free(dn); |
| } |
| |
| char *cn = CERT_GetCommonName(entityName); |
| if (cn) { |
| info.commonName = cn; |
| PORT_Free(cn); |
| } |
| |
| char *email = CERT_GetCertEmailAddress(entityName); |
| if (email) { |
| info.email = email; |
| PORT_Free(email); |
| } |
| |
| char *org = CERT_GetOrgName(entityName); |
| if (org) { |
| info.organization = org; |
| PORT_Free(org); |
| } |
| |
| return info; |
| } |
| |
| static GooString SECItemToGooString(const SECItem &secItem) |
| { |
| // TODO do we need to handle secItem.type; |
| return GooString((const char *)secItem.data, secItem.len); |
| } |
| |
| std::unique_ptr<X509CertificateInfo> SignatureHandler::getCertificateInfo() const |
| { |
| if (!CMSSignerInfo) |
| return nullptr; |
| |
| CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); |
| if (!cert) |
| return nullptr; |
| |
| auto certInfo = std::make_unique<X509CertificateInfo>(); |
| |
| certInfo->setVersion(DER_GetInteger(&cert->version) + 1); |
| certInfo->setSerialNumber(SECItemToGooString(cert->serialNumber)); |
| |
| //issuer info |
| certInfo->setIssuerInfo(getEntityInfo(&cert->issuer)); |
| |
| //validity |
| PRTime notBefore, notAfter; |
| CERT_GetCertTimes(cert, ¬Before, ¬After); |
| X509CertificateInfo::Validity certValidity; |
| certValidity.notBefore = static_cast<time_t>(notBefore/1000000); |
| certValidity.notAfter = static_cast<time_t>(notAfter/1000000); |
| certInfo->setValidity(certValidity); |
| |
| //subject info |
| certInfo->setSubjectInfo(getEntityInfo(&cert->subject)); |
| |
| //public key info |
| X509CertificateInfo::PublicKeyInfo pkInfo; |
| SECKEYPublicKey *pk = CERT_ExtractPublicKey(cert); |
| switch (pk->keyType) |
| { |
| case rsaKey: |
| pkInfo.publicKey = SECItemToGooString(pk->u.rsa.modulus); |
| pkInfo.publicKeyType = RSAKEY; |
| break; |
| case dsaKey: |
| pkInfo.publicKey = SECItemToGooString(pk->u.dsa.publicValue); |
| pkInfo.publicKeyType = DSAKEY; |
| break; |
| case ecKey: |
| pkInfo.publicKey = SECItemToGooString(pk->u.ec.publicValue); |
| pkInfo.publicKeyType = ECKEY; |
| break; |
| default: |
| pkInfo.publicKey = SECItemToGooString(cert->subjectPublicKeyInfo.subjectPublicKey); |
| pkInfo.publicKeyType = OTHERKEY; |
| break; |
| } |
| pkInfo.publicKeyStrength = SECKEY_PublicKeyStrengthInBits(pk); |
| certInfo->setPublicKeyInfo(std::move(pkInfo)); |
| |
| certInfo->setKeyUsageExtensions(cert->keyUsage); |
| certInfo->setCertificateDER(SECItemToGooString(cert->derCert)); |
| certInfo->setIsSelfSigned(CERT_CompareName(&cert->subject, &cert->issuer) == SECEqual); |
| |
| SECKEY_DestroyPublicKey(pk); |
| |
| return certInfo; |
| } |
| |
| static GooString *getDefaultFirefoxCertDB_Linux() |
| { |
| GooString * finalPath = nullptr; |
| DIR *toSearchIn; |
| struct dirent *subFolder; |
| |
| GooString * homePath = new GooString(getenv("HOME")); |
| homePath = homePath->append("/.mozilla/firefox/"); |
| |
| if ((toSearchIn = opendir(homePath->c_str())) == nullptr) { |
| error(errInternal, 0, "couldn't find default Firefox Folder"); |
| delete homePath; |
| return nullptr; |
| } |
| do { |
| if ((subFolder = readdir(toSearchIn)) != nullptr) { |
| if (strstr(subFolder->d_name, "default") != nullptr) { |
| finalPath = homePath->append(subFolder->d_name); |
| closedir(toSearchIn); |
| return finalPath; |
| } |
| } |
| } while (subFolder != nullptr); |
| |
| closedir(toSearchIn); |
| delete homePath; |
| return nullptr; |
| } |
| |
| /** |
| * Initialise NSS |
| */ |
| void SignatureHandler::setNSSDir(const GooString &nssDir) |
| { |
| static bool setNssDirCalled = false; |
| |
| if (NSS_IsInitialized() && nssDir.getLength() > 0) { |
| error(errInternal, 0, "You need to call setNSSDir before signature validation related operations happen"); |
| return; |
| } |
| |
| if (setNssDirCalled) |
| return; |
| |
| setNssDirCalled = true; |
| |
| atexit(shutdownNss); |
| |
| bool initSuccess = false; |
| if (nssDir.getLength() > 0) { |
| initSuccess = (NSS_Init(nssDir.c_str()) == SECSuccess); |
| } else { |
| GooString *certDBPath = getDefaultFirefoxCertDB_Linux(); |
| if (certDBPath == nullptr) { |
| initSuccess = (NSS_Init("sql:/etc/pki/nssdb") == SECSuccess); |
| } else { |
| initSuccess = (NSS_Init(certDBPath->c_str()) == SECSuccess); |
| } |
| if (!initSuccess) { |
| GooString homeNssDb(getenv("HOME")); |
| homeNssDb.append("/.pki/nssdb"); |
| initSuccess = (NSS_Init(homeNssDb.c_str()) == SECSuccess); |
| if (!initSuccess) { |
| NSS_NoDB_Init(nullptr); |
| } |
| } |
| delete certDBPath; |
| } |
| |
| if (initSuccess) { |
| //Make sure NSS root certificates module is loaded |
| SECMOD_AddNewModule("Root Certs", "libnssckbi.so", 0, 0); |
| } |
| } |
| |
| |
| SignatureHandler::SignatureHandler(unsigned char *p7, int p7_length) |
| : hash_context(nullptr), |
| CMSMessage(nullptr), |
| CMSSignedData(nullptr), |
| CMSSignerInfo(nullptr), |
| temp_certs(nullptr) |
| { |
| setNSSDir({}); |
| CMSitem.data = p7; |
| CMSitem.len = p7_length; |
| CMSMessage = CMS_MessageCreate(&CMSitem); |
| CMSSignedData = CMS_SignedDataCreate(CMSMessage); |
| if (CMSSignedData) { |
| CMSSignerInfo = CMS_SignerInfoCreate(CMSSignedData); |
| hash_context = initHashContext(); |
| } |
| } |
| |
| HASHContext * SignatureHandler::initHashContext() |
| { |
| |
| SECItem usedAlgorithm = NSS_CMSSignedData_GetDigestAlgs(CMSSignedData)[0]->algorithm; |
| hash_length = digestLength(SECOID_FindOIDTag(&usedAlgorithm)); |
| HASH_HashType hashType; |
| hashType = HASH_GetHashTypeByOidTag(SECOID_FindOIDTag(&usedAlgorithm)); |
| return HASH_Create(hashType); |
| } |
| |
| void SignatureHandler::updateHash(unsigned char * data_block, int data_len) |
| { |
| if (hash_context) { |
| HASH_Update(hash_context, data_block, data_len); |
| } |
| } |
| |
| SignatureHandler::~SignatureHandler() |
| { |
| SECITEM_FreeItem(&CMSitem, PR_FALSE); |
| if (CMSMessage) |
| NSS_CMSMessage_Destroy(CMSMessage); |
| |
| if (hash_context) |
| HASH_Destroy(hash_context); |
| |
| free(temp_certs); |
| } |
| |
| NSSCMSMessage *SignatureHandler::CMS_MessageCreate(SECItem * cms_item) |
| { |
| if (cms_item->data){ |
| return NSS_CMSMessage_CreateFromDER(cms_item, nullptr, nullptr /* Content callback */ |
| , nullptr, nullptr /*Password callback*/ |
| , nullptr, nullptr /*Decrypt callback*/); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| NSSCMSSignedData *SignatureHandler::CMS_SignedDataCreate(NSSCMSMessage * cms_msg) |
| { |
| if (!NSS_CMSMessage_IsSigned(cms_msg)) { |
| error(errInternal, 0, "Input couldn't be parsed as a CMS signature"); |
| return nullptr; |
| } |
| |
| NSSCMSContentInfo *cinfo = NSS_CMSMessage_ContentLevel(cms_msg, 0); |
| if (!cinfo) { |
| error(errInternal, 0, "Error in NSS_CMSMessage_ContentLevel"); |
| return nullptr; |
| } |
| |
| NSSCMSSignedData *signedData = (NSSCMSSignedData*) NSS_CMSContentInfo_GetContent(cinfo); |
| if (!signedData) { |
| error(errInternal, 0, "CError in NSS_CMSContentInfo_GetContent()"); |
| return nullptr; |
| } |
| |
| if (signedData->rawCerts) |
| { |
| size_t i; |
| for (i = 0; signedData->rawCerts[i]; ++i) {} // just count the length of the certificate chain |
| |
| // tempCerts field needs to be filled for complete memory release by NSSCMSSignedData_Destroy |
| signedData->tempCerts = (CERTCertificate **) gmallocn( i+1, sizeof(CERTCertificate *)); |
| memset(signedData->tempCerts, 0, (i+1) * sizeof(CERTCertificate *)); |
| // store the addresses of these temporary certificates for future release |
| for (i = 0; signedData->rawCerts[i]; ++i) |
| signedData->tempCerts[i] = CERT_NewTempCertificate(CERT_GetDefaultCertDB(), signedData->rawCerts[i], nullptr, 0, 0); |
| |
| temp_certs = signedData->tempCerts; |
| return signedData; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| NSSCMSSignerInfo *SignatureHandler::CMS_SignerInfoCreate(NSSCMSSignedData * cms_sig_data) |
| { |
| NSSCMSSignerInfo *signerInfo = NSS_CMSSignedData_GetSignerInfo(cms_sig_data, 0); |
| if (!signerInfo) { |
| printf("Error in NSS_CMSSignedData_GetSignerInfo()\n"); |
| return nullptr; |
| } else { |
| return signerInfo; |
| } |
| } |
| |
| static SignatureValidationStatus NSS_SigTranslate(NSSCMSVerificationStatus nss_code) |
| { |
| switch(nss_code) |
| { |
| case NSSCMSVS_GoodSignature: |
| return SIGNATURE_VALID; |
| |
| case NSSCMSVS_BadSignature: |
| return SIGNATURE_INVALID; |
| |
| case NSSCMSVS_DigestMismatch: |
| return SIGNATURE_DIGEST_MISMATCH; |
| |
| case NSSCMSVS_ProcessingError: |
| return SIGNATURE_DECODING_ERROR; |
| |
| default: |
| return SIGNATURE_GENERIC_ERROR; |
| } |
| } |
| |
| SignatureValidationStatus SignatureHandler::validateSignature() |
| { |
| unsigned char *digest_buffer = nullptr; |
| |
| if (!CMSSignedData) |
| return SIGNATURE_GENERIC_ERROR; |
| |
| if (!NSS_IsInitialized()) |
| return SIGNATURE_GENERIC_ERROR; |
| |
| if (!hash_context) |
| return SIGNATURE_GENERIC_ERROR; |
| |
| digest_buffer = (unsigned char *)PORT_Alloc(hash_length); |
| unsigned int result_len = 0; |
| |
| HASH_End(hash_context, digest_buffer, &result_len, hash_length); |
| |
| SECItem digest; |
| digest.data = digest_buffer; |
| digest.len = hash_length; |
| |
| if ((NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB())) == nullptr) |
| CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; |
| |
| SECItem * content_info_data = CMSSignedData->contentInfo.content.data; |
| if (content_info_data != nullptr && content_info_data->data != nullptr) |
| { |
| /* |
| This means it's not a detached type signature |
| so the digest is contained in SignedData->contentInfo |
| */ |
| if (memcmp(digest.data, content_info_data->data, hash_length) == 0 |
| && digest.len == content_info_data->len) |
| { |
| PORT_Free(digest_buffer); |
| return SIGNATURE_VALID; |
| } |
| else |
| { |
| PORT_Free(digest_buffer); |
| return SIGNATURE_DIGEST_MISMATCH; |
| } |
| |
| } |
| else if (NSS_CMSSignerInfo_Verify(CMSSignerInfo, &digest, nullptr) != SECSuccess) |
| { |
| |
| PORT_Free(digest_buffer); |
| return NSS_SigTranslate(CMSSignerInfo->verificationStatus); |
| } |
| else |
| { |
| PORT_Free(digest_buffer); |
| return SIGNATURE_VALID; |
| } |
| } |
| |
| CertificateValidationStatus SignatureHandler::validateCertificate(time_t validation_time) |
| { |
| CERTCertificate *cert; |
| |
| if (!CMSSignerInfo) |
| return CERTIFICATE_GENERIC_ERROR; |
| |
| if ((cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB())) == nullptr) |
| CMSSignerInfo->verificationStatus = NSSCMSVS_SigningCertNotFound; |
| |
| PRTime vTime = 0; // time in microseconds since the epoch, special value 0 means now |
| if (validation_time > 0) |
| vTime = 1000000*(PRTime)validation_time; |
| CERTValInParam inParams[3]; |
| inParams[0].type = cert_pi_revocationFlags; |
| inParams[0].value.pointer.revocation = CERT_GetClassicOCSPEnabledSoftFailurePolicy(); |
| inParams[1].type = cert_pi_date; |
| inParams[1].value.scalar.time = vTime; |
| inParams[2].type = cert_pi_end; |
| |
| CERT_PKIXVerifyCert(cert, certificateUsageEmailSigner, inParams, nullptr, |
| CMSSignerInfo->cmsg->pwfn_arg); |
| |
| switch(PORT_GetError()) |
| { |
| // 0 not defined in SECErrorCodes, it means success for this purpose. |
| case 0: |
| return CERTIFICATE_TRUSTED; |
| |
| case SEC_ERROR_UNKNOWN_ISSUER: |
| return CERTIFICATE_UNKNOWN_ISSUER; |
| |
| case SEC_ERROR_UNTRUSTED_ISSUER: |
| return CERTIFICATE_UNTRUSTED_ISSUER; |
| |
| case SEC_ERROR_REVOKED_CERTIFICATE: |
| return CERTIFICATE_REVOKED; |
| |
| case SEC_ERROR_EXPIRED_CERTIFICATE: |
| return CERTIFICATE_EXPIRED; |
| } |
| |
| return CERTIFICATE_GENERIC_ERROR; |
| } |