forked from mirrors/gecko-dev
The NSS Base64 functions are less safe and convenient to use than the XPCOM ones. They're also an unnecessary dependency on NSS. The NSS Base64 functions behave slightly differently than the XPCOM ones: 1. ATOB_ConvertAsciiToItem() / NSSBase64_DecodeBuffer() silently ignore invalid characters like CRLF, space and so on. Base64Decode() will return an error if these characters are encountered. 2. BTOA_DataToAscii() will produce output that has CRLF inserted every 64 characters. Base64Encode() doesn't do this. For the reasons listed below, no unexpected compatibility issues should arise: 1. AppSignatureVerification.cpp already filters out CRLF and spaces for Manifest and Signature values before decoding. 2. ExtendedValidation.cpp is only given what should be valid hard-coded input to decode. 3. ContentSignatureVerifier.cpp already splits on CRLF for when it needs to decode PEM certs. Spaces shouldn't be likely. For Content-Signature header verification, examination of real input to a running instance of Firefox suggests CRLF and spaces will not be present in the header to decode. 4. nsCryptoHash.cpp encode is affected, but we actually don't want the CRLF behaviour. 5. nsDataSignatureVerifier.cpp decode is affected, but we add whitespace stripping to maintain backwards compatibility. 6. nsKeygenHandler.cpp encode is affected, but the previous CRLF behaviour was arguably a bug, since neither WHATWG or W3C specs specified this. MozReview-Commit-ID: IWMFxqVZMeX --HG-- extra : rebase_source : 4863b2e5eabef0555e8e1ebe39216d0d9393f3e9
448 lines
9.9 KiB
C++
448 lines
9.9 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* 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 "nsCryptoHash.h"
|
|
|
|
#include <algorithm>
|
|
|
|
#include "mozilla/ArrayUtils.h"
|
|
#include "mozilla/Base64.h"
|
|
#include "mozilla/Casting.h"
|
|
#include "nsDependentString.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIKeyModule.h"
|
|
#include "nsString.h"
|
|
#include "pk11pub.h"
|
|
#include "sechash.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
namespace {
|
|
|
|
static const uint64_t STREAM_BUFFER_SIZE = 4096;
|
|
|
|
} // namespace
|
|
|
|
//---------------------------------------------
|
|
// Implementing nsICryptoHash
|
|
//---------------------------------------------
|
|
|
|
nsCryptoHash::nsCryptoHash()
|
|
: mHashContext(nullptr)
|
|
, mInitialized(false)
|
|
{
|
|
}
|
|
|
|
nsCryptoHash::~nsCryptoHash()
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return;
|
|
}
|
|
destructorSafeDestroyNSSReference();
|
|
shutdown(ShutdownCalledFrom::Object);
|
|
}
|
|
|
|
void
|
|
nsCryptoHash::virtualDestroyNSSReference()
|
|
{
|
|
destructorSafeDestroyNSSReference();
|
|
}
|
|
|
|
void
|
|
nsCryptoHash::destructorSafeDestroyNSSReference()
|
|
{
|
|
mHashContext = nullptr;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(nsCryptoHash, nsICryptoHash)
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHash::Init(uint32_t algorithm)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
HASH_HashType hashType;
|
|
switch (algorithm) {
|
|
case nsICryptoHash::MD2:
|
|
hashType = HASH_AlgMD2; break;
|
|
case nsICryptoHash::MD5:
|
|
hashType = HASH_AlgMD5; break;
|
|
case nsICryptoHash::SHA1:
|
|
hashType = HASH_AlgSHA1; break;
|
|
case nsICryptoHash::SHA256:
|
|
hashType = HASH_AlgSHA256; break;
|
|
case nsICryptoHash::SHA384:
|
|
hashType = HASH_AlgSHA384; break;
|
|
case nsICryptoHash::SHA512:
|
|
hashType = HASH_AlgSHA512; break;
|
|
default:
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (mHashContext) {
|
|
if (!mInitialized && HASH_GetType(mHashContext.get()) == hashType) {
|
|
mInitialized = true;
|
|
HASH_Begin(mHashContext.get());
|
|
return NS_OK;
|
|
}
|
|
|
|
// Destroy current hash context if the type was different
|
|
// or Finish method wasn't called.
|
|
mHashContext = nullptr;
|
|
mInitialized = false;
|
|
}
|
|
|
|
mHashContext.reset(HASH_Create(hashType));
|
|
if (!mHashContext) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
HASH_Begin(mHashContext.get());
|
|
mInitialized = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHash::InitWithString(const nsACString & aAlgorithm)
|
|
{
|
|
if (aAlgorithm.LowerCaseEqualsLiteral("md2"))
|
|
return Init(nsICryptoHash::MD2);
|
|
|
|
if (aAlgorithm.LowerCaseEqualsLiteral("md5"))
|
|
return Init(nsICryptoHash::MD5);
|
|
|
|
if (aAlgorithm.LowerCaseEqualsLiteral("sha1"))
|
|
return Init(nsICryptoHash::SHA1);
|
|
|
|
if (aAlgorithm.LowerCaseEqualsLiteral("sha256"))
|
|
return Init(nsICryptoHash::SHA256);
|
|
|
|
if (aAlgorithm.LowerCaseEqualsLiteral("sha384"))
|
|
return Init(nsICryptoHash::SHA384);
|
|
|
|
if (aAlgorithm.LowerCaseEqualsLiteral("sha512"))
|
|
return Init(nsICryptoHash::SHA512);
|
|
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHash::Update(const uint8_t *data, uint32_t len)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mInitialized) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
HASH_Update(mHashContext.get(), data, len);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHash::UpdateFromStream(nsIInputStream *data, uint32_t aLen)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mInitialized)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
if (!data)
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
uint64_t n;
|
|
nsresult rv = data->Available(&n);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// if the user has passed UINT32_MAX, then read
|
|
// everything in the stream
|
|
|
|
uint64_t len = aLen;
|
|
if (aLen == UINT32_MAX)
|
|
len = n;
|
|
|
|
// So, if the stream has NO data available for the hash,
|
|
// or if the data available is less then what the caller
|
|
// requested, we can not fulfill the hash update. In this
|
|
// case, just return NS_ERROR_NOT_AVAILABLE indicating
|
|
// that there is not enough data in the stream to satisify
|
|
// the request.
|
|
|
|
if (n == 0 || n < len) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
char buffer[STREAM_BUFFER_SIZE];
|
|
while (len > 0) {
|
|
uint64_t readLimit = std::min<uint64_t>(STREAM_BUFFER_SIZE, len);
|
|
uint32_t read;
|
|
rv = data->Read(buffer, AssertedCast<uint32_t>(readLimit), &read);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
rv = Update(BitwiseCast<uint8_t*>(buffer), read);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
len -= read;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHash::Finish(bool ascii, nsACString & _retval)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mInitialized) {
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
uint32_t hashLen = 0;
|
|
unsigned char buffer[HASH_LENGTH_MAX];
|
|
HASH_End(mHashContext.get(), buffer, &hashLen, HASH_LENGTH_MAX);
|
|
|
|
mInitialized = false;
|
|
|
|
if (ascii) {
|
|
nsDependentCSubstring dataStr(BitwiseCast<char*>(buffer), hashLen);
|
|
return Base64Encode(dataStr, _retval);
|
|
}
|
|
|
|
_retval.Assign(BitwiseCast<char*>(buffer), hashLen);
|
|
return NS_OK;
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// Implementing nsICryptoHMAC
|
|
//---------------------------------------------
|
|
|
|
NS_IMPL_ISUPPORTS(nsCryptoHMAC, nsICryptoHMAC)
|
|
|
|
nsCryptoHMAC::nsCryptoHMAC()
|
|
: mHMACContext(nullptr)
|
|
{
|
|
}
|
|
|
|
nsCryptoHMAC::~nsCryptoHMAC()
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return;
|
|
}
|
|
destructorSafeDestroyNSSReference();
|
|
shutdown(ShutdownCalledFrom::Object);
|
|
}
|
|
|
|
void
|
|
nsCryptoHMAC::virtualDestroyNSSReference()
|
|
{
|
|
destructorSafeDestroyNSSReference();
|
|
}
|
|
|
|
void
|
|
nsCryptoHMAC::destructorSafeDestroyNSSReference()
|
|
{
|
|
mHMACContext = nullptr;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHMAC::Init(uint32_t aAlgorithm, nsIKeyObject *aKeyObject)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (mHMACContext) {
|
|
mHMACContext = nullptr;
|
|
}
|
|
|
|
CK_MECHANISM_TYPE mechType;
|
|
switch (aAlgorithm) {
|
|
case nsICryptoHMAC::MD5:
|
|
mechType = CKM_MD5_HMAC; break;
|
|
case nsICryptoHMAC::SHA1:
|
|
mechType = CKM_SHA_1_HMAC; break;
|
|
case nsICryptoHMAC::SHA256:
|
|
mechType = CKM_SHA256_HMAC; break;
|
|
case nsICryptoHMAC::SHA384:
|
|
mechType = CKM_SHA384_HMAC; break;
|
|
case nsICryptoHMAC::SHA512:
|
|
mechType = CKM_SHA512_HMAC; break;
|
|
default:
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
NS_ENSURE_ARG_POINTER(aKeyObject);
|
|
|
|
nsresult rv;
|
|
|
|
int16_t keyType;
|
|
rv = aKeyObject->GetType(&keyType);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
NS_ENSURE_TRUE(keyType == nsIKeyObject::SYM_KEY, NS_ERROR_INVALID_ARG);
|
|
|
|
PK11SymKey* key;
|
|
// GetKeyObj doesn't addref the key
|
|
rv = aKeyObject->GetKeyObj(&key);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
SECItem rawData;
|
|
rawData.data = 0;
|
|
rawData.len = 0;
|
|
mHMACContext.reset(PK11_CreateContextBySymKey(mechType, CKA_SIGN, key,
|
|
&rawData));
|
|
NS_ENSURE_TRUE(mHMACContext, NS_ERROR_FAILURE);
|
|
|
|
if (PK11_DigestBegin(mHMACContext.get()) != SECSuccess) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHMAC::Update(const uint8_t *aData, uint32_t aLen)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mHMACContext)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
if (!aData)
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
if (PK11_DigestOp(mHMACContext.get(), aData, aLen) != SECSuccess) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHMAC::UpdateFromStream(nsIInputStream *aStream, uint32_t aLen)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mHMACContext)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
if (!aStream)
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
uint64_t n;
|
|
nsresult rv = aStream->Available(&n);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
// if the user has passed UINT32_MAX, then read
|
|
// everything in the stream
|
|
|
|
uint64_t len = aLen;
|
|
if (aLen == UINT32_MAX)
|
|
len = n;
|
|
|
|
// So, if the stream has NO data available for the hash,
|
|
// or if the data available is less then what the caller
|
|
// requested, we can not fulfill the HMAC update. In this
|
|
// case, just return NS_ERROR_NOT_AVAILABLE indicating
|
|
// that there is not enough data in the stream to satisify
|
|
// the request.
|
|
|
|
if (n == 0 || n < len)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
char buffer[STREAM_BUFFER_SIZE];
|
|
while (len > 0) {
|
|
uint64_t readLimit = std::min<uint64_t>(STREAM_BUFFER_SIZE, len);
|
|
uint32_t read;
|
|
rv = aStream->Read(buffer, AssertedCast<uint32_t>(readLimit), &read);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (read == 0) {
|
|
return NS_BASE_STREAM_CLOSED;
|
|
}
|
|
|
|
rv = Update(BitwiseCast<uint8_t*>(buffer), read);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
len -= read;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHMAC::Finish(bool aASCII, nsACString & _retval)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!mHMACContext)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
uint32_t hashLen = 0;
|
|
unsigned char buffer[HASH_LENGTH_MAX];
|
|
SECStatus srv = PK11_DigestFinal(mHMACContext.get(), buffer, &hashLen,
|
|
HASH_LENGTH_MAX);
|
|
if (srv != SECSuccess) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aASCII) {
|
|
nsDependentCSubstring dataStr(BitwiseCast<char*>(buffer), hashLen);
|
|
return Base64Encode(dataStr, _retval);
|
|
}
|
|
|
|
_retval.Assign(BitwiseCast<char*>(buffer), hashLen);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsCryptoHMAC::Reset()
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (PK11_DigestBegin(mHMACContext.get()) != SECSuccess) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|