fune/security/manager/ssl/nsCryptoHash.cpp
Cykesiopka 7995951109 Bug 1338897 - Avoid using NSS Base64 functions in PSM. r=keeler
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
2017-03-17 23:31:40 +08:00

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;
}