From da8f86f15be2189defd30221a3b1d405512d723d Mon Sep 17 00:00:00 2001 From: "dcamp@mozilla.com" Date: Tue, 26 Feb 2008 22:31:13 -0800 Subject: [PATCH] Bug 415799: Interface and implementation for HMAC support. p=honzab@allpeers.com (Jan Bambas), r=kaie, r=rrelyea, sr=dveditz, blocking1.9=sayrer --- netwerk/base/public/Makefile.in | 1 + netwerk/base/public/nsICryptoHMAC.idl | 139 ++++++++++++++ netwerk/build/nsNetCID.h | 5 + security/manager/ssl/src/nsNSSComponent.cpp | 180 +++++++++++++++++- security/manager/ssl/src/nsNSSComponent.h | 20 +- security/manager/ssl/src/nsNSSModule.cpp | 8 + .../manager/ssl/tests/test_hash_algorithms.js | 21 ++ security/manager/ssl/tests/test_hmac.js | 46 +++++ 8 files changed, 416 insertions(+), 4 deletions(-) create mode 100644 netwerk/base/public/nsICryptoHMAC.idl create mode 100644 security/manager/ssl/tests/test_hmac.js diff --git a/netwerk/base/public/Makefile.in b/netwerk/base/public/Makefile.in index 14cf574087d..7fe717374e3 100644 --- a/netwerk/base/public/Makefile.in +++ b/netwerk/base/public/Makefile.in @@ -71,6 +71,7 @@ XPIDLSRCS = \ nsIBufferedStreams.idl \ nsICancelable.idl \ nsICryptoHash.idl \ + nsICryptoHMAC.idl \ nsIDownloader.idl \ nsIEncodedChannel.idl \ nsIFileStreams.idl \ diff --git a/netwerk/base/public/nsICryptoHMAC.idl b/netwerk/base/public/nsICryptoHMAC.idl new file mode 100644 index 00000000000..96608cf2fd4 --- /dev/null +++ b/netwerk/base/public/nsICryptoHMAC.idl @@ -0,0 +1,139 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Doug Turner . + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Doug Turner + * Honza Bambas + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" +interface nsIInputStream; + +/** + * nsICryptoHMAC + * This interface provides HMAC signature algorithms. + */ + +[scriptable, uuid(B8E1AC84-E10D-47fe-9D03-F0AF2E943E71)] +interface nsICryptoHMAC : nsISupports +{ + /** + * Hashing Algorithms. These values are to be used by the + * |init| method to indicate which hashing function to + * use. These values map onto the values defined in + * mozilla/security/nss/lib/softoken/pkcs11t.h and are + * switched to CKM_*_HMAC constant. + */ + const short MD2 = 1; + const short MD5 = 2; + const short SHA1 = 3; + const short SHA256 = 4; + const short SHA384 = 5; + const short SHA512 = 6; + + /** + * Initialize the hashing object. This method may be + * called multiple times with different algorithm types. + * + * @param aAlgorithm the algorithm type to be used. + * This value must be one of the above valid + * algorithm types. + * + * @param aKeyData the raw key data used for the HMAC + * computation + * + * @param aKeyLength length of the key in bytes + * + * @throws NS_ERROR_INVALID_ARG if an unsupported algorithm + * type is passed. + * + * NOTE: This method must be called before any other method + * on this interface is called. + */ + void init(in unsigned long aAlgorithm, [const, array, size_is(aKeyLen)] in octet aKeyData, + in unsigned long aKeyLen); + + /** + * @param aData a buffer to calculate the hash over + * + * @param aLen the length of the buffer |aData| + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + */ + void update([const, array, size_is(aLen)] in octet aData, in unsigned long aLen); + + /** + * Calculates and updates a new hash based on a given data stream. + * + * @param aStream an input stream to read from. + * + * @param aLen how much to read from the given |aStream|. Passing + * PR_UINT32_MAX indicates that all data available will be used + * to update the hash. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * @throws NS_ERROR_NOT_AVAILABLE if the requested amount of + * data to be calculated into the hash is not available. + * + */ + void updateFromStream(in nsIInputStream aStream, in unsigned long aLen); + + /** + * Completes this HMAC object and produces the actual HMAC diegest data. + * + * @param aASCII if true then the returned value is a base-64 + * encoded string. if false, then the returned value is + * binary data. + * + * @return a hash of the data that was read by this object. This can + * be either binary data or base 64 encoded. + * + * @throws NS_ERROR_NOT_INITIALIZED if |init| has not been + * called. + * + * NOTE: This method may be called any time after |init| + * is called. This call resets the object to its + * pre-init state. + */ + ACString finish(in PRBool aASCII); + + /** + * Reinitialize HMAC context to be reused with the same + * settings (the key and hash algorithm) but on different + * set of data. + */ + void reset(); +}; diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 9646da436f4..e2c7e6fa3da 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -846,6 +846,11 @@ */ #define NS_CRYPTO_HASH_CONTRACTID "@mozilla.org/security/hash;1" +/** + * Must implement nsICryptoHMAC. + */ +#define NS_CRYPTO_HMAC_CONTRACTID "@mozilla.org/security/hmac;1" + /****************************************************************************** * Categories */ diff --git a/security/manager/ssl/src/nsNSSComponent.cpp b/security/manager/ssl/src/nsNSSComponent.cpp index aa3eb3214ff..27cb859e0e7 100644 --- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -2492,13 +2492,13 @@ nsCryptoHash::UpdateFromStream(nsIInputStream *data, PRUint32 len) return NS_ERROR_NOT_AVAILABLE; char buffer[NS_CRYPTO_HASH_BUFFER_SIZE]; - PRUint32 read; + PRUint32 read, readLimit; while(NS_SUCCEEDED(rv) && len>0) { - read = PR_MIN(NS_CRYPTO_HASH_BUFFER_SIZE, len); + readLimit = PR_MIN(NS_CRYPTO_HASH_BUFFER_SIZE, len); - rv = data->Read(buffer, read, &read); + rv = data->Read(buffer, readLimit, &read); if (NS_SUCCEEDED(rv)) rv = Update((const PRUint8*)buffer, read); @@ -2527,6 +2527,8 @@ nsCryptoHash::Finish(PRBool ascii, nsACString & _retval) if (ascii) { char *asciiData = BTOA_DataToAscii(buffer, hashLen); + NS_ENSURE_TRUE(asciiData, NS_ERROR_OUT_OF_MEMORY); + _retval.Assign(asciiData); PORT_Free(asciiData); } @@ -2538,6 +2540,178 @@ nsCryptoHash::Finish(PRBool ascii, nsACString & _retval) return NS_OK; } +//--------------------------------------------- +// Implementing nsICryptoHMAC +//--------------------------------------------- + +NS_IMPL_ISUPPORTS1(nsCryptoHMAC, nsICryptoHMAC) + +nsCryptoHMAC::nsCryptoHMAC() +{ + mHMACContext = nsnull; +} + +nsCryptoHMAC::~nsCryptoHMAC() +{ + if (mHMACContext) + PK11_DestroyContext(mHMACContext, PR_TRUE); +} + +/* void init (in unsigned long aAlgorithm, in octet aKeyData, in long aKeyLength); */ +NS_IMETHODIMP nsCryptoHMAC::Init(PRUint32 aAlgorithm, const PRUint8 *aKeyData, PRUint32 aKeyLen) +{ + if (mHMACContext) + { + PK11_DestroyContext(mHMACContext, PR_TRUE); + mHMACContext = nsnull; + } + + CK_MECHANISM_TYPE HMACMechType; + switch (aAlgorithm) + { + case nsCryptoHMAC::MD2: + HMACMechType = CKM_MD2_HMAC; break; + case nsCryptoHMAC::MD5: + HMACMechType = CKM_MD5_HMAC; break; + case nsCryptoHMAC::SHA1: + HMACMechType = CKM_SHA_1_HMAC; break; + case nsCryptoHMAC::SHA256: + HMACMechType = CKM_SHA256_HMAC; break; + case nsCryptoHMAC::SHA384: + HMACMechType = CKM_SHA384_HMAC; break; + case nsCryptoHMAC::SHA512: + HMACMechType = CKM_SHA512_HMAC; break; + default: + return NS_ERROR_INVALID_ARG; + } + + PK11SlotInfo *slot = PK11_GetBestSlot(HMACMechType, nsnull); + NS_ENSURE_TRUE(slot, NS_ERROR_FAILURE); + + SECItem rawData; + rawData.data = const_cast(aKeyData); + rawData.len = aKeyLen; + + PK11SymKey* key = PK11_ImportSymKey( + slot, HMACMechType, PK11_OriginUnwrap, CKA_SIGN, &rawData, nsnull); + PK11_FreeSlot(slot); + + NS_ENSURE_TRUE(key, NS_ERROR_FAILURE); + + rawData.data = 0; + rawData.len = 0; + mHMACContext = PK11_CreateContextBySymKey( + HMACMechType, CKA_SIGN, key, &rawData); + PK11_FreeSymKey(key); + + NS_ENSURE_TRUE(mHMACContext, NS_ERROR_FAILURE); + + SECStatus ss = PK11_DigestBegin(mHMACContext); + NS_ENSURE_TRUE(ss == SECSuccess, NS_ERROR_FAILURE); + + return NS_OK; +} + +/* void update ([array, size_is (aLen), const] in octet aData, in unsigned long aLen); */ +NS_IMETHODIMP nsCryptoHMAC::Update(const PRUint8 *aData, PRUint32 aLen) +{ + if (!mHMACContext) + return NS_ERROR_NOT_INITIALIZED; + + if (!aData) + return NS_ERROR_INVALID_ARG; + + SECStatus ss = PK11_DigestOp(mHMACContext, aData, aLen); + NS_ENSURE_TRUE(ss == SECSuccess, NS_ERROR_FAILURE); + + return NS_OK; +} + +/* void updateFromStream (in nsIInputStream aStream, in unsigned long aLen); */ +NS_IMETHODIMP nsCryptoHMAC::UpdateFromStream(nsIInputStream *aStream, PRUint32 aLen) +{ + if (!mHMACContext) + return NS_ERROR_NOT_INITIALIZED; + + if (!aStream) + return NS_ERROR_INVALID_ARG; + + PRUint32 n; + nsresult rv = aStream->Available(&n); + if (NS_FAILED(rv)) + return rv; + + // if the user has passed PR_UINT32_MAX, then read + // everything in the stream + + if (aLen == PR_UINT32_MAX) + aLen = 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 < aLen) + return NS_ERROR_NOT_AVAILABLE; + + char buffer[NS_CRYPTO_HASH_BUFFER_SIZE]; + PRUint32 read, readLimit; + + while(NS_SUCCEEDED(rv) && aLen > 0) + { + readLimit = PR_MIN(NS_CRYPTO_HASH_BUFFER_SIZE, aLen); + + rv = aStream->Read(buffer, readLimit, &read); + if (read == 0) + return NS_BASE_STREAM_CLOSED; + + if (NS_SUCCEEDED(rv)) + rv = Update((const PRUint8*)buffer, read); + + aLen -= read; + } + + return rv; +} + +/* ACString finish (in PRBool aASCII); */ +NS_IMETHODIMP nsCryptoHMAC::Finish(PRBool aASCII, nsACString & _retval) +{ + if (!mHMACContext) + return NS_ERROR_NOT_INITIALIZED; + + PRUint32 hashLen = 0; + unsigned char buffer[HASH_LENGTH_MAX]; + unsigned char* pbuffer = buffer; + + PK11_DigestFinal(mHMACContext, pbuffer, &hashLen, HASH_LENGTH_MAX); + if (aASCII) + { + char *asciiData = BTOA_DataToAscii(buffer, hashLen); + NS_ENSURE_TRUE(asciiData, NS_ERROR_OUT_OF_MEMORY); + + _retval.Assign(asciiData); + PORT_Free(asciiData); + } + else + { + _retval.Assign((const char*)buffer, hashLen); + } + + return NS_OK; +} + +/* void reset (); */ +NS_IMETHODIMP nsCryptoHMAC::Reset() +{ + SECStatus ss = PK11_DigestBegin(mHMACContext); + NS_ENSURE_TRUE(ss == SECSuccess, NS_ERROR_FAILURE); + + return NS_OK; +} NS_IMPL_ISUPPORTS1(PipUIContext, nsIInterfaceRequestor) diff --git a/security/manager/ssl/src/nsNSSComponent.h b/security/manager/ssl/src/nsNSSComponent.h index fd9fdf4885a..e3d1f66ba17 100644 --- a/security/manager/ssl/src/nsNSSComponent.h +++ b/security/manager/ssl/src/nsNSSComponent.h @@ -64,6 +64,7 @@ #include "nsHashtable.h" #include "prlock.h" #include "nsICryptoHash.h" +#include "nsICryptoHMAC.h" #include "hasht.h" #include "nsNSSCallbacks.h" @@ -86,9 +87,12 @@ #define NS_PSMCONTENTLISTEN_CID {0xc94f4a30, 0x64d7, 0x11d4, {0x99, 0x60, 0x00, 0xb0, 0xd0, 0x23, 0x54, 0xa0}} #define NS_PSMCONTENTLISTEN_CONTRACTID "@mozilla.org/security/psmdownload;1" -#define NS_CRYPTO_HASH_CLASSNAME "Mozilla Cryto Hash Function Component" +#define NS_CRYPTO_HASH_CLASSNAME "Mozilla Crypto Hash Function Component" #define NS_CRYPTO_HASH_CID {0x36a1d3b3, 0xd886, 0x4317, {0x96, 0xff, 0x87, 0xb0, 0x00, 0x5c, 0xfe, 0xf7}} +#define NS_CRYPTO_HMAC_CLASSNAME "Mozilla Crypto HMAC Function Component" +#define NS_CRYPTO_HMAC_CID {0xa496d0a2, 0xdff7, 0x4e23, {0xbd, 0x65, 0x1c, 0xa7, 0x42, 0xfa, 0x17, 0x8a}} + //-------------------------------------------- // Now we need a content listener to register //-------------------------------------------- @@ -185,6 +189,20 @@ private: HASHContext* mHashContext; }; +class nsCryptoHMAC : public nsICryptoHMAC +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICRYPTOHMAC + + nsCryptoHMAC(); + +private: + ~nsCryptoHMAC(); + + PK11Context* mHMACContext; +}; + struct PRLock; class nsNSSShutDownList; class nsSSLThread; diff --git a/security/manager/ssl/src/nsNSSModule.cpp b/security/manager/ssl/src/nsNSSModule.cpp index 4d438b96cca..5d0032f0ba9 100644 --- a/security/manager/ssl/src/nsNSSModule.cpp +++ b/security/manager/ssl/src/nsNSSModule.cpp @@ -194,6 +194,7 @@ NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsCRLManager) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsCipherInfoService) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR_INIT(PR_FALSE, nsNTLMAuthModule, InitTest) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsCryptoHash) +NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsCryptoHMAC) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsStreamCipher) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsKeyObject) NS_NSS_GENERIC_FACTORY_CONSTRUCTOR(PR_FALSE, nsKeyObjectFactory) @@ -396,6 +397,13 @@ static const nsModuleComponentInfo components[] = nsCryptoHashConstructor }, + { + NS_CRYPTO_HMAC_CLASSNAME, + NS_CRYPTO_HMAC_CID, + NS_CRYPTO_HMAC_CONTRACTID, + nsCryptoHMACConstructor + }, + { NS_CERT_PICKER_CLASSNAME, NS_CERT_PICKER_CID, diff --git a/security/manager/ssl/tests/test_hash_algorithms.js b/security/manager/ssl/tests/test_hash_algorithms.js index be1c61bc69d..89854eea9ee 100644 --- a/security/manager/ssl/tests/test_hash_algorithms.js +++ b/security/manager/ssl/tests/test_hash_algorithms.js @@ -79,11 +79,32 @@ function doHash(algo, value, cmp) { } } +function doHashStream(algo, value, cmp) { + var converter = new ScriptableUnicodeConverter(); + var hash = new CryptoHash(algo); + + converter.charset = 'utf8'; + var stream = converter.convertToInputStream(value); + hash.updateFromStream(stream, stream.available()); + hash = hexdigest(hash.finish(false)); + if (cmp != hash) { + do_throw("Hash mismatch!\n" + + " Expected: " + cmp + "\n" + + " Actual: " + hash + "\n" + + " Algo: " + algo); + } +} + function run_test() { for (algo in hashes) { hashes[algo].forEach( function(e, i) { doHash(algo, messages[i], e); + + if (messages[i].length) { + // this test doesn't work for empty string/stream + doHashStream(algo, messages[i], e); + } } ); } diff --git a/security/manager/ssl/tests/test_hmac.js b/security/manager/ssl/tests/test_hmac.js new file mode 100644 index 00000000000..920b9c2d536 --- /dev/null +++ b/security/manager/ssl/tests/test_hmac.js @@ -0,0 +1,46 @@ +var ScriptableUnicodeConverter = + Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter", + "nsIScriptableUnicodeConverter"); + +function getHMAC(data, key) +{ + var converter = new ScriptableUnicodeConverter(); + converter.charset = 'utf8'; + var keyarray = converter.convertToByteArray(key, {}); + var dataarray = converter.convertToByteArray(data, {}); + + var cryptoHMAC = Components.classes["@mozilla.org/security/hmac;1"] + .createInstance(Components.interfaces.nsICryptoHMAC); + + cryptoHMAC.init(Components.interfaces.nsICryptoHMAC.SHA1, keyarray, keyarray.length); + cryptoHMAC.update(dataarray, dataarray.length); + var digest1 = cryptoHMAC.finish(true); + + cryptoHMAC.reset(); + cryptoHMAC.update(dataarray, dataarray.length); + var digest2 = cryptoHMAC.finish(true); + + do_check_eq(digest1, digest2); + + return digest1; +} + +function run_test() { + const key1 = 'MyKey_ABCDEFGHIJKLMN'; + const key2 = 'MyKey_01234567890123'; + + const dataA = "Secret message"; + const dataB = "Secres message"; + + var diegest1a = getHMAC(key1, dataA); + var diegest2 = getHMAC(key2, dataA); + var diegest1b = getHMAC(key1, dataA); + + do_check_eq(diegest1a, diegest1b); + do_check_neq(diegest1a, diegest2); + + var diegest1 = getHMAC(key1, dataA); + diegest2 = getHMAC(key1, dataB); + + do_check_neq(diegest1, diegest2); +}