1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  *
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "nsCryptoHash.h"
8 
9 #include <algorithm>
10 
11 #include "mozilla/ArrayUtils.h"
12 #include "mozilla/Base64.h"
13 #include "mozilla/Casting.h"
14 #include "nsDependentString.h"
15 #include "nsIInputStream.h"
16 #include "nsIKeyModule.h"
17 #include "nsString.h"
18 #include "pk11pub.h"
19 #include "sechash.h"
20 
21 using namespace mozilla;
22 
23 namespace {
24 
25 static const uint64_t STREAM_BUFFER_SIZE = 4096;
26 
27 }  // namespace
28 
29 //---------------------------------------------
30 // Implementing nsICryptoHash
31 //---------------------------------------------
32 
nsCryptoHash()33 nsCryptoHash::nsCryptoHash() : mHashContext(nullptr), mInitialized(false) {}
34 
NS_IMPL_ISUPPORTS(nsCryptoHash,nsICryptoHash)35 NS_IMPL_ISUPPORTS(nsCryptoHash, nsICryptoHash)
36 
37 NS_IMETHODIMP
38 nsCryptoHash::Init(uint32_t algorithm) {
39   HASH_HashType hashType;
40   switch (algorithm) {
41     case nsICryptoHash::MD5:
42       hashType = HASH_AlgMD5;
43       break;
44     case nsICryptoHash::SHA1:
45       hashType = HASH_AlgSHA1;
46       break;
47     case nsICryptoHash::SHA256:
48       hashType = HASH_AlgSHA256;
49       break;
50     case nsICryptoHash::SHA384:
51       hashType = HASH_AlgSHA384;
52       break;
53     case nsICryptoHash::SHA512:
54       hashType = HASH_AlgSHA512;
55       break;
56     default:
57       return NS_ERROR_INVALID_ARG;
58   }
59 
60   if (mHashContext) {
61     if (!mInitialized && HASH_GetType(mHashContext.get()) == hashType) {
62       mInitialized = true;
63       HASH_Begin(mHashContext.get());
64       return NS_OK;
65     }
66 
67     // Destroy current hash context if the type was different
68     // or Finish method wasn't called.
69     mHashContext = nullptr;
70     mInitialized = false;
71   }
72 
73   mHashContext.reset(HASH_Create(hashType));
74   if (!mHashContext) {
75     return NS_ERROR_INVALID_ARG;
76   }
77 
78   HASH_Begin(mHashContext.get());
79   mInitialized = true;
80   return NS_OK;
81 }
82 
83 NS_IMETHODIMP
InitWithString(const nsACString & aAlgorithm)84 nsCryptoHash::InitWithString(const nsACString& aAlgorithm) {
85   if (aAlgorithm.LowerCaseEqualsLiteral("md5")) return Init(nsICryptoHash::MD5);
86 
87   if (aAlgorithm.LowerCaseEqualsLiteral("sha1"))
88     return Init(nsICryptoHash::SHA1);
89 
90   if (aAlgorithm.LowerCaseEqualsLiteral("sha256"))
91     return Init(nsICryptoHash::SHA256);
92 
93   if (aAlgorithm.LowerCaseEqualsLiteral("sha384"))
94     return Init(nsICryptoHash::SHA384);
95 
96   if (aAlgorithm.LowerCaseEqualsLiteral("sha512"))
97     return Init(nsICryptoHash::SHA512);
98 
99   return NS_ERROR_INVALID_ARG;
100 }
101 
102 NS_IMETHODIMP
Update(const uint8_t * data,uint32_t len)103 nsCryptoHash::Update(const uint8_t* data, uint32_t len) {
104   if (!mInitialized) {
105     return NS_ERROR_NOT_INITIALIZED;
106   }
107 
108   HASH_Update(mHashContext.get(), data, len);
109   return NS_OK;
110 }
111 
112 NS_IMETHODIMP
UpdateFromStream(nsIInputStream * data,uint32_t aLen)113 nsCryptoHash::UpdateFromStream(nsIInputStream* data, uint32_t aLen) {
114   if (!mInitialized) return NS_ERROR_NOT_INITIALIZED;
115 
116   if (!data) return NS_ERROR_INVALID_ARG;
117 
118   uint64_t n;
119   nsresult rv = data->Available(&n);
120   if (NS_FAILED(rv)) return rv;
121 
122   // if the user has passed UINT32_MAX, then read
123   // everything in the stream
124 
125   uint64_t len = aLen;
126   if (aLen == UINT32_MAX) len = n;
127 
128   // So, if the stream has NO data available for the hash,
129   // or if the data available is less then what the caller
130   // requested, we can not fulfill the hash update.  In this
131   // case, just return NS_ERROR_NOT_AVAILABLE indicating
132   // that there is not enough data in the stream to satisify
133   // the request.
134 
135   if (n == 0 || n < len) {
136     return NS_ERROR_NOT_AVAILABLE;
137   }
138 
139   char buffer[STREAM_BUFFER_SIZE];
140   while (len > 0) {
141     uint64_t readLimit = std::min<uint64_t>(STREAM_BUFFER_SIZE, len);
142     uint32_t read;
143     rv = data->Read(buffer, AssertedCast<uint32_t>(readLimit), &read);
144     if (NS_FAILED(rv)) {
145       return rv;
146     }
147 
148     rv = Update(BitwiseCast<uint8_t*>(buffer), read);
149     if (NS_FAILED(rv)) {
150       return rv;
151     }
152 
153     len -= read;
154   }
155 
156   return NS_OK;
157 }
158 
159 NS_IMETHODIMP
Finish(bool ascii,nsACString & _retval)160 nsCryptoHash::Finish(bool ascii, nsACString& _retval) {
161   if (!mInitialized) {
162     return NS_ERROR_NOT_INITIALIZED;
163   }
164 
165   uint32_t hashLen = 0;
166   unsigned char buffer[HASH_LENGTH_MAX];
167   HASH_End(mHashContext.get(), buffer, &hashLen, HASH_LENGTH_MAX);
168 
169   mInitialized = false;
170 
171   if (ascii) {
172     nsDependentCSubstring dataStr(BitwiseCast<char*>(buffer), hashLen);
173     return Base64Encode(dataStr, _retval);
174   }
175 
176   _retval.Assign(BitwiseCast<char*>(buffer), hashLen);
177   return NS_OK;
178 }
179 
180 //---------------------------------------------
181 // Implementing nsICryptoHMAC
182 //---------------------------------------------
183 
NS_IMPL_ISUPPORTS(nsCryptoHMAC,nsICryptoHMAC)184 NS_IMPL_ISUPPORTS(nsCryptoHMAC, nsICryptoHMAC)
185 
186 nsCryptoHMAC::nsCryptoHMAC() : mHMACContext(nullptr) {}
187 
188 NS_IMETHODIMP
Init(uint32_t aAlgorithm,nsIKeyObject * aKeyObject)189 nsCryptoHMAC::Init(uint32_t aAlgorithm, nsIKeyObject* aKeyObject) {
190   if (mHMACContext) {
191     mHMACContext = nullptr;
192   }
193 
194   CK_MECHANISM_TYPE mechType;
195   switch (aAlgorithm) {
196     case nsICryptoHMAC::MD5:
197       mechType = CKM_MD5_HMAC;
198       break;
199     case nsICryptoHMAC::SHA1:
200       mechType = CKM_SHA_1_HMAC;
201       break;
202     case nsICryptoHMAC::SHA256:
203       mechType = CKM_SHA256_HMAC;
204       break;
205     case nsICryptoHMAC::SHA384:
206       mechType = CKM_SHA384_HMAC;
207       break;
208     case nsICryptoHMAC::SHA512:
209       mechType = CKM_SHA512_HMAC;
210       break;
211     default:
212       return NS_ERROR_INVALID_ARG;
213   }
214 
215   NS_ENSURE_ARG_POINTER(aKeyObject);
216 
217   nsresult rv;
218 
219   int16_t keyType;
220   rv = aKeyObject->GetType(&keyType);
221   NS_ENSURE_SUCCESS(rv, rv);
222 
223   NS_ENSURE_TRUE(keyType == nsIKeyObject::SYM_KEY, NS_ERROR_INVALID_ARG);
224 
225   PK11SymKey* key;
226   // GetKeyObj doesn't addref the key
227   rv = aKeyObject->GetKeyObj(&key);
228   NS_ENSURE_SUCCESS(rv, rv);
229 
230   SECItem rawData;
231   rawData.data = 0;
232   rawData.len = 0;
233   mHMACContext.reset(
234       PK11_CreateContextBySymKey(mechType, CKA_SIGN, key, &rawData));
235   NS_ENSURE_TRUE(mHMACContext, NS_ERROR_FAILURE);
236 
237   if (PK11_DigestBegin(mHMACContext.get()) != SECSuccess) {
238     return NS_ERROR_FAILURE;
239   }
240 
241   return NS_OK;
242 }
243 
244 NS_IMETHODIMP
Update(const uint8_t * aData,uint32_t aLen)245 nsCryptoHMAC::Update(const uint8_t* aData, uint32_t aLen) {
246   if (!mHMACContext) return NS_ERROR_NOT_INITIALIZED;
247 
248   if (!aData) return NS_ERROR_INVALID_ARG;
249 
250   if (PK11_DigestOp(mHMACContext.get(), aData, aLen) != SECSuccess) {
251     return NS_ERROR_FAILURE;
252   }
253 
254   return NS_OK;
255 }
256 
257 NS_IMETHODIMP
UpdateFromStream(nsIInputStream * aStream,uint32_t aLen)258 nsCryptoHMAC::UpdateFromStream(nsIInputStream* aStream, uint32_t aLen) {
259   if (!mHMACContext) return NS_ERROR_NOT_INITIALIZED;
260 
261   if (!aStream) return NS_ERROR_INVALID_ARG;
262 
263   uint64_t n;
264   nsresult rv = aStream->Available(&n);
265   if (NS_FAILED(rv)) return rv;
266 
267   // if the user has passed UINT32_MAX, then read
268   // everything in the stream
269 
270   uint64_t len = aLen;
271   if (aLen == UINT32_MAX) len = n;
272 
273   // So, if the stream has NO data available for the hash,
274   // or if the data available is less then what the caller
275   // requested, we can not fulfill the HMAC update.  In this
276   // case, just return NS_ERROR_NOT_AVAILABLE indicating
277   // that there is not enough data in the stream to satisify
278   // the request.
279 
280   if (n == 0 || n < len) return NS_ERROR_NOT_AVAILABLE;
281 
282   char buffer[STREAM_BUFFER_SIZE];
283   while (len > 0) {
284     uint64_t readLimit = std::min<uint64_t>(STREAM_BUFFER_SIZE, len);
285     uint32_t read;
286     rv = aStream->Read(buffer, AssertedCast<uint32_t>(readLimit), &read);
287     if (NS_FAILED(rv)) {
288       return rv;
289     }
290 
291     if (read == 0) {
292       return NS_BASE_STREAM_CLOSED;
293     }
294 
295     rv = Update(BitwiseCast<uint8_t*>(buffer), read);
296     if (NS_FAILED(rv)) {
297       return rv;
298     }
299 
300     len -= read;
301   }
302 
303   return NS_OK;
304 }
305 
306 NS_IMETHODIMP
Finish(bool aASCII,nsACString & _retval)307 nsCryptoHMAC::Finish(bool aASCII, nsACString& _retval) {
308   if (!mHMACContext) return NS_ERROR_NOT_INITIALIZED;
309 
310   uint32_t hashLen = 0;
311   unsigned char buffer[HASH_LENGTH_MAX];
312   SECStatus srv =
313       PK11_DigestFinal(mHMACContext.get(), buffer, &hashLen, HASH_LENGTH_MAX);
314   if (srv != SECSuccess) {
315     return NS_ERROR_FAILURE;
316   }
317 
318   if (aASCII) {
319     nsDependentCSubstring dataStr(BitwiseCast<char*>(buffer), hashLen);
320     return Base64Encode(dataStr, _retval);
321   }
322 
323   _retval.Assign(BitwiseCast<char*>(buffer), hashLen);
324   return NS_OK;
325 }
326 
327 NS_IMETHODIMP
Reset()328 nsCryptoHMAC::Reset() {
329   if (PK11_DigestBegin(mHMACContext.get()) != SECSuccess) {
330     return NS_ERROR_FAILURE;
331   }
332 
333   return NS_OK;
334 }
335