| //======================================================================== |
| // |
| // 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, 2021-2023 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> |
| // Copyright 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de> |
| // Copyright 2020 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden |
| // Copyright 2021 Theofilos Intzoglou <int.teo@gmail.com> |
| // Copyright 2021 Marek Kasik <mkasik@redhat.com> |
| // Copyright 2022 Erich E. Hoover <erich.e.hoover@gmail.com> |
| // Copyright 2023 Tobias Deiminger <tobias.deiminger@posteo.de> |
| // Copyright 2023, 2024 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk> |
| // Copyright 2023 Ingo Klöcker <kloecker@kde.org> |
| // |
| //======================================================================== |
| |
| #include <config.h> |
| |
| #include "NSSCryptoSignBackend.h" |
| #include "goo/gdir.h" |
| #include "goo/gmem.h" |
| |
| #include <optional> |
| #include <vector> |
| |
| #include <Error.h> |
| |
| /* NSS headers */ |
| #include <secmod.h> |
| #include <secoid.h> |
| #include <keyhi.h> |
| #include <secder.h> |
| #include <pk11pub.h> |
| #include <secpkcs7.h> |
| |
| #include <cert.h> |
| #include <hasht.h> |
| #include <secerr.h> |
| #include <sechash.h> |
| #include <cms.h> |
| #include <cmst.h> |
| |
| /** |
| * General name, defined by RFC 3280. |
| */ |
| struct GeneralName |
| { |
| CERTName name; |
| }; |
| |
| /** |
| * List of general names (only one for now), defined by RFC 3280. |
| */ |
| struct GeneralNames |
| { |
| GeneralName names; |
| }; |
| |
| /** |
| * Supplies different fields to identify a certificate, defined by RFC 5035. |
| */ |
| struct IssuerSerial |
| { |
| GeneralNames issuer; |
| SECItem serialNumber; |
| }; |
| |
| /** |
| * Supplies different fields that are used to identify certificates, defined by |
| * RFC 5035. |
| */ |
| struct ESSCertIDv2 |
| { |
| SECAlgorithmID hashAlgorithm; |
| SECItem certHash; |
| IssuerSerial issuerSerial; |
| }; |
| |
| /** |
| * This attribute uses the ESSCertIDv2 structure, defined by RFC 5035. |
| */ |
| struct SigningCertificateV2 |
| { |
| ESSCertIDv2 **certs; |
| |
| SigningCertificateV2() : certs(nullptr) { } |
| }; |
| |
| /** |
| * GeneralName ::= CHOICE { |
| * otherName [0] OtherName, |
| * rfc822Name [1] IA5String, |
| * dNSName [2] IA5String, |
| * x400Address [3] ORAddress, |
| * directoryName [4] Name, |
| * ediPartyName [5] EDIPartyName, |
| * uniformResourceIdentifier [6] IA5String, |
| * iPAddress [7] OCTET STRING, |
| * registeredID [8] OBJECT IDENTIFIER |
| * } |
| */ |
| const SEC_ASN1Template GeneralNameTemplate[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralName) }, { SEC_ASN1_INLINE, offsetof(GeneralName, name), SEC_ASN1_GET(CERT_NameTemplate), 0 }, { 0, 0, nullptr, 0 } }; |
| |
| /** |
| * GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName |
| */ |
| const SEC_ASN1Template GeneralNamesTemplate[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(GeneralNames) }, { SEC_ASN1_INLINE | SEC_ASN1_CONTEXT_SPECIFIC | 4, offsetof(GeneralNames, names), GeneralNameTemplate, 0 }, { 0, 0, nullptr, 0 } }; |
| |
| /** |
| * IssuerSerial ::= SEQUENCE { |
| * issuer GeneralNames, |
| * serialNumber CertificateSerialNumber |
| * } |
| */ |
| const SEC_ASN1Template IssuerSerialTemplate[] = { |
| { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(IssuerSerial) }, { SEC_ASN1_INLINE, offsetof(IssuerSerial, issuer), GeneralNamesTemplate, 0 }, { SEC_ASN1_INTEGER, offsetof(IssuerSerial, serialNumber), nullptr, 0 }, { 0, 0, nullptr, 0 } |
| }; |
| |
| /** |
| * Hash ::= OCTET STRING |
| * |
| * ESSCertIDv2 ::= SEQUENCE { |
| * hashAlgorithm AlgorithmIdentifier DEFAULT {algorithm id-sha256}, |
| * certHash Hash, |
| * issuerSerial IssuerSerial OPTIONAL |
| * } |
| */ |
| |
| const SEC_ASN1Template ESSCertIDv2Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(ESSCertIDv2) }, |
| { SEC_ASN1_INLINE, offsetof(ESSCertIDv2, hashAlgorithm), SEC_ASN1_GET(SECOID_AlgorithmIDTemplate), 0 }, |
| { SEC_ASN1_OCTET_STRING, offsetof(ESSCertIDv2, certHash), nullptr, 0 }, |
| { SEC_ASN1_INLINE, offsetof(ESSCertIDv2, issuerSerial), IssuerSerialTemplate, 0 }, |
| { 0, 0, nullptr, 0 } }; |
| |
| /** |
| * SigningCertificateV2 ::= SEQUENCE { |
| * } |
| */ |
| const SEC_ASN1Template SigningCertificateV2Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(SigningCertificateV2) }, { SEC_ASN1_SEQUENCE_OF, offsetof(SigningCertificateV2, certs), ESSCertIDv2Template, 0 }, { 0, 0, nullptr, 0 } }; |
| |
| /* |
| struct PKIStatusInfo |
| { |
| SECItem status; |
| SECItem statusString; |
| SECItem failInfo; |
| }; |
| |
| const SEC_ASN1Template PKIStatusInfo_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(PKIStatusInfo) }, |
| { SEC_ASN1_INTEGER, offsetof(PKIStatusInfo, status), nullptr, 0 }, |
| { SEC_ASN1_CONSTRUCTED | SEC_ASN1_SEQUENCE | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, statusString), nullptr, 0 }, |
| { SEC_ASN1_BIT_STRING | SEC_ASN1_OPTIONAL, offsetof(PKIStatusInfo, failInfo), nullptr, 0 }, |
| { 0, 0, nullptr, 0 } }; |
| |
| const SEC_ASN1Template Any_Template[] = { { SEC_ASN1_ANY, 0, nullptr, sizeof(SECItem) } }; |
| |
| struct TimeStampResp |
| { |
| PKIStatusInfo status; |
| SECItem timeStampToken; |
| }; |
| |
| const SEC_ASN1Template TimeStampResp_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampResp) }, |
| { SEC_ASN1_INLINE, offsetof(TimeStampResp, status), PKIStatusInfo_Template, 0 }, |
| { SEC_ASN1_ANY | SEC_ASN1_OPTIONAL, offsetof(TimeStampResp, timeStampToken), Any_Template, 0 }, |
| { 0, 0, nullptr, 0 } }; |
| |
| const SEC_ASN1Template MessageImprint_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(MessageImprint) }, |
| { SEC_ASN1_INLINE, offsetof(MessageImprint, hashAlgorithm), SECOID_AlgorithmIDTemplate, 0 }, |
| { SEC_ASN1_OCTET_STRING, offsetof(MessageImprint, hashedMessage), nullptr, 0 }, |
| { 0, 0, nullptr, 0 } }; |
| |
| const SEC_ASN1Template Extension_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(Extension) }, |
| { SEC_ASN1_OBJECT_ID, offsetof(Extension, extnID), nullptr, 0 }, |
| { SEC_ASN1_BOOLEAN, offsetof(Extension, critical), nullptr, 0 }, |
| { SEC_ASN1_OCTET_STRING, offsetof(Extension, extnValue), nullptr, 0 }, |
| { 0, 0, nullptr, 0 } }; |
| |
| const SEC_ASN1Template Extensions_Template[] = { { SEC_ASN1_SEQUENCE_OF, 0, Extension_Template, 0 } }; |
| |
| const SEC_ASN1Template TimeStampReq_Template[] = { { SEC_ASN1_SEQUENCE, 0, nullptr, sizeof(TimeStampReq) }, |
| { SEC_ASN1_INTEGER, offsetof(TimeStampReq, version), nullptr, 0 }, |
| { SEC_ASN1_INLINE, offsetof(TimeStampReq, messageImprint), MessageImprint_Template, 0 }, |
| { SEC_ASN1_OBJECT_ID | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, reqPolicy), nullptr, 0 }, |
| { SEC_ASN1_INTEGER | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, nonce), nullptr, 0 }, |
| { SEC_ASN1_BOOLEAN | SEC_ASN1_OPTIONAL, offsetof(TimeStampReq, certReq), nullptr, 0 }, |
| { SEC_ASN1_OPTIONAL | SEC_ASN1_CONTEXT_SPECIFIC | 0, offsetof(TimeStampReq, extensions), Extensions_Template, 0 }, |
| { 0, 0, nullptr, 0 } }; |
| */ |
| |
| static NSSCMSMessage *CMS_MessageCreate(SECItem *cms_item); |
| static NSSCMSSignedData *CMS_SignedDataCreate(NSSCMSMessage *cms_msg); |
| static NSSCMSSignerInfo *CMS_SignerInfoCreate(NSSCMSSignedData *cms_sig_data); |
| |
| // a dummy, actually |
| static char *passwordCallback(PK11SlotInfo * /*slot*/, PRBool /*retry*/, void *arg) |
| { |
| return PL_strdup(static_cast<char *>(arg)); |
| } |
| |
| static void shutdownNss() |
| { |
| if (NSS_Shutdown() != SECSuccess) { |
| fprintf(stderr, "NSS_Shutdown failed: %s\n", PR_ErrorToString(PORT_GetError(), PR_LANGUAGE_I_DEFAULT)); |
| } |
| } |
| |
| // SEC_StringToOID() and NSS_CMSSignerInfo_AddUnauthAttr() are |
| // not exported from libsmime, so copy them here. Sigh. |
| |
| static SECStatus my_SEC_StringToOID(PLArenaPool *arena, SECItem *to, const char *from, PRUint32 len) |
| { |
| PRUint32 decimal_numbers = 0; |
| PRUint32 result_bytes = 0; |
| SECStatus rv; |
| PRUint8 result[1024]; |
| |
| static const PRUint32 max_decimal = 0xffffffff / 10; |
| static const char OIDstring[] = { "OID." }; |
| |
| if (!from || !to) { |
| PORT_SetError(SEC_ERROR_INVALID_ARGS); |
| return SECFailure; |
| } |
| if (!len) { |
| len = PL_strlen(from); |
| } |
| if (len >= 4 && !PL_strncasecmp(from, OIDstring, 4)) { |
| from += 4; /* skip leading "OID." if present */ |
| len -= 4; |
| } |
| if (!len) { |
| bad_data: |
| PORT_SetError(SEC_ERROR_BAD_DATA); |
| return SECFailure; |
| } |
| do { |
| PRUint32 decimal = 0; |
| while (len > 0 && (*from >= '0' && *from <= '9')) { |
| PRUint32 addend = *from++ - '0'; |
| --len; |
| if (decimal > max_decimal) { /* overflow */ |
| goto bad_data; |
| } |
| decimal = (decimal * 10) + addend; |
| if (decimal < addend) { /* overflow */ |
| goto bad_data; |
| } |
| } |
| if (len != 0 && *from != '.') { |
| goto bad_data; |
| } |
| if (decimal_numbers == 0) { |
| if (decimal > 2) { |
| goto bad_data; |
| } |
| result[0] = decimal * 40; |
| result_bytes = 1; |
| } else if (decimal_numbers == 1) { |
| if (decimal > 40) { |
| goto bad_data; |
| } |
| result[0] += decimal; |
| } else { |
| /* encode the decimal number, */ |
| PRUint8 *rp; |
| PRUint32 num_bytes = 0; |
| PRUint32 tmp = decimal; |
| while (tmp) { |
| num_bytes++; |
| tmp >>= 7; |
| } |
| if (!num_bytes) { |
| ++num_bytes; /* use one byte for a zero value */ |
| } |
| if (num_bytes + result_bytes > sizeof result) { |
| goto bad_data; |
| } |
| tmp = num_bytes; |
| rp = result + result_bytes - 1; |
| rp[tmp] = static_cast<PRUint8>(decimal & 0x7f); |
| decimal >>= 7; |
| while (--tmp > 0) { |
| rp[tmp] = static_cast<PRUint8>(decimal | 0x80); |
| decimal >>= 7; |
| } |
| result_bytes += num_bytes; |
| } |
| ++decimal_numbers; |
| if (len > 0) { /* skip trailing '.' */ |
| ++from; |
| --len; |
| } |
| } while (len > 0); |
| /* now result contains result_bytes of data */ |
| if (to->data && to->len >= result_bytes) { |
| to->len = result_bytes; |
| PORT_Memcpy(to->data, result, to->len); |
| rv = SECSuccess; |
| } else { |
| SECItem result_item = { siBuffer, nullptr, 0 }; |
| result_item.data = result; |
| result_item.len = result_bytes; |
| rv = SECITEM_CopyItem(arena, to, &result_item); |
| } |
| return rv; |
| } |
| |
| static NSSCMSAttribute *my_NSS_CMSAttributeArray_FindAttrByOidTag(NSSCMSAttribute **attrs, SECOidTag oidtag, PRBool only) |
| { |
| SECOidData *oid; |
| NSSCMSAttribute *attr1, *attr2; |
| |
| if (attrs == nullptr) { |
| return nullptr; |
| } |
| |
| oid = SECOID_FindOIDByTag(oidtag); |
| if (oid == nullptr) { |
| return nullptr; |
| } |
| |
| while ((attr1 = *attrs++) != nullptr) { |
| if (attr1->type.len == oid->oid.len && PORT_Memcmp(attr1->type.data, oid->oid.data, oid->oid.len) == 0) { |
| break; |
| } |
| } |
| |
| if (attr1 == nullptr) { |
| return nullptr; |
| } |
| |
| if (!only) { |
| return attr1; |
| } |
| |
| while ((attr2 = *attrs++) != nullptr) { |
| if (attr2->type.len == oid->oid.len && PORT_Memcmp(attr2->type.data, oid->oid.data, oid->oid.len) == 0) { |
| break; |
| } |
| } |
| |
| if (attr2 != nullptr) { |
| return nullptr; |
| } |
| |
| return attr1; |
| } |
| |
| static SECStatus my_NSS_CMSArray_Add(PLArenaPool *poolp, void ***array, void *obj) |
| { |
| int n = 0; |
| void **dest; |
| |
| PORT_Assert(array != NULL); |
| if (array == nullptr) { |
| return SECFailure; |
| } |
| |
| if (*array == nullptr) { |
| dest = static_cast<void **>(PORT_ArenaAlloc(poolp, 2 * sizeof(void *))); |
| } else { |
| void **p = *array; |
| while (*p++) { |
| n++; |
| } |
| dest = static_cast<void **>(PORT_ArenaGrow(poolp, *array, (n + 1) * sizeof(void *), (n + 2) * sizeof(void *))); |
| } |
| |
| if (dest == nullptr) { |
| return SECFailure; |
| } |
| |
| dest[n] = obj; |
| dest[n + 1] = nullptr; |
| *array = dest; |
| return SECSuccess; |
| } |
| |
| static SECOidTag my_NSS_CMSAttribute_GetType(NSSCMSAttribute *attr) |
| { |
| SECOidData *typetag; |
| |
| typetag = SECOID_FindOID(&(attr->type)); |
| if (typetag == nullptr) { |
| return SEC_OID_UNKNOWN; |
| } |
| |
| return typetag->offset; |
| } |
| |
| static SECStatus my_NSS_CMSAttributeArray_AddAttr(PLArenaPool *poolp, NSSCMSAttribute ***attrs, NSSCMSAttribute *attr) |
| { |
| NSSCMSAttribute *oattr; |
| void *mark; |
| SECOidTag type; |
| |
| mark = PORT_ArenaMark(poolp); |
| |
| /* find oidtag of attr */ |
| type = my_NSS_CMSAttribute_GetType(attr); |
| |
| /* see if we have one already */ |
| oattr = my_NSS_CMSAttributeArray_FindAttrByOidTag(*attrs, type, PR_FALSE); |
| PORT_Assert(oattr == NULL); |
| if (oattr != nullptr) { |
| goto loser; /* XXX or would it be better to replace it? */ |
| } |
| |
| /* no, shove it in */ |
| if (my_NSS_CMSArray_Add(poolp, reinterpret_cast<void ***>(attrs), static_cast<void *>(attr)) != SECSuccess) { |
| goto loser; |
| } |
| |
| PORT_ArenaUnmark(poolp, mark); |
| return SECSuccess; |
| |
| loser: |
| PORT_ArenaRelease(poolp, mark); |
| return SECFailure; |
| } |
| |
| static SECStatus my_NSS_CMSSignerInfo_AddAuthAttr(NSSCMSSignerInfo *signerinfo, NSSCMSAttribute *attr) |
| { |
| return my_NSS_CMSAttributeArray_AddAttr(signerinfo->cmsg->poolp, &(signerinfo->authAttr), attr); |
| } |
| |
| static SECOidTag ConvertHashAlgorithmToNss(HashAlgorithm digestAlgId) |
| { |
| switch (digestAlgId) { |
| case HashAlgorithm::Md2: |
| return SEC_OID_MD2; |
| case HashAlgorithm::Md5: |
| return SEC_OID_MD5; |
| case HashAlgorithm::Sha1: |
| return SEC_OID_SHA1; |
| case HashAlgorithm::Sha256: |
| return SEC_OID_SHA256; |
| case HashAlgorithm::Sha384: |
| return SEC_OID_SHA384; |
| case HashAlgorithm::Sha512: |
| return SEC_OID_SHA512; |
| case HashAlgorithm::Sha224: |
| return SEC_OID_SHA224; |
| case HashAlgorithm::Unknown: |
| return SEC_OID_UNKNOWN; |
| } |
| return SEC_OID_UNKNOWN; |
| } |
| |
| static HashAlgorithm ConvertHashTypeFromNss(HASH_HashType type) |
| { |
| switch (type) { |
| case HASH_AlgMD2: |
| return HashAlgorithm::Md2; |
| case HASH_AlgMD5: |
| return HashAlgorithm::Md5; |
| case HASH_AlgSHA1: |
| return HashAlgorithm::Sha1; |
| case HASH_AlgSHA256: |
| return HashAlgorithm::Sha256; |
| case HASH_AlgSHA384: |
| return HashAlgorithm::Sha384; |
| case HASH_AlgSHA512: |
| return HashAlgorithm::Sha512; |
| case HASH_AlgSHA224: |
| return HashAlgorithm::Sha224; |
| #if NSS_VMAJOR >= 3 && NSS_VMINOR >= 91 |
| // TODO Expose this in HashAlgorithm if PDF supports them |
| case HASH_AlgSHA3_224: |
| case HASH_AlgSHA3_256: |
| case HASH_AlgSHA3_384: |
| case HASH_AlgSHA3_512: |
| #endif |
| case HASH_AlgNULL: |
| case HASH_AlgTOTAL: |
| return HashAlgorithm::Unknown; |
| } |
| return HashAlgorithm::Unknown; |
| } |
| |
| static unsigned int digestLength(HashAlgorithm digestAlgId) |
| { |
| switch (digestAlgId) { |
| case HashAlgorithm::Sha1: |
| return 20; |
| case HashAlgorithm::Sha256: |
| return 32; |
| case HashAlgorithm::Sha384: |
| return 48; |
| case HashAlgorithm::Sha512: |
| return 64; |
| default: |
| printf("ERROR: Unrecognized Hash ID\n"); |
| return 0; |
| } |
| } |
| |
| std::string NSSSignatureVerification::getSignerName() const |
| { |
| if (!NSS_IsInitialized()) { |
| return {}; |
| } |
| if (!CMSSignerInfo) { |
| return {}; |
| } |
| |
| auto signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); |
| if (!signing_cert) { |
| return {}; |
| } |
| |
| char *commonName = CERT_GetCommonName(&signing_cert->subject); |
| if (!commonName) { |
| return {}; |
| } |
| std::string name(commonName); |
| PORT_Free(commonName); |
| |
| return name; |
| } |
| |
| std::string NSSSignatureVerification::getSignerSubjectDN() const |
| { |
| if (!CMSSignerInfo) { |
| return {}; |
| } |
| auto signing_cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); |
| if (!signing_cert) { |
| return {}; |
| } |
| return std::string { signing_cert->subjectName }; |
| } |
| |
| std::chrono::system_clock::time_point NSSSignatureVerification::getSigningTime() const |
| { |
| if (!CMSSignerInfo) { |
| return {}; |
| } |
| PRTime sTime; // time in microseconds since the epoch |
| |
| if (NSS_CMSSignerInfo_GetSigningTime(CMSSignerInfo, &sTime) != SECSuccess) { |
| return {}; |
| } |
| |
| return std::chrono::system_clock::from_time_t(static_cast<time_t>(sTime / 1000000)); |
| } |
| |
| static X509CertificateInfo::EntityInfo getEntityInfo(CERTName *entityName) |
| { |
| 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); |
| } |
| |
| static std::unique_ptr<X509CertificateInfo> getCertificateInfoFromCERT(CERTCertificate *cert) |
| { |
| 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)); |
| |
| // nickname (as a handle to refer to the CERT later) |
| certInfo->setNickName(GooString(cert->dbnickname)); |
| |
| // public key info |
| X509CertificateInfo::PublicKeyInfo pkInfo; |
| SECKEYPublicKey *pk = CERT_ExtractPublicKey(cert); |
| if (pk) { |
| 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); |
| SECKEY_DestroyPublicKey(pk); |
| } else { |
| pkInfo.publicKey = SECItemToGooString(cert->subjectPublicKeyInfo.subjectPublicKey); |
| pkInfo.publicKeyType = OTHERKEY; |
| } |
| 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; |
| } |
| |
| std::unique_ptr<X509CertificateInfo> NSSSignatureVerification::getCertificateInfo() const |
| { |
| if (!CMSSignerInfo) { |
| return nullptr; |
| } |
| CERTCertificate *cert = NSS_CMSSignerInfo_GetSigningCertificate(CMSSignerInfo, CERT_GetDefaultCertDB()); |
| if (!cert) { |
| return nullptr; |
| } |
| return getCertificateInfoFromCERT(cert); |
| } |
| |
| std::unique_ptr<X509CertificateInfo> NSSSignatureCreation::getCertificateInfo() const |
| { |
| if (!signing_cert) { |
| return nullptr; |
| } |
| return getCertificateInfoFromCERT(signing_cert); |
| } |
| |
| static std::optional<std::string> getDefaultFirefoxCertDB() |
| { |
| #ifdef _WIN32 |
| const char *env = getenv("APPDATA"); |
| if (!env) { |
| return {}; |
| } |
| const std::string firefoxPath = std::string(env) + "/Mozilla/Firefox/Profiles/"; |
| #else |
| const char *env = getenv("HOME"); |
| if (!env) { |
| return {}; |
| } |
| const std::string firefoxPath = std::string(env) + "/.mozilla/firefox/"; |
| #endif |
| |
| GDir firefoxDir(firefoxPath.c_str()); |
| std::unique_ptr<GDirEntry> entry; |
| while (entry = firefoxDir.getNextEntry(), entry != nullptr) { |
| if (entry->isDir() && entry->getName()->toStr().find("default") != std::string::npos) { |
| return entry->getFullPath()->toStr(); |
| } |
| } |
| return {}; |
| } |
| |
| std::string NSSSignatureConfiguration::sNssDir; |
| |
| /** |
| * Initialise NSS |
| */ |
| void NSSSignatureConfiguration::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); |
| sNssDir = nssDir.toStr(); |
| } else { |
| const std::optional<std::string> certDBPath = getDefaultFirefoxCertDB(); |
| if (!certDBPath) { |
| initSuccess = (NSS_Init("sql:/etc/pki/nssdb") == SECSuccess); |
| sNssDir = "sql:/etc/pki/nssdb"; |
| } else { |
| initSuccess = (NSS_Init(certDBPath->c_str()) == SECSuccess); |
| sNssDir = *certDBPath; |
| } |
| if (!initSuccess) { |
| GooString homeNssDb(getenv("HOME")); |
| homeNssDb.append("/.pki/nssdb"); |
| initSuccess = (NSS_Init(homeNssDb.c_str()) == SECSuccess); |
| sNssDir = homeNssDb.toStr(); |
| } |
| } |
| |
| if (initSuccess) { |
| // Make sure NSS root certificates module is loaded |
| SECMOD_AddNewModule("Root Certs", "libnssckbi.so", 0, 0); |
| } else { |
| fprintf(stderr, "NSS_Init failed: %s\n", PR_ErrorToString(PORT_GetError(), PR_LANGUAGE_I_DEFAULT)); |
| NSS_NoDB_Init(nullptr); |
| } |
| } |
| |
| std::string NSSSignatureConfiguration::getNSSDir() |
| { |
| return sNssDir; |
| } |
| |
| static std::function<char *(const char *)> PasswordFunction; |
| |
| void NSSSignatureConfiguration::setNSSPasswordCallback(const std::function<char *(const char *)> &f) |
| { |
| PasswordFunction = f; |
| } |
| |
| NSSSignatureVerification::NSSSignatureVerification(std::vector<unsigned char> &&p7data) : p7(std::move(p7data)), CMSMessage(nullptr), CMSSignedData(nullptr), CMSSignerInfo(nullptr) |
| { |
| NSSSignatureConfiguration::setNSSDir({}); |
| CMSitem.data = p7.data(); |
| CMSitem.len = p7.size(); |
| CMSMessage = CMS_MessageCreate(&CMSitem); |
| CMSSignedData = CMS_SignedDataCreate(CMSMessage); |
| if (CMSSignedData) { |
| CMSSignerInfo = CMS_SignerInfoCreate(CMSSignedData); |
| SECAlgorithmID **algs = NSS_CMSSignedData_GetDigestAlgs(CMSSignedData); |
| while (*algs != nullptr) { |
| SECItem usedAlgorithm = (*algs)->algorithm; |
| auto hashAlgorithm = SECOID_FindOIDTag(&usedAlgorithm); |
| HASH_HashType hashType = HASH_GetHashTypeByOidTag(hashAlgorithm); |
| hashContext = HashContext::create(ConvertHashTypeFromNss(hashType)); |
| |
| if (hashContext) { |
| break; |
| } |
| ++algs; |
| } |
| } |
| } |
| |
| NSSSignatureCreation::NSSSignatureCreation(const std::string &certNickname, HashAlgorithm digestAlgTag) : hashContext(HashContext::create(digestAlgTag)), signing_cert(nullptr) |
| { |
| NSSSignatureConfiguration::setNSSDir({}); |
| signing_cert = CERT_FindCertByNickname(CERT_GetDefaultCertDB(), certNickname.c_str()); |
| } |
| |
| HashAlgorithm NSSSignatureVerification::getHashAlgorithm() const |
| { |
| if (hashContext) { |
| return hashContext->getHashAlgorithm(); |
| } else { |
| return HashAlgorithm::Unknown; |
| } |
| } |
| |
| void NSSSignatureVerification::addData(unsigned char *data_block, int data_len) |
| { |
| if (hashContext) { |
| hashContext->updateHash(data_block, data_len); |
| } |
| } |
| |
| void NSSSignatureCreation::addData(unsigned char *data_block, int data_len) |
| { |
| hashContext->updateHash(data_block, data_len); |
| } |
| |
| NSSSignatureCreation::~NSSSignatureCreation() |
| { |
| if (signing_cert) { |
| CERT_DestroyCertificate(signing_cert); |
| } |
| } |
| NSSSignatureVerification::~NSSSignatureVerification() |
| { |
| if (CMSMessage) { |
| // in the CMS_SignedDataCreate, we malloc some memory |
| // inside the CMSSignedData structure |
| // which is otherwise destructed by NSS_CMSMessage_Destroy |
| // but given we did the malloc ourselves |
| // we also need to free it ourselves. |
| // After we free the surrounding memory but we need |
| // a handle to it before. |
| CERTCertificate **toFree = nullptr; |
| if (CMSSignedData) { |
| toFree = CMSSignedData->tempCerts; |
| } |
| NSS_CMSMessage_Destroy(CMSMessage); |
| free(toFree); |
| } |
| } |
| |
| static NSSCMSMessage *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; |
| } |
| } |
| |
| static NSSCMSSignedData *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); |
| } |
| return signedData; |
| } else { |
| return nullptr; |
| } |
| } |
| |
| static NSSCMSSignerInfo *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 NSSSignatureVerification::validateSignature() |
| { |
| if (!CMSSignedData) { |
| return SIGNATURE_GENERIC_ERROR; |
| } |
| |
| if (!NSS_IsInitialized()) { |
| return SIGNATURE_GENERIC_ERROR; |
| } |
| |
| if (!hashContext) { |
| return SIGNATURE_GENERIC_ERROR; |
| } |
| |
| std::vector<unsigned char> digest_buffer = hashContext->endHash(); |
| |
| SECItem digest; |
| digest.data = digest_buffer.data(); |
| digest.len = digest_buffer.size(); |
| |
| 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 (digest.len == content_info_data->len && memcmp(digest.data, content_info_data->data, digest.len) == 0) { |
| return SIGNATURE_VALID; |
| } else { |
| return SIGNATURE_DIGEST_MISMATCH; |
| } |
| |
| } else if (NSS_CMSSignerInfo_Verify(CMSSignerInfo, &digest, nullptr) != SECSuccess) { |
| return NSS_SigTranslate(CMSSignerInfo->verificationStatus); |
| } else { |
| return SIGNATURE_VALID; |
| } |
| } |
| |
| void NSSSignatureVerification::validateCertificateAsync(std::chrono::system_clock::time_point validation_time, bool ocspRevocationCheck, bool useAIACertFetch, const std::function<void()> &doneCallback) |
| { |
| cachedValidationStatus.reset(); |
| CERTCertificate *cert; |
| |
| if (!CMSSignerInfo) { |
| validationStatus = std::async([doneCallback]() { |
| if (doneCallback) { |
| doneCallback(); |
| } |
| return CERTIFICATE_GENERIC_ERROR; |
| }); |
| return; |
| } |
| |
| 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 > std::chrono::system_clock::time_point {}) { |
| vTime = 1000000 * (PRTime)std::chrono::system_clock::to_time_t(validation_time); |
| } |
| CERTValInParam inParams[4]; |
| inParams[0].type = cert_pi_revocationFlags; |
| if (ocspRevocationCheck) { |
| inParams[0].value.pointer.revocation = CERT_GetClassicOCSPEnabledSoftFailurePolicy(); |
| } else { |
| inParams[0].value.pointer.revocation = CERT_GetClassicOCSPDisabledPolicy(); |
| } |
| inParams[1].type = cert_pi_date; |
| inParams[1].value.scalar.time = vTime; |
| if (useAIACertFetch) { |
| inParams[2].type = cert_pi_useAIACertFetch; |
| inParams[2].value.scalar.b = PR_TRUE; |
| inParams[3].type = cert_pi_end; |
| } else { |
| inParams[2].type = cert_pi_end; |
| } |
| |
| CERT_PKIXVerifyCert(cert, certificateUsageEmailSigner, inParams, nullptr, CMSSignerInfo->cmsg->pwfn_arg); |
| |
| // Here we are just faking the asynchronousness. It should |
| // somehow be the call to CERT_PXIXVerifyCert that would |
| // be put in the thread, but I'm not sure about all of the |
| // thread safety of nss. |
| |
| validationStatus = std::async([result = PORT_GetError(), doneCallback]() { |
| if (doneCallback) { |
| doneCallback(); |
| } |
| |
| switch (result) { |
| // 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; |
| }); |
| } |
| |
| CertificateValidationStatus NSSSignatureVerification::validateCertificateResult() |
| { |
| if (cachedValidationStatus) { |
| return cachedValidationStatus.value(); |
| } |
| if (!validationStatus.valid()) { |
| return CERTIFICATE_NOT_VERIFIED; |
| } |
| validationStatus.wait(); |
| cachedValidationStatus = validationStatus.get(); |
| return cachedValidationStatus.value(); |
| } |
| |
| std::optional<GooString> NSSSignatureCreation::signDetached(const std::string &password) |
| { |
| if (!hashContext) { |
| return {}; |
| } |
| std::vector<unsigned char> digest_buffer = hashContext->endHash(); |
| SECItem digest; |
| digest.data = digest_buffer.data(); |
| digest.len = digest_buffer.size(); |
| |
| ///////////////////////////////////// |
| /// Code from LibreOffice under MPLv2 |
| ///////////////////////////////////// |
| struct NSSCMSMessageDestroyer |
| { |
| void operator()(NSSCMSMessage *message) { NSS_CMSMessage_Destroy(message); } |
| }; |
| std::unique_ptr<NSSCMSMessage, NSSCMSMessageDestroyer> cms_msg { NSS_CMSMessage_Create(nullptr) }; |
| if (!cms_msg) { |
| return {}; |
| } |
| |
| NSSCMSSignedData *cms_sd = NSS_CMSSignedData_Create(cms_msg.get()); |
| if (!cms_sd) { |
| return {}; |
| } |
| |
| NSSCMSContentInfo *cms_cinfo = NSS_CMSMessage_GetContentInfo(cms_msg.get()); |
| |
| if (NSS_CMSContentInfo_SetContent_SignedData(cms_msg.get(), cms_cinfo, cms_sd) != SECSuccess) { |
| return {}; |
| } |
| |
| cms_cinfo = NSS_CMSSignedData_GetContentInfo(cms_sd); |
| |
| // Attach NULL data as detached data |
| if (NSS_CMSContentInfo_SetContent_Data(cms_msg.get(), cms_cinfo, nullptr, PR_TRUE) != SECSuccess) { |
| return {}; |
| } |
| |
| // hardcode SHA256 these days... |
| NSSCMSSignerInfo *cms_signer = NSS_CMSSignerInfo_Create(cms_msg.get(), signing_cert, SEC_OID_SHA256); |
| if (!cms_signer) { |
| return {}; |
| } |
| |
| if (NSS_CMSSignerInfo_IncludeCerts(cms_signer, NSSCMSCM_CertChain, certUsageEmailSigner) != SECSuccess) { |
| return {}; |
| } |
| |
| if (NSS_CMSSignedData_AddCertificate(cms_sd, signing_cert) != SECSuccess) { |
| return {}; |
| } |
| |
| if (NSS_CMSSignedData_AddSignerInfo(cms_sd, cms_signer) != SECSuccess) { |
| return {}; |
| } |
| |
| if (NSS_CMSSignedData_SetDigestValue(cms_sd, SEC_OID_SHA256, &digest) != SECSuccess) { |
| return {}; |
| } |
| |
| struct PLArenaFreeFalse |
| { |
| void operator()(PLArenaPool *arena) { PORT_FreeArena(arena, PR_FALSE); } |
| }; |
| std::unique_ptr<PLArenaPool, PLArenaFreeFalse> arena { PORT_NewArena(CryptoSign::maxSupportedSignatureSize) }; |
| |
| // Add the signing certificate as a signed attribute. |
| ESSCertIDv2 *aCertIDs[2]; |
| ESSCertIDv2 aCertID; |
| // Write ESSCertIDv2.hashAlgorithm. |
| aCertID.hashAlgorithm.algorithm.data = nullptr; |
| aCertID.hashAlgorithm.parameters.data = nullptr; |
| SECOID_SetAlgorithmID(arena.get(), &aCertID.hashAlgorithm, SEC_OID_SHA256, nullptr); |
| |
| // Write ESSCertIDv2.certHash. |
| SECItem aCertHashItem; |
| unsigned char certhash[32]; |
| SECStatus rv = PK11_HashBuf(SEC_OID_SHA256, certhash, signing_cert->derCert.data, signing_cert->derCert.len); |
| if (rv != SECSuccess) { |
| return {}; |
| } |
| |
| aCertHashItem.type = siBuffer; |
| aCertHashItem.data = certhash; |
| aCertHashItem.len = 32; |
| aCertID.certHash = aCertHashItem; |
| |
| // Write ESSCertIDv2.issuerSerial. |
| IssuerSerial aSerial; |
| GeneralName aName; |
| aName.name = signing_cert->issuer; |
| aSerial.issuer.names = aName; |
| aSerial.serialNumber = signing_cert->serialNumber; |
| aCertID.issuerSerial = aSerial; |
| // Write SigningCertificateV2.certs. |
| aCertIDs[0] = &aCertID; |
| aCertIDs[1] = nullptr; |
| SigningCertificateV2 aCertificate; |
| aCertificate.certs = &aCertIDs[0]; |
| |
| SECItem *pEncodedCertificate = SEC_ASN1EncodeItem(nullptr, nullptr, &aCertificate, SigningCertificateV2Template); |
| if (!pEncodedCertificate) { |
| return {}; |
| } |
| |
| NSSCMSAttribute aAttribute; |
| SECItem aAttributeValues[2]; |
| SECItem *pAttributeValues[2]; |
| pAttributeValues[0] = aAttributeValues; |
| pAttributeValues[1] = nullptr; |
| aAttributeValues[0] = *pEncodedCertificate; |
| aAttributeValues[1].type = siBuffer; |
| aAttributeValues[1].data = nullptr; |
| aAttributeValues[1].len = 0; |
| aAttribute.values = pAttributeValues; |
| |
| SECOidData aOidData; |
| aOidData.oid.data = nullptr; |
| /* |
| * id-aa-signingCertificateV2 OBJECT IDENTIFIER ::= |
| * { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs9(9) |
| * smime(16) id-aa(2) 47 } |
| */ |
| if (my_SEC_StringToOID(arena.get(), &aOidData.oid, "1.2.840.113549.1.9.16.2.47", 0) != SECSuccess) { |
| return {}; |
| } |
| |
| aOidData.offset = SEC_OID_UNKNOWN; |
| aOidData.desc = "id-aa-signingCertificateV2"; |
| aOidData.mechanism = CKM_SHA_1; |
| aOidData.supportedExtension = UNSUPPORTED_CERT_EXTENSION; |
| aAttribute.typeTag = &aOidData; |
| aAttribute.type = aOidData.oid; |
| aAttribute.encoded = PR_TRUE; |
| |
| if (my_NSS_CMSSignerInfo_AddAuthAttr(cms_signer, &aAttribute) != SECSuccess) { |
| return {}; |
| } |
| |
| SECItem cms_output; |
| cms_output.data = nullptr; |
| cms_output.len = 0; |
| |
| NSSCMSEncoderContext *cms_ecx = NSS_CMSEncoder_Start(cms_msg.get(), nullptr, nullptr, &cms_output, arena.get(), passwordCallback, password.empty() ? nullptr : const_cast<char *>(password.c_str()), nullptr, nullptr, nullptr, nullptr); |
| if (!cms_ecx) { |
| return {}; |
| } |
| |
| if (NSS_CMSEncoder_Finish(cms_ecx) != SECSuccess) { |
| return {}; |
| } |
| |
| auto signature = GooString(reinterpret_cast<const char *>(cms_output.data), cms_output.len); |
| |
| SECITEM_FreeItem(pEncodedCertificate, PR_TRUE); |
| |
| return signature; |
| } |
| |
| static char *GetPasswordFunction(PK11SlotInfo *slot, PRBool /*retry*/, void * /*arg*/) |
| { |
| const char *name = PK11_GetTokenName(slot); |
| if (PasswordFunction) { |
| return PasswordFunction(name); |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<CryptoSign::VerificationInterface> NSSCryptoSignBackend::createVerificationHandler(std::vector<unsigned char> &&pkcs7) |
| { |
| return std::make_unique<NSSSignatureVerification>(std::move(pkcs7)); |
| } |
| |
| std::unique_ptr<CryptoSign::SigningInterface> NSSCryptoSignBackend::createSigningHandler(const std::string &certID, HashAlgorithm digestAlgTag) |
| { |
| return std::make_unique<NSSSignatureCreation>(certID, digestAlgTag); |
| } |
| |
| std::vector<std::unique_ptr<X509CertificateInfo>> NSSCryptoSignBackend::getAvailableSigningCertificates() |
| { |
| // set callback, in case one of the slots has a password set |
| PK11_SetPasswordFunc(GetPasswordFunction); |
| NSSSignatureConfiguration::setNSSDir({}); |
| |
| std::vector<std::unique_ptr<X509CertificateInfo>> certsList; |
| PK11SlotList *slotList = PK11_GetAllTokens(CKM_INVALID_MECHANISM, PR_FALSE, PR_FALSE, nullptr); |
| if (slotList) { |
| for (PK11SlotListElement *slotElement = slotList->head; slotElement; slotElement = slotElement->next) { |
| PK11SlotInfo *pSlot = slotElement->slot; |
| if (PK11_NeedLogin(pSlot)) { |
| SECStatus nRet = PK11_Authenticate(pSlot, PR_TRUE, nullptr); |
| // PK11_Authenticate may fail in case the a slot has not been initialized. |
| // this is the case if the user has a new profile, so that they have never |
| // added a personal certificate. |
| if (nRet != SECSuccess && PORT_GetError() != SEC_ERROR_IO) { |
| continue; |
| } |
| } |
| |
| SECKEYPrivateKeyList *privKeyList = PK11_ListPrivateKeysInSlot(pSlot); |
| if (privKeyList) { |
| for (SECKEYPrivateKeyListNode *curPri = PRIVKEY_LIST_HEAD(privKeyList); !PRIVKEY_LIST_END(curPri, privKeyList) && curPri != nullptr; curPri = PRIVKEY_LIST_NEXT(curPri)) { |
| if (curPri->key) { |
| CERTCertificate *cert = PK11_GetCertFromPrivateKey(curPri->key); |
| if (cert) { |
| certsList.push_back(getCertificateInfoFromCERT(cert)); |
| CERT_DestroyCertificate(cert); |
| } |
| } |
| } |
| SECKEY_DestroyPrivateKeyList(privKeyList); |
| } |
| } |
| PK11_FreeSlotList(slotList); |
| } |
| |
| PK11_SetPasswordFunc(nullptr); |
| |
| return certsList; |
| } |
| |
| void HashContext::updateHash(unsigned char *data_block, int data_len) |
| { |
| HASH_Update(hash_context.get(), data_block, data_len); |
| } |
| |
| std::vector<unsigned char> HashContext::endHash() |
| { |
| auto hash_length = digestLength(digest_alg_tag); |
| std::vector<unsigned char> digestBuffer(hash_length); |
| unsigned int result_length = 0; |
| HASH_End(hash_context.get(), digestBuffer.data(), &result_length, digestBuffer.size()); |
| digestBuffer.resize(result_length); |
| |
| return digestBuffer; |
| } |
| |
| HashContext::HashContext(HashAlgorithm algorithm, private_tag) : hash_context { HASH_Create(HASH_GetHashTypeByOidTag(ConvertHashAlgorithmToNss(algorithm))) }, digest_alg_tag(algorithm) { } |
| |
| std::unique_ptr<HashContext> HashContext::create(HashAlgorithm algorithm) |
| { |
| auto ctx = std::make_unique<HashContext>(algorithm, private_tag {}); |
| if (ctx->hash_context) { |
| return ctx; |
| } |
| return {}; |
| } |
| |
| HashAlgorithm HashContext::getHashAlgorithm() const |
| { |
| return digest_alg_tag; |
| } |
| |
| NSSCryptoSignBackend::~NSSCryptoSignBackend() = default; |