337 lines
10 KiB
C++
337 lines
10 KiB
C++
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||
|
|
||
|
#include "nsDataSignatureVerifier.h"
|
||
|
|
||
|
#include "cms.h"
|
||
|
#include "cryptohi.h"
|
||
|
#include "keyhi.h"
|
||
|
#include "mozilla/Casting.h"
|
||
|
#include "mozilla/Unused.h"
|
||
|
#include "nsCOMPtr.h"
|
||
|
#include "nsNSSComponent.h"
|
||
|
#include "nssb64.h"
|
||
|
#include "pkix/pkixnss.h"
|
||
|
#include "pkix/pkixtypes.h"
|
||
|
#include "ScopedNSSTypes.h"
|
||
|
#include "secerr.h"
|
||
|
#include "SharedCertVerifier.h"
|
||
|
|
||
|
using namespace mozilla;
|
||
|
using namespace mozilla::pkix;
|
||
|
using namespace mozilla::psm;
|
||
|
|
||
|
SEC_ASN1_MKSUB(SECOID_AlgorithmIDTemplate)
|
||
|
|
||
|
NS_IMPL_ISUPPORTS(nsDataSignatureVerifier, nsIDataSignatureVerifier)
|
||
|
|
||
|
const SEC_ASN1Template CERT_SignatureDataTemplate[] =
|
||
|
{
|
||
|
{ SEC_ASN1_SEQUENCE,
|
||
|
0, nullptr, sizeof(CERTSignedData) },
|
||
|
{ SEC_ASN1_INLINE | SEC_ASN1_XTRN,
|
||
|
offsetof(CERTSignedData,signatureAlgorithm),
|
||
|
SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate), },
|
||
|
{ SEC_ASN1_BIT_STRING,
|
||
|
offsetof(CERTSignedData,signature), },
|
||
|
{ 0, }
|
||
|
};
|
||
|
|
||
|
nsDataSignatureVerifier::~nsDataSignatureVerifier()
|
||
|
{
|
||
|
nsNSSShutDownPreventionLock locker;
|
||
|
if (isAlreadyShutDown()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
shutdown(ShutdownCalledFrom::Object);
|
||
|
}
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsDataSignatureVerifier::VerifyData(const nsACString& aData,
|
||
|
const nsACString& aSignature,
|
||
|
const nsACString& aPublicKey,
|
||
|
bool* _retval)
|
||
|
{
|
||
|
NS_ENSURE_ARG_POINTER(_retval);
|
||
|
|
||
|
nsNSSShutDownPreventionLock locker;
|
||
|
if (isAlreadyShutDown()) {
|
||
|
return NS_ERROR_NOT_AVAILABLE;
|
||
|
}
|
||
|
|
||
|
// Allocate an arena to handle the majority of the allocations
|
||
|
UniquePLArenaPool arena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
||
|
if (!arena) {
|
||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
|
||
|
// Base 64 decode the key
|
||
|
SECItem keyItem;
|
||
|
PORT_Memset(&keyItem, 0, sizeof(SECItem));
|
||
|
if (!NSSBase64_DecodeBuffer(arena.get(), &keyItem,
|
||
|
PromiseFlatCString(aPublicKey).get(),
|
||
|
aPublicKey.Length())) {
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
// Extract the public key from the data
|
||
|
UniqueCERTSubjectPublicKeyInfo pki(
|
||
|
SECKEY_DecodeDERSubjectPublicKeyInfo(&keyItem));
|
||
|
if (!pki) {
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
UniqueSECKEYPublicKey publicKey(SECKEY_ExtractPublicKey(pki.get()));
|
||
|
if (!publicKey) {
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
// Base 64 decode the signature
|
||
|
SECItem signatureItem;
|
||
|
PORT_Memset(&signatureItem, 0, sizeof(SECItem));
|
||
|
if (!NSSBase64_DecodeBuffer(arena.get(), &signatureItem,
|
||
|
PromiseFlatCString(aSignature).get(),
|
||
|
aSignature.Length())) {
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
// Decode the signature and algorithm
|
||
|
CERTSignedData sigData;
|
||
|
PORT_Memset(&sigData, 0, sizeof(CERTSignedData));
|
||
|
SECStatus srv = SEC_QuickDERDecodeItem(arena.get(), &sigData,
|
||
|
CERT_SignatureDataTemplate,
|
||
|
&signatureItem);
|
||
|
if (srv != SECSuccess) {
|
||
|
return NS_ERROR_FAILURE;
|
||
|
}
|
||
|
|
||
|
// Perform the final verification
|
||
|
DER_ConvertBitString(&(sigData.signature));
|
||
|
srv = VFY_VerifyDataWithAlgorithmID(
|
||
|
BitwiseCast<const unsigned char*, const char*>(
|
||
|
PromiseFlatCString(aData).get()),
|
||
|
aData.Length(), publicKey.get(), &(sigData.signature),
|
||
|
&(sigData.signatureAlgorithm), nullptr, nullptr);
|
||
|
|
||
|
*_retval = (srv == SECSuccess);
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
namespace mozilla {
|
||
|
|
||
|
nsresult
|
||
|
VerifyCMSDetachedSignatureIncludingCertificate(
|
||
|
const SECItem& buffer, const SECItem& detachedDigest,
|
||
|
nsresult (*verifyCertificate)(CERTCertificate* cert, void* context,
|
||
|
void* pinArg),
|
||
|
void* verifyCertificateContext, void* pinArg,
|
||
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
||
|
{
|
||
|
// XXX: missing pinArg is tolerated.
|
||
|
if (NS_WARN_IF(!buffer.data && buffer.len > 0) ||
|
||
|
NS_WARN_IF(!detachedDigest.data && detachedDigest.len > 0) ||
|
||
|
(!verifyCertificate) ||
|
||
|
NS_WARN_IF(!verifyCertificateContext)) {
|
||
|
return NS_ERROR_INVALID_ARG;
|
||
|
}
|
||
|
|
||
|
UniqueNSSCMSMessage
|
||
|
cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr,
|
||
|
nullptr, nullptr, nullptr, nullptr,
|
||
|
nullptr));
|
||
|
if (!cmsMsg) {
|
||
|
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
|
||
|
}
|
||
|
|
||
|
if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) {
|
||
|
return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
|
||
|
}
|
||
|
|
||
|
NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0);
|
||
|
if (!cinfo) {
|
||
|
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
|
||
|
}
|
||
|
|
||
|
// We're expecting this to be a PKCS#7 signedData content info.
|
||
|
if (NSS_CMSContentInfo_GetContentTypeTag(cinfo)
|
||
|
!= SEC_OID_PKCS7_SIGNED_DATA) {
|
||
|
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
|
||
|
}
|
||
|
|
||
|
// signedData is non-owning
|
||
|
NSSCMSSignedData* signedData =
|
||
|
static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo));
|
||
|
if (!signedData) {
|
||
|
return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
|
||
|
}
|
||
|
|
||
|
// Set digest value.
|
||
|
if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1,
|
||
|
const_cast<SECItem*>(&detachedDigest))) {
|
||
|
return NS_ERROR_CMS_VERIFY_BAD_DIGEST;
|
||
|
}
|
||
|
|
||
|
// Parse the certificates into CERTCertificate objects held in memory so
|
||
|
// verifyCertificate will be able to find them during path building.
|
||
|
UniqueCERTCertList certs(CERT_NewCertList());
|
||
|
if (!certs) {
|
||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
if (signedData->rawCerts) {
|
||
|
for (size_t i = 0; signedData->rawCerts[i]; ++i) {
|
||
|
UniqueCERTCertificate
|
||
|
cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(),
|
||
|
signedData->rawCerts[i], nullptr, false,
|
||
|
true));
|
||
|
// Skip certificates that fail to parse
|
||
|
if (!cert) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (CERT_AddCertToListTail(certs.get(), cert.get()) != SECSuccess) {
|
||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
|
||
|
Unused << cert.release(); // Ownership transferred to the cert list.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get the end-entity certificate.
|
||
|
int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData);
|
||
|
if (NS_WARN_IF(numSigners != 1)) {
|
||
|
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
|
||
|
}
|
||
|
// signer is non-owning.
|
||
|
NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0);
|
||
|
if (NS_WARN_IF(!signer)) {
|
||
|
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
|
||
|
}
|
||
|
CERTCertificate* signerCert =
|
||
|
NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB());
|
||
|
if (!signerCert) {
|
||
|
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
|
||
|
}
|
||
|
|
||
|
nsresult rv = verifyCertificate(signerCert, verifyCertificateContext, pinArg);
|
||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
// See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS.
|
||
|
SECOidData* contentTypeOidData =
|
||
|
SECOID_FindOID(&signedData->contentInfo.contentType);
|
||
|
if (!contentTypeOidData) {
|
||
|
return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
|
||
|
}
|
||
|
|
||
|
return MapSECStatus(NSS_CMSSignerInfo_Verify(signer,
|
||
|
const_cast<SECItem*>(&detachedDigest),
|
||
|
&contentTypeOidData->oid));
|
||
|
}
|
||
|
|
||
|
} // namespace mozilla
|
||
|
|
||
|
namespace {
|
||
|
|
||
|
struct VerifyCertificateContext
|
||
|
{
|
||
|
nsCOMPtr<nsIX509Cert> signingCert;
|
||
|
UniqueCERTCertList builtChain;
|
||
|
};
|
||
|
|
||
|
static nsresult
|
||
|
VerifyCertificate(CERTCertificate* cert, void* voidContext, void* pinArg)
|
||
|
{
|
||
|
// XXX: missing pinArg is tolerated
|
||
|
if (NS_WARN_IF(!cert) || NS_WARN_IF(!voidContext)) {
|
||
|
return NS_ERROR_INVALID_ARG;
|
||
|
}
|
||
|
|
||
|
VerifyCertificateContext* context =
|
||
|
static_cast<VerifyCertificateContext*>(voidContext);
|
||
|
|
||
|
nsCOMPtr<nsIX509Cert> xpcomCert(nsNSSCertificate::Create(cert));
|
||
|
if (!xpcomCert) {
|
||
|
return NS_ERROR_OUT_OF_MEMORY;
|
||
|
}
|
||
|
|
||
|
context->signingCert = xpcomCert;
|
||
|
|
||
|
RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
|
||
|
NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
|
||
|
|
||
|
mozilla::pkix::Result result =
|
||
|
certVerifier->VerifyCert(cert,
|
||
|
certificateUsageObjectSigner,
|
||
|
Now(), pinArg,
|
||
|
nullptr, // hostname
|
||
|
context->builtChain);
|
||
|
if (result != Success) {
|
||
|
return GetXPCOMFromNSSError(MapResultToPRErrorCode(result));
|
||
|
}
|
||
|
|
||
|
return NS_OK;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
NS_IMETHODIMP
|
||
|
nsDataSignatureVerifier::VerifySignature(const char* aRSABuf,
|
||
|
uint32_t aRSABufLen,
|
||
|
const char* aPlaintext,
|
||
|
uint32_t aPlaintextLen,
|
||
|
int32_t* aErrorCode,
|
||
|
nsIX509Cert** aSigningCert)
|
||
|
{
|
||
|
if (!aRSABuf || !aPlaintext || !aErrorCode || !aSigningCert) {
|
||
|
return NS_ERROR_INVALID_ARG;
|
||
|
}
|
||
|
|
||
|
nsNSSShutDownPreventionLock locker;
|
||
|
if (isAlreadyShutDown()) {
|
||
|
return NS_ERROR_NOT_AVAILABLE;
|
||
|
}
|
||
|
|
||
|
*aErrorCode = VERIFY_ERROR_OTHER;
|
||
|
*aSigningCert = nullptr;
|
||
|
|
||
|
Digest digest;
|
||
|
nsresult rv = digest.DigestBuf(
|
||
|
SEC_OID_SHA1,
|
||
|
BitwiseCast<const uint8_t*, const char*>(aPlaintext),
|
||
|
aPlaintextLen);
|
||
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
|
return rv;
|
||
|
}
|
||
|
|
||
|
SECItem buffer = {
|
||
|
siBuffer,
|
||
|
BitwiseCast<unsigned char*, const char*>(aRSABuf),
|
||
|
aRSABufLen
|
||
|
};
|
||
|
|
||
|
VerifyCertificateContext context;
|
||
|
// XXX: pinArg is missing
|
||
|
rv = VerifyCMSDetachedSignatureIncludingCertificate(buffer, digest.get(),
|
||
|
VerifyCertificate,
|
||
|
&context, nullptr, locker);
|
||
|
if (NS_SUCCEEDED(rv)) {
|
||
|
*aErrorCode = VERIFY_OK;
|
||
|
} else if (NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY) {
|
||
|
if (rv == GetXPCOMFromNSSError(SEC_ERROR_UNKNOWN_ISSUER)) {
|
||
|
*aErrorCode = VERIFY_ERROR_UNKNOWN_ISSUER;
|
||
|
} else {
|
||
|
*aErrorCode = VERIFY_ERROR_OTHER;
|
||
|
}
|
||
|
rv = NS_OK;
|
||
|
}
|
||
|
if (rv == NS_OK) {
|
||
|
context.signingCert.forget(aSigningCert);
|
||
|
}
|
||
|
|
||
|
return rv;
|
||
|
}
|