blob: c8258403a5f97f88581a8477aca762fa0c4a23fb [file] [log] [blame]
//========================================================================
//
// 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>
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, &notBefore, &notAfter);
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);
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;
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);
if (NSS_Shutdown()!=SECSuccess)
fprintf(stderr, "Detail: %s\n", PR_ErrorToString(PORT_GetError(), PR_LANGUAGE_I_DEFAULT));
}
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;
}