mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-02 05:17:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
19
services/crypto/component/moz.build
Normal file
19
services/crypto/component/moz.build
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
XPIDL_SOURCES += [
|
||||
'nsISyncJPAKE.idl',
|
||||
]
|
||||
|
||||
XPIDL_MODULE = 'services-crypto-component'
|
||||
|
||||
SOURCES += [
|
||||
'nsSyncJPAKE.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
103
services/crypto/component/nsISyncJPAKE.idl
Normal file
103
services/crypto/component/nsISyncJPAKE.idl
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/* 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 "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(5ab02a98-5122-4b90-93cd-f259c4b42e3a)]
|
||||
interface nsISyncJPAKE : nsISupports
|
||||
{
|
||||
/**
|
||||
* Perform first round of the JPAKE exchange.
|
||||
*
|
||||
* @param aSignerID
|
||||
* String identifying the signer.
|
||||
* @param aGX1
|
||||
* Schnorr signature value g^x1, in hex representation.
|
||||
* @param aGV1
|
||||
* Schnorr signature value g^v1 (v1 is a random value), in hex
|
||||
* representation.
|
||||
* @param aR1
|
||||
* Schnorr signature value r1 = v1 - x1 * h, in hex representation.
|
||||
* @param aGX2
|
||||
* Schnorr signature value g^x2, in hex representation.
|
||||
* @param aGV2
|
||||
* Schnorr signature value g^v2 (v2 is a random value), in hex
|
||||
* representation.
|
||||
* @param aR2
|
||||
* Schnorr signature value r2 = v2 - x2 * h, in hex representation.
|
||||
*/
|
||||
void round1(in ACString aSignerID,
|
||||
out ACString aGX1,
|
||||
out ACString aGV1,
|
||||
out ACString aR1,
|
||||
out ACString aGX2,
|
||||
out ACString aGV2,
|
||||
out ACString aR2);
|
||||
|
||||
/**
|
||||
* Perform second round of the JPAKE exchange.
|
||||
*
|
||||
* @param aPeerID
|
||||
* String identifying the peer.
|
||||
* @param aPIN
|
||||
* String containing the weak secret (PIN).
|
||||
* @param aGX3
|
||||
* Schnorr signature value g^x3, in hex representation.
|
||||
* @param aGV3
|
||||
* Schnorr signature value g^v3 (v3 is a random value), in hex
|
||||
* representation.
|
||||
* @param aR3
|
||||
* Schnorr signature value r3 = v3 - x3 * h, in hex representation.
|
||||
* @param aGX4
|
||||
* Schnorr signature value g^x4, in hex representation.
|
||||
* @param aGV4
|
||||
* Schnorr signature value g^v4 (v4 is a random value), in hex
|
||||
* representation.
|
||||
* @param aR4
|
||||
* Schnorr signature value r4 = v4 - x4 * h, in hex representation.
|
||||
* @param aA
|
||||
* Schnorr signature value A, in hex representation.
|
||||
* @param aGVA
|
||||
* Schnorr signature value g^va (va is a random value), in hex
|
||||
* representation.
|
||||
* @param aRA
|
||||
* Schnorr signature value ra = va - xa * h, in hex representation.
|
||||
*/
|
||||
void round2(in ACString aPeerID,
|
||||
in ACString aPIN,
|
||||
in ACString aGX3,
|
||||
in ACString aGV3,
|
||||
in ACString aR3,
|
||||
in ACString aGX4,
|
||||
in ACString aGV4,
|
||||
in ACString aR4,
|
||||
out ACString aA,
|
||||
out ACString aGVA,
|
||||
out ACString aRA);
|
||||
|
||||
/**
|
||||
* Perform the final step of the JPAKE exchange. This will compute
|
||||
* the key and expand the key to two keys, an AES256 encryption key
|
||||
* and a 256 bit HMAC key. It returns a key confirmation value
|
||||
* (SHA256d of the key) and the encryption and HMAC keys.
|
||||
*
|
||||
* @param aB
|
||||
* Schnorr signature value B, in hex representation.
|
||||
* @param aGVB
|
||||
* Schnorr signature value g^vb (vb is a random value), in hex
|
||||
* representation.
|
||||
* @param aRB
|
||||
* Schnorr signature value rb = vb - xb * h, in hex representation.
|
||||
* @param aAES256Key
|
||||
* The AES 256 encryption key, in base64 representation.
|
||||
* @param aHMAC256Key
|
||||
* The 256 bit HMAC key, in base64 representation.
|
||||
*/
|
||||
void final(in ACString aB,
|
||||
in ACString aGVB,
|
||||
in ACString aRB,
|
||||
in ACString aHkdfInfo,
|
||||
out ACString aAES256Key,
|
||||
out ACString aHMAC256Key);
|
||||
};
|
||||
484
services/crypto/component/nsSyncJPAKE.cpp
Normal file
484
services/crypto/component/nsSyncJPAKE.cpp
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
/* 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 "nsSyncJPAKE.h"
|
||||
|
||||
#include "base64.h"
|
||||
#include "keyhi.h"
|
||||
#include "mozilla/ModuleUtils.h"
|
||||
#include "mozilla/Move.h"
|
||||
#include "nsDebug.h"
|
||||
#include "nsError.h"
|
||||
#include "nsString.h"
|
||||
#include "nscore.h"
|
||||
#include "pk11pub.h"
|
||||
#include "pkcs11.h"
|
||||
#include "secerr.h"
|
||||
#include "secmodt.h"
|
||||
#include "secport.h"
|
||||
|
||||
using mozilla::fallible;
|
||||
|
||||
static bool
|
||||
hex_from_2char(const unsigned char *c2, unsigned char *byteval)
|
||||
{
|
||||
int i;
|
||||
unsigned char offset;
|
||||
*byteval = 0;
|
||||
for (i=0; i<2; i++) {
|
||||
if (c2[i] >= '0' && c2[i] <= '9') {
|
||||
offset = c2[i] - '0';
|
||||
*byteval |= offset << 4*(1-i);
|
||||
} else if (c2[i] >= 'a' && c2[i] <= 'f') {
|
||||
offset = c2[i] - 'a';
|
||||
*byteval |= (offset + 10) << 4*(1-i);
|
||||
} else if (c2[i] >= 'A' && c2[i] <= 'F') {
|
||||
offset = c2[i] - 'A';
|
||||
*byteval |= (offset + 10) << 4*(1-i);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
fromHex(const char * str, unsigned char * p, size_t sLen)
|
||||
{
|
||||
size_t i;
|
||||
if (sLen & 1)
|
||||
return false;
|
||||
|
||||
for (i = 0; i < sLen / 2; ++i) {
|
||||
if (!hex_from_2char((const unsigned char *) str + (2*i),
|
||||
(unsigned char *) p + i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
fromHexString(const nsACString & str, unsigned char * p, size_t pMaxLen)
|
||||
{
|
||||
char * strData = (char *) str.Data();
|
||||
unsigned len = str.Length();
|
||||
NS_ENSURE_ARG(len / 2 <= pMaxLen);
|
||||
if (!fromHex(strData, p, len)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static bool
|
||||
toHexString(const unsigned char * str, unsigned len, nsACString & out)
|
||||
{
|
||||
static const char digits[] = "0123456789ABCDEF";
|
||||
if (!out.SetCapacity(2 * len, fallible))
|
||||
return false;
|
||||
out.SetLength(0);
|
||||
for (unsigned i = 0; i < len; ++i) {
|
||||
out.Append(digits[str[i] >> 4]);
|
||||
out.Append(digits[str[i] & 0x0f]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
mapErrno()
|
||||
{
|
||||
int err = PORT_GetError();
|
||||
switch (err) {
|
||||
case SEC_ERROR_NO_MEMORY: return NS_ERROR_OUT_OF_MEMORY;
|
||||
default: return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
}
|
||||
|
||||
#define NUM_ELEM(x) (sizeof(x) / sizeof (x)[0])
|
||||
|
||||
static const char p[] =
|
||||
"90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C"
|
||||
"7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F"
|
||||
"009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1"
|
||||
"29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B"
|
||||
"6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394"
|
||||
"87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0"
|
||||
"E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E"
|
||||
"BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D"
|
||||
"AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F"
|
||||
"B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D"
|
||||
"597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E"
|
||||
"C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73";
|
||||
static const char q[] =
|
||||
"CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D";
|
||||
static const char g[] =
|
||||
"5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37"
|
||||
"F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB"
|
||||
"805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1"
|
||||
"F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8"
|
||||
"A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17"
|
||||
"A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C"
|
||||
"4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3"
|
||||
"9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B"
|
||||
"9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8"
|
||||
"E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828"
|
||||
"E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33"
|
||||
"787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B";
|
||||
|
||||
NS_IMETHODIMP nsSyncJPAKE::Round1(const nsACString & aSignerID,
|
||||
nsACString & aGX1,
|
||||
nsACString & aGV1,
|
||||
nsACString & aR1,
|
||||
nsACString & aGX2,
|
||||
nsACString & aGV2,
|
||||
nsACString & aR2)
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(round == JPAKENotStarted);
|
||||
NS_ENSURE_STATE(key == nullptr);
|
||||
|
||||
static CK_MECHANISM_TYPE mechanisms[] = {
|
||||
CKM_NSS_JPAKE_ROUND1_SHA256,
|
||||
CKM_NSS_JPAKE_ROUND2_SHA256,
|
||||
CKM_NSS_JPAKE_FINAL_SHA256
|
||||
};
|
||||
|
||||
UniquePK11SlotInfo slot(PK11_GetBestSlotMultiple(mechanisms,
|
||||
NUM_ELEM(mechanisms),
|
||||
nullptr));
|
||||
NS_ENSURE_STATE(slot != nullptr);
|
||||
|
||||
CK_BYTE pBuf[(NUM_ELEM(p) - 1) / 2];
|
||||
CK_BYTE qBuf[(NUM_ELEM(q) - 1) / 2];
|
||||
CK_BYTE gBuf[(NUM_ELEM(g) - 1) / 2];
|
||||
|
||||
CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND1;
|
||||
NS_ENSURE_STATE(fromHex(p, pBuf, (NUM_ELEM(p) - 1)));
|
||||
NS_ENSURE_STATE(fromHex(q, qBuf, (NUM_ELEM(q) - 1)));
|
||||
NS_ENSURE_STATE(fromHex(g, gBuf, (NUM_ELEM(g) - 1)));
|
||||
CK_ATTRIBUTE keyTemplate[] = {
|
||||
{ CKA_NSS_JPAKE_SIGNERID, (CK_BYTE *) aSignerID.Data(),
|
||||
aSignerID.Length() },
|
||||
{ CKA_KEY_TYPE, &keyType, sizeof keyType },
|
||||
{ CKA_PRIME, pBuf, sizeof pBuf },
|
||||
{ CKA_SUBPRIME, qBuf, sizeof qBuf },
|
||||
{ CKA_BASE, gBuf, sizeof gBuf }
|
||||
};
|
||||
|
||||
CK_BYTE gx1Buf[NUM_ELEM(p) / 2];
|
||||
CK_BYTE gv1Buf[NUM_ELEM(p) / 2];
|
||||
CK_BYTE r1Buf [NUM_ELEM(p) / 2];
|
||||
CK_BYTE gx2Buf[NUM_ELEM(p) / 2];
|
||||
CK_BYTE gv2Buf[NUM_ELEM(p) / 2];
|
||||
CK_BYTE r2Buf [NUM_ELEM(p) / 2];
|
||||
CK_NSS_JPAKERound1Params rp = {
|
||||
{ gx1Buf, sizeof gx1Buf, gv1Buf, sizeof gv1Buf, r1Buf, sizeof r1Buf },
|
||||
{ gx2Buf, sizeof gx2Buf, gv2Buf, sizeof gv2Buf, r2Buf, sizeof r2Buf }
|
||||
};
|
||||
SECItem paramsItem;
|
||||
paramsItem.data = (unsigned char *) &rp;
|
||||
paramsItem.len = sizeof rp;
|
||||
key = UniquePK11SymKey(
|
||||
PK11_KeyGenWithTemplate(slot.get(), CKM_NSS_JPAKE_ROUND1_SHA256,
|
||||
CKM_NSS_JPAKE_ROUND1_SHA256, ¶msItem,
|
||||
keyTemplate, NUM_ELEM(keyTemplate), nullptr));
|
||||
nsresult rv = key != nullptr
|
||||
? NS_OK
|
||||
: mapErrno();
|
||||
if (rv == NS_OK) {
|
||||
NS_ENSURE_TRUE(toHexString(rp.gx1.pGX, rp.gx1.ulGXLen, aGX1) &&
|
||||
toHexString(rp.gx1.pGV, rp.gx1.ulGVLen, aGV1) &&
|
||||
toHexString(rp.gx1.pR, rp.gx1.ulRLen, aR1) &&
|
||||
toHexString(rp.gx2.pGX, rp.gx2.ulGXLen, aGX2) &&
|
||||
toHexString(rp.gx2.pGV, rp.gx2.ulGVLen, aGV2) &&
|
||||
toHexString(rp.gx2.pR, rp.gx2.ulRLen, aR2),
|
||||
NS_ERROR_OUT_OF_MEMORY);
|
||||
round = JPAKEBeforeRound2;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsSyncJPAKE::Round2(const nsACString & aPeerID,
|
||||
const nsACString & aPIN,
|
||||
const nsACString & aGX3,
|
||||
const nsACString & aGV3,
|
||||
const nsACString & aR3,
|
||||
const nsACString & aGX4,
|
||||
const nsACString & aGV4,
|
||||
const nsACString & aR4,
|
||||
nsACString & aA,
|
||||
nsACString & aGVA,
|
||||
nsACString & aRA)
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(round == JPAKEBeforeRound2);
|
||||
NS_ENSURE_STATE(key != nullptr);
|
||||
NS_ENSURE_ARG(!aPeerID.IsEmpty());
|
||||
|
||||
/* PIN cannot be equal to zero when converted to a bignum. NSS 3.12.9 J-PAKE
|
||||
assumes that the caller has already done this check. Future versions of
|
||||
NSS J-PAKE will do this check internally. See Bug 609068 Comment 4 */
|
||||
bool foundNonZero = false;
|
||||
for (size_t i = 0; i < aPIN.Length(); ++i) {
|
||||
if (aPIN[i] != 0) {
|
||||
foundNonZero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
NS_ENSURE_ARG(foundNonZero);
|
||||
|
||||
CK_BYTE gx3Buf[NUM_ELEM(p)/2], gv3Buf[NUM_ELEM(p)/2], r3Buf [NUM_ELEM(p)/2];
|
||||
CK_BYTE gx4Buf[NUM_ELEM(p)/2], gv4Buf[NUM_ELEM(p)/2], r4Buf [NUM_ELEM(p)/2];
|
||||
CK_BYTE gxABuf[NUM_ELEM(p)/2], gvABuf[NUM_ELEM(p)/2], rABuf [NUM_ELEM(p)/2];
|
||||
nsresult rv = fromHexString(aGX3, gx3Buf, sizeof gx3Buf);
|
||||
if (rv == NS_OK) rv = fromHexString(aGV3, gv3Buf, sizeof gv3Buf);
|
||||
if (rv == NS_OK) rv = fromHexString(aR3, r3Buf, sizeof r3Buf);
|
||||
if (rv == NS_OK) rv = fromHexString(aGX4, gx4Buf, sizeof gx4Buf);
|
||||
if (rv == NS_OK) rv = fromHexString(aGV4, gv4Buf, sizeof gv4Buf);
|
||||
if (rv == NS_OK) rv = fromHexString(aR4, r4Buf, sizeof r4Buf);
|
||||
if (rv != NS_OK)
|
||||
return rv;
|
||||
|
||||
CK_NSS_JPAKERound2Params rp;
|
||||
rp.pSharedKey = (CK_BYTE *) aPIN.Data();
|
||||
rp.ulSharedKeyLen = aPIN.Length();
|
||||
rp.gx3.pGX = gx3Buf; rp.gx3.ulGXLen = aGX3.Length() / 2;
|
||||
rp.gx3.pGV = gv3Buf; rp.gx3.ulGVLen = aGV3.Length() / 2;
|
||||
rp.gx3.pR = r3Buf; rp.gx3.ulRLen = aR3 .Length() / 2;
|
||||
rp.gx4.pGX = gx4Buf; rp.gx4.ulGXLen = aGX4.Length() / 2;
|
||||
rp.gx4.pGV = gv4Buf; rp.gx4.ulGVLen = aGV4.Length() / 2;
|
||||
rp.gx4.pR = r4Buf; rp.gx4.ulRLen = aR4 .Length() / 2;
|
||||
rp.A.pGX = gxABuf; rp.A .ulGXLen = sizeof gxABuf;
|
||||
rp.A.pGV = gvABuf; rp.A .ulGVLen = sizeof gxABuf;
|
||||
rp.A.pR = rABuf; rp.A .ulRLen = sizeof gxABuf;
|
||||
|
||||
// Bug 629090: NSS 3.12.9 J-PAKE fails to check that gx^4 != 1, so check here.
|
||||
bool gx4Good = false;
|
||||
for (unsigned i = 0; i < rp.gx4.ulGXLen; ++i) {
|
||||
if (rp.gx4.pGX[i] > 1 || (rp.gx4.pGX[i] != 0 && i < rp.gx4.ulGXLen - 1)) {
|
||||
gx4Good = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
NS_ENSURE_ARG(gx4Good);
|
||||
|
||||
SECItem paramsItem;
|
||||
paramsItem.data = (unsigned char *) &rp;
|
||||
paramsItem.len = sizeof rp;
|
||||
CK_KEY_TYPE keyType = CKK_NSS_JPAKE_ROUND2;
|
||||
CK_ATTRIBUTE keyTemplate[] = {
|
||||
{ CKA_NSS_JPAKE_PEERID, (CK_BYTE *) aPeerID.Data(), aPeerID.Length(), },
|
||||
{ CKA_KEY_TYPE, &keyType, sizeof keyType }
|
||||
};
|
||||
UniquePK11SymKey newKey(PK11_DeriveWithTemplate(key.get(),
|
||||
CKM_NSS_JPAKE_ROUND2_SHA256,
|
||||
¶msItem,
|
||||
CKM_NSS_JPAKE_FINAL_SHA256,
|
||||
CKA_DERIVE, 0,
|
||||
keyTemplate,
|
||||
NUM_ELEM(keyTemplate),
|
||||
false));
|
||||
if (newKey != nullptr) {
|
||||
if (toHexString(rp.A.pGX, rp.A.ulGXLen, aA) &&
|
||||
toHexString(rp.A.pGV, rp.A.ulGVLen, aGVA) &&
|
||||
toHexString(rp.A.pR, rp.A.ulRLen, aRA)) {
|
||||
round = JPAKEAfterRound2;
|
||||
key = Move(newKey);
|
||||
return NS_OK;
|
||||
} else {
|
||||
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
} else {
|
||||
rv = mapErrno();
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
setBase64(const unsigned char * data, unsigned len, nsACString & out)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
const char * base64 = BTOA_DataToAscii(data, len);
|
||||
|
||||
if (base64 != nullptr) {
|
||||
size_t len = PORT_Strlen(base64);
|
||||
if (out.SetCapacity(len, fallible)) {
|
||||
out.SetLength(0);
|
||||
out.Append(base64, len);
|
||||
} else {
|
||||
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
PORT_Free((void*) base64);
|
||||
} else {
|
||||
rv = NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
base64KeyValue(PK11SymKey * key, nsACString & keyString)
|
||||
{
|
||||
nsresult rv = NS_OK;
|
||||
if (PK11_ExtractKeyValue(key) == SECSuccess) {
|
||||
const SECItem * value = PK11_GetKeyData(key);
|
||||
rv = value != nullptr && value->data != nullptr && value->len > 0
|
||||
? setBase64(value->data, value->len, keyString)
|
||||
: NS_ERROR_UNEXPECTED;
|
||||
} else {
|
||||
rv = mapErrno();
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
static nsresult
|
||||
extractBase64KeyValue(UniquePK11SymKey & keyBlock, CK_ULONG bitPosition,
|
||||
CK_MECHANISM_TYPE destMech, int keySize,
|
||||
nsACString & keyString)
|
||||
{
|
||||
SECItem paramsItem;
|
||||
paramsItem.data = (CK_BYTE *) &bitPosition;
|
||||
paramsItem.len = sizeof bitPosition;
|
||||
PK11SymKey * key = PK11_Derive(keyBlock.get(), CKM_EXTRACT_KEY_FROM_KEY,
|
||||
¶msItem, destMech,
|
||||
CKA_SIGN, keySize);
|
||||
if (key == nullptr)
|
||||
return mapErrno();
|
||||
nsresult rv = base64KeyValue(key, keyString);
|
||||
PK11_FreeSymKey(key);
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP nsSyncJPAKE::Final(const nsACString & aB,
|
||||
const nsACString & aGVB,
|
||||
const nsACString & aRB,
|
||||
const nsACString & aHKDFInfo,
|
||||
nsACString & aAES256Key,
|
||||
nsACString & aHMAC256Key)
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
static const unsigned AES256_KEY_SIZE = 256 / 8;
|
||||
static const unsigned HMAC_SHA256_KEY_SIZE = 256 / 8;
|
||||
CK_EXTRACT_PARAMS aesBitPosition = 0;
|
||||
CK_EXTRACT_PARAMS hmacBitPosition = aesBitPosition + (AES256_KEY_SIZE * 8);
|
||||
|
||||
NS_ENSURE_STATE(round == JPAKEAfterRound2);
|
||||
NS_ENSURE_STATE(key != nullptr);
|
||||
|
||||
CK_BYTE gxBBuf[NUM_ELEM(p)/2], gvBBuf[NUM_ELEM(p)/2], rBBuf [NUM_ELEM(p)/2];
|
||||
nsresult rv = fromHexString(aB, gxBBuf, sizeof gxBBuf);
|
||||
if (rv == NS_OK) rv = fromHexString(aGVB, gvBBuf, sizeof gvBBuf);
|
||||
if (rv == NS_OK) rv = fromHexString(aRB, rBBuf, sizeof rBBuf);
|
||||
if (rv != NS_OK)
|
||||
return rv;
|
||||
|
||||
CK_NSS_JPAKEFinalParams rp;
|
||||
rp.B.pGX = gxBBuf; rp.B.ulGXLen = aB .Length() / 2;
|
||||
rp.B.pGV = gvBBuf; rp.B.ulGVLen = aGVB.Length() / 2;
|
||||
rp.B.pR = rBBuf; rp.B.ulRLen = aRB .Length() / 2;
|
||||
SECItem paramsItem;
|
||||
paramsItem.data = (unsigned char *) &rp;
|
||||
paramsItem.len = sizeof rp;
|
||||
UniquePK11SymKey keyMaterial(PK11_Derive(key.get(), CKM_NSS_JPAKE_FINAL_SHA256,
|
||||
¶msItem, CKM_NSS_HKDF_SHA256,
|
||||
CKA_DERIVE, 0));
|
||||
UniquePK11SymKey keyBlock;
|
||||
|
||||
if (keyMaterial == nullptr)
|
||||
rv = mapErrno();
|
||||
|
||||
if (rv == NS_OK) {
|
||||
CK_NSS_HKDFParams hkdfParams;
|
||||
hkdfParams.bExtract = CK_TRUE;
|
||||
hkdfParams.pSalt = nullptr;
|
||||
hkdfParams.ulSaltLen = 0;
|
||||
hkdfParams.bExpand = CK_TRUE;
|
||||
hkdfParams.pInfo = (CK_BYTE *) aHKDFInfo.Data();
|
||||
hkdfParams.ulInfoLen = aHKDFInfo.Length();
|
||||
paramsItem.data = (unsigned char *) &hkdfParams;
|
||||
paramsItem.len = sizeof hkdfParams;
|
||||
keyBlock = UniquePK11SymKey(
|
||||
PK11_Derive(keyMaterial.get(), CKM_NSS_HKDF_SHA256, ¶msItem,
|
||||
CKM_EXTRACT_KEY_FROM_KEY, CKA_DERIVE,
|
||||
AES256_KEY_SIZE + HMAC_SHA256_KEY_SIZE));
|
||||
if (keyBlock == nullptr)
|
||||
rv = mapErrno();
|
||||
}
|
||||
|
||||
if (rv == NS_OK) {
|
||||
rv = extractBase64KeyValue(keyBlock, aesBitPosition, CKM_AES_CBC,
|
||||
AES256_KEY_SIZE, aAES256Key);
|
||||
}
|
||||
if (rv == NS_OK) {
|
||||
rv = extractBase64KeyValue(keyBlock, hmacBitPosition, CKM_SHA256_HMAC,
|
||||
HMAC_SHA256_KEY_SIZE, aHMAC256Key);
|
||||
}
|
||||
|
||||
if (rv == NS_OK) {
|
||||
SECStatus srv = PK11_ExtractKeyValue(keyMaterial.get());
|
||||
NS_ENSURE_TRUE(srv == SECSuccess, NS_ERROR_UNEXPECTED);
|
||||
SECItem * keyMaterialBytes = PK11_GetKeyData(keyMaterial.get());
|
||||
NS_ENSURE_TRUE(keyMaterialBytes != nullptr, NS_ERROR_UNEXPECTED);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(nsSyncJPAKE)
|
||||
NS_DEFINE_NAMED_CID(NS_SYNCJPAKE_CID);
|
||||
|
||||
nsSyncJPAKE::nsSyncJPAKE() : round(JPAKENotStarted), key(nullptr) { }
|
||||
|
||||
nsSyncJPAKE::~nsSyncJPAKE()
|
||||
{
|
||||
nsNSSShutDownPreventionLock locker;
|
||||
if (isAlreadyShutDown()) {
|
||||
return;
|
||||
}
|
||||
destructorSafeDestroyNSSReference();
|
||||
shutdown(ShutdownCalledFrom::Object);
|
||||
}
|
||||
|
||||
void
|
||||
nsSyncJPAKE::virtualDestroyNSSReference()
|
||||
{
|
||||
destructorSafeDestroyNSSReference();
|
||||
}
|
||||
|
||||
void
|
||||
nsSyncJPAKE::destructorSafeDestroyNSSReference()
|
||||
{
|
||||
key = nullptr;
|
||||
}
|
||||
|
||||
static const mozilla::Module::CIDEntry kServicesCryptoCIDs[] = {
|
||||
{ &kNS_SYNCJPAKE_CID, false, nullptr, nsSyncJPAKEConstructor },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module::ContractIDEntry kServicesCryptoContracts[] = {
|
||||
{ NS_SYNCJPAKE_CONTRACTID, &kNS_SYNCJPAKE_CID },
|
||||
{ nullptr }
|
||||
};
|
||||
|
||||
static const mozilla::Module kServicesCryptoModule = {
|
||||
mozilla::Module::kVersion,
|
||||
kServicesCryptoCIDs,
|
||||
kServicesCryptoContracts
|
||||
};
|
||||
|
||||
NSMODULE_DEFN(nsServicesCryptoModule) = &kServicesCryptoModule;
|
||||
38
services/crypto/component/nsSyncJPAKE.h
Normal file
38
services/crypto/component/nsSyncJPAKE.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* 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/. */
|
||||
#ifndef nsSyncJPAKE_h__
|
||||
#define nsSyncJPAKE_h__
|
||||
|
||||
#include "ScopedNSSTypes.h"
|
||||
#include "nsISyncJPAKE.h"
|
||||
#include "nsNSSShutDown.h"
|
||||
|
||||
#define NS_SYNCJPAKE_CONTRACTID \
|
||||
"@mozilla.org/services-crypto/sync-jpake;1"
|
||||
|
||||
#define NS_SYNCJPAKE_CID \
|
||||
{0x0b9721c0, 0x1805, 0x47c3, {0x86, 0xce, 0x68, 0x13, 0x79, 0x5a, 0x78, 0x3f}}
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
class nsSyncJPAKE : public nsISyncJPAKE
|
||||
, public nsNSSShutDownObject
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSISYNCJPAKE
|
||||
nsSyncJPAKE();
|
||||
protected:
|
||||
virtual ~nsSyncJPAKE();
|
||||
private:
|
||||
virtual void virtualDestroyNSSReference() override;
|
||||
void destructorSafeDestroyNSSReference();
|
||||
|
||||
enum { JPAKENotStarted, JPAKEBeforeRound2, JPAKEAfterRound2 } round;
|
||||
UniquePK11SymKey key;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsSyncJPAKE, nsISyncJPAKE)
|
||||
|
||||
#endif // nsSyncJPAKE_h__
|
||||
289
services/crypto/component/tests/unit/test_jpake.js
Normal file
289
services/crypto/component/tests/unit/test_jpake.js
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
|
||||
// Ensure PSM is initialized.
|
||||
Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
|
||||
|
||||
function do_check_throws(func) {
|
||||
let have_error = false;
|
||||
try {
|
||||
func();
|
||||
} catch(ex) {
|
||||
dump("Was expecting an exception. Caught: " + ex + "\n");
|
||||
have_error = true;
|
||||
}
|
||||
do_check_true(have_error);
|
||||
}
|
||||
|
||||
function test_success() {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
let b = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
let a_gx1 = {};
|
||||
let a_gv1 = {};
|
||||
let a_r1 = {};
|
||||
let a_gx2 = {};
|
||||
let a_gv2 = {};
|
||||
let a_r2 = {};
|
||||
|
||||
let b_gx1 = {};
|
||||
let b_gv1 = {};
|
||||
let b_r1 = {};
|
||||
let b_gx2 = {};
|
||||
let b_gv2 = {};
|
||||
let b_r2 = {};
|
||||
|
||||
a.round1("alice", a_gx1, a_gv1, a_r1, a_gx2, a_gv2, a_r2);
|
||||
b.round1("bob", b_gx1, b_gv1, b_r1, b_gx2, b_gv2, b_r2);
|
||||
|
||||
let a_A = {};
|
||||
let a_gva = {};
|
||||
let a_ra = {};
|
||||
|
||||
let b_A = {};
|
||||
let b_gva = {};
|
||||
let b_ra = {};
|
||||
|
||||
a.round2("bob", "sekrit", b_gx1.value, b_gv1.value, b_r1.value,
|
||||
b_gx2.value, b_gv2.value, b_r2.value, a_A, a_gva, a_ra);
|
||||
b.round2("alice", "sekrit", a_gx1.value, a_gv1.value, a_r1.value,
|
||||
a_gx2.value, a_gv2.value, a_r2.value, b_A, b_gva, b_ra);
|
||||
|
||||
let a_aes = {};
|
||||
let a_hmac = {};
|
||||
let b_aes = {};
|
||||
let b_hmac = {};
|
||||
|
||||
a.final(b_A.value, b_gva.value, b_ra.value, "ohai", a_aes, a_hmac);
|
||||
b.final(a_A.value, a_gva.value, a_ra.value, "ohai", b_aes, b_hmac);
|
||||
|
||||
do_check_eq(a_aes.value, b_aes.value);
|
||||
do_check_eq(a_hmac.value, b_hmac.value);
|
||||
}
|
||||
|
||||
function test_failure(modlen) {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
let b = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
let a_gx1 = {};
|
||||
let a_gv1 = {};
|
||||
let a_r1 = {};
|
||||
let a_gx2 = {};
|
||||
let a_gv2 = {};
|
||||
let a_r2 = {};
|
||||
|
||||
let b_gx1 = {};
|
||||
let b_gv1 = {};
|
||||
let b_r1 = {};
|
||||
let b_gx2 = {};
|
||||
let b_gv2 = {};
|
||||
let b_r2 = {};
|
||||
|
||||
a.round1("alice", a_gx1, a_gv1, a_r1, a_gx2, a_gv2, a_r2);
|
||||
b.round1("bob", b_gx1, b_gv1, b_r1, b_gx2, b_gv2, b_r2);
|
||||
|
||||
let a_A = {};
|
||||
let a_gva = {};
|
||||
let a_ra = {};
|
||||
|
||||
let b_A = {};
|
||||
let b_gva = {};
|
||||
let b_ra = {};
|
||||
|
||||
// Note how the PINs are different (secret vs. sekrit)
|
||||
a.round2("bob", "secret", b_gx1.value, b_gv1.value, b_r1.value,
|
||||
b_gx2.value, b_gv2.value, b_r2.value, a_A, a_gva, a_ra);
|
||||
b.round2("alice", "sekrit", a_gx1.value, a_gv1.value, a_r1.value,
|
||||
a_gx2.value, a_gv2.value, a_r2.value, b_A, b_gva, b_ra);
|
||||
|
||||
let a_aes = {};
|
||||
let a_hmac = {};
|
||||
let b_aes = {};
|
||||
let b_hmac = {};
|
||||
|
||||
a.final(b_A.value, b_gva.value, b_ra.value, "ohai", a_aes, a_hmac);
|
||||
b.final(a_A.value, a_gva.value, a_ra.value, "ohai", b_aes, b_hmac);
|
||||
|
||||
do_check_neq(a_aes.value, b_aes.value);
|
||||
do_check_neq(a_hmac.value, b_hmac.value);
|
||||
}
|
||||
|
||||
function test_same_signerids() {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
let b = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
let gx1 = {};
|
||||
let gv1 = {};
|
||||
let r1 = {};
|
||||
let gx2 = {};
|
||||
let gv2 = {};
|
||||
let r2 = {};
|
||||
|
||||
a.round1("alice", {}, {}, {}, {}, {}, {});
|
||||
b.round1("alice", gx1, gv1, r1, gx2, gv2, r2);
|
||||
do_check_throws(function() {
|
||||
a.round2("alice", "sekrit", gx1.value, gv1.value, r1.value,
|
||||
gx2.value, gv2.value, r2.value, {}, {}, {});
|
||||
});
|
||||
}
|
||||
|
||||
function test_bad_zkp() {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
let b = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
let gx1 = {};
|
||||
let gv1 = {};
|
||||
let r1 = {};
|
||||
let gx2 = {};
|
||||
let gv2 = {};
|
||||
let r2 = {};
|
||||
|
||||
a.round1("alice", {}, {}, {}, {}, {}, {});
|
||||
b.round1("bob", gx1, gv1, r1, gx2, gv2, r2);
|
||||
do_check_throws(function() {
|
||||
a.round2("invalid", "sekrit", gx1.value, gv1.value, r1.value,
|
||||
gx2.value, gv2.value, r2.value, {}, {}, {});
|
||||
});
|
||||
}
|
||||
|
||||
function test_x4_zero() {
|
||||
// The PKCS#11 API for J-PAKE does not allow us to choose any of the nonces.
|
||||
// In order to test the defence against x4 (mod p) == 1, we had to generate
|
||||
// our own signed nonces using a the FreeBL JPAKE_Sign function directly.
|
||||
// To verify the signatures are accurate, pass the given value of R as the
|
||||
// "testRandom" parameter to FreeBL's JPAKE_Sign, along with the given values
|
||||
// for X and GX, using signerID "alice". Then verify that each GV returned
|
||||
// from JPAKE_Sign matches the value specified here.
|
||||
let test = function(badGX, badX_GV, badX_R) {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
let b = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
let a_gx1 = {};
|
||||
let a_gv1 = {};
|
||||
let a_r1 = {};
|
||||
let a_gx2 = {};
|
||||
let a_gv2 = {};
|
||||
let a_r2 = {};
|
||||
|
||||
let b_gx1 = {};
|
||||
let b_gv1 = {};
|
||||
let b_r1 = {};
|
||||
let b_gx2 = {};
|
||||
let b_gv2 = {};
|
||||
let b_r2 = {};
|
||||
|
||||
a.round1("alice", a_gx1, a_gv1, a_r1, a_gx2, a_gv2, a_r2);
|
||||
b.round1("bob", b_gx1, b_gv1, b_r1, b_gx2, b_gv2, b_r2);
|
||||
|
||||
// Replace the g^x2 generated by A with the given illegal value.
|
||||
a_gx2.value = badGX;
|
||||
a_gv2.value = badX_GV;
|
||||
a_r2.value = badX_R;
|
||||
|
||||
let b_A = {};
|
||||
let b_gva = {};
|
||||
let b_ra = {};
|
||||
|
||||
do_check_throws(function() {
|
||||
b.round2("alice", "secret", a_gx1.value, a_gv1.value, a_r1.value,
|
||||
a_gx2.value, a_gv2.value, a_r2.value, b_A, b_gva, b_ra);
|
||||
});
|
||||
};
|
||||
|
||||
// g^x is NIST 3072's p + 1, (p + 1) mod p == 1, x == 0
|
||||
test("90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C"
|
||||
+ "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F"
|
||||
+ "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1"
|
||||
+ "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B"
|
||||
+ "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394"
|
||||
+ "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0"
|
||||
+ "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E"
|
||||
+ "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D"
|
||||
+ "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F"
|
||||
+ "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D"
|
||||
+ "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E"
|
||||
+ "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB74",
|
||||
"5386107A0DD4A96ECF8D9BCF864BDE23AAEF13351F5550D777A32C1FEC165ED67AE51"
|
||||
+ "66C3876AABC1FED1A0993754F3AEE256530F529548F8FE010BC0D070175569845"
|
||||
+ "CF009AD24BC897A9CA1F18E1A9CE421DD54FD93AB528BC2594B47791713165276"
|
||||
+ "7B76903190C3DCD2076FEC1E61FFFC32D1B07273B06EA2889E66FCBFD41FE8984"
|
||||
+ "5FCE36056B09D1F20E58BB6BAA07A32796F11998BEF0AB3D387E2FB4FE3073FEB"
|
||||
+ "634BA91709010A70DA29C06F8F92D638C4F158680EAFEB5E0E323BD7DACB671C0"
|
||||
+ "BA3EDEEAB5CAA243CABAB28E7205AC9A0AAEAFE132635DAC7FE001C19F880A96E"
|
||||
+ "395C42536D694F81B4F44DC66D7D6FBE933C56ABF585837291D8751C18EB1F3FB"
|
||||
+ "620582E6A7B795D699E38C270863A289583CB9D07651E6BA3B82BC656B49BD09B"
|
||||
+ "6B8C27F370120C7CB89D0829BE51D56356EA836012E9204FF4D1CA8B1B7F9C768"
|
||||
+ "4BB2B0F226FD4042EEBAD931FDBD4F81F8425B305752F5E37FFA2B73BB5A034EC"
|
||||
+ "7EEF5AAC92EA212897E3A2B8961D2147710ECCE127B942AB2",
|
||||
"05CC4DF005FE006C11111624E14806E4A904A4D1D6A53E795AC7867A960CD4FD");
|
||||
|
||||
// x == 0 implies g^x == 1
|
||||
test("01",
|
||||
"488759644532FA7C53E5239F2A365D4B9189582BDD2967A1852FE56568382B65"
|
||||
+ "C66BDFCD9B581EAEF4BB497CAF1290ECDFA47A1D1658DC5DC9248D9A4135"
|
||||
+ "DC70B6A8497CDF117236841FA18500DC696A92EEF5000ABE68E9C75B37BC"
|
||||
+ "6A722126BE728163AA90A6B03D5585994D3403557EEF08E819C72D143BBC"
|
||||
+ "CDF74559645066CB3607E1B0430365356389FC8FB3D66FD2B6E2E834EC23"
|
||||
+ "0B0234956752D07F983C918488C8E5A124B062D50B44C5E6FB36BCB03E39"
|
||||
+ "0385B17CF8062B6688371E6AF5915C2B1AAA31C9294943CC6DC1B994FC09"
|
||||
+ "49CA31828B83F3D6DFB081B26045DFD9F10092588B63F1D6E68881A06522"
|
||||
+ "5A417CA9555B036DE89D349AC794A43EB28FE320F9A321F06A9364C88B54"
|
||||
+ "99EEF4816375B119824ACC9AA56D1340B6A49D05F855DE699B351012028C"
|
||||
+ "CA43001F708CC61E71CA3849935BEEBABC0D268CD41B8D2B8DCA705FDFF8"
|
||||
+ "1DAA772DA96EDEA0B291FD5C0C1B8EFE5318D37EBC1BFF53A9DDEC4171A6"
|
||||
+ "479E341438970058E25C8F2BCDA6166C8BF1B065C174",
|
||||
"8B2BACE575179D762F6F2FFDBFF00B497C07766AB3EED9961447CF6F43D06A97");
|
||||
}
|
||||
|
||||
function test_invalid_input_round2() {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
a.round1("alice", {}, {}, {}, {}, {}, {});
|
||||
do_check_throws(function() {
|
||||
a.round2("invalid", "sekrit", "some", "real", "garbage",
|
||||
"even", "more", "garbage", {}, {}, {});
|
||||
});
|
||||
}
|
||||
|
||||
function test_invalid_input_final() {
|
||||
let a = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
let b = Cc["@mozilla.org/services-crypto/sync-jpake;1"]
|
||||
.createInstance(Ci.nsISyncJPAKE);
|
||||
|
||||
let gx1 = {};
|
||||
let gv1 = {};
|
||||
let r1 = {};
|
||||
let gx2 = {};
|
||||
let gv2 = {};
|
||||
let r2 = {};
|
||||
|
||||
a.round1("alice", {}, {}, {}, {}, {}, {});
|
||||
b.round1("bob", gx1, gv1, r1, gx2, gv2, r2);
|
||||
a.round2("bob", "sekrit", gx1.value, gv1.value, r1.value,
|
||||
gx2.value, gv2.value, r2.value, {}, {}, {});
|
||||
do_check_throws(function() {
|
||||
a.final("some", "garbage", "alright", "foobar-info", {}, {});
|
||||
});
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_x4_zero();
|
||||
test_success();
|
||||
test_failure();
|
||||
test_same_signerids();
|
||||
test_bad_zkp();
|
||||
test_invalid_input_round2();
|
||||
test_invalid_input_final();
|
||||
}
|
||||
6
services/crypto/component/tests/unit/xpcshell.ini
Normal file
6
services/crypto/component/tests/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[DEFAULT]
|
||||
head =
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
|
||||
[test_jpake.js]
|
||||
1
services/crypto/cryptoComponents.manifest
Normal file
1
services/crypto/cryptoComponents.manifest
Normal file
|
|
@ -0,0 +1 @@
|
|||
resource services-crypto resource://gre/modules/services-crypto/
|
||||
266
services/crypto/modules/WeaveCrypto.js
Normal file
266
services/crypto/modules/WeaveCrypto.js
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
/* 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/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["WeaveCrypto"];
|
||||
|
||||
var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://services-common/async.js");
|
||||
|
||||
Cu.importGlobalProperties(['crypto']);
|
||||
|
||||
const CRYPT_ALGO = "AES-CBC";
|
||||
const CRYPT_ALGO_LENGTH = 256;
|
||||
const AES_CBC_IV_SIZE = 16;
|
||||
const OPERATIONS = { ENCRYPT: 0, DECRYPT: 1 };
|
||||
const UTF_LABEL = "utf-8";
|
||||
|
||||
const KEY_DERIVATION_ALGO = "PBKDF2";
|
||||
const KEY_DERIVATION_HASHING_ALGO = "SHA-1";
|
||||
const KEY_DERIVATION_ITERATIONS = 4096; // PKCS#5 recommends at least 1000.
|
||||
const DERIVED_KEY_ALGO = CRYPT_ALGO;
|
||||
|
||||
this.WeaveCrypto = function WeaveCrypto() {
|
||||
this.init();
|
||||
};
|
||||
|
||||
WeaveCrypto.prototype = {
|
||||
prefBranch : null,
|
||||
debug : true, // services.sync.log.cryptoDebug
|
||||
|
||||
observer : {
|
||||
_self : null,
|
||||
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
observe(subject, topic, data) {
|
||||
let self = this._self;
|
||||
self.log("Observed " + topic + " topic.");
|
||||
if (topic == "nsPref:changed") {
|
||||
self.debug = self.prefBranch.getBoolPref("cryptoDebug");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
// Preferences. Add observer so we get notified of changes.
|
||||
this.prefBranch = Services.prefs.getBranch("services.sync.log.");
|
||||
this.prefBranch.addObserver("cryptoDebug", this.observer, false);
|
||||
this.observer._self = this;
|
||||
try {
|
||||
this.debug = this.prefBranch.getBoolPref("cryptoDebug");
|
||||
} catch (x) {
|
||||
this.debug = false;
|
||||
}
|
||||
XPCOMUtils.defineLazyGetter(this, 'encoder', () => new TextEncoder(UTF_LABEL));
|
||||
XPCOMUtils.defineLazyGetter(this, 'decoder', () => new TextDecoder(UTF_LABEL, { fatal: true }));
|
||||
},
|
||||
|
||||
log(message) {
|
||||
if (!this.debug) {
|
||||
return;
|
||||
}
|
||||
dump("WeaveCrypto: " + message + "\n");
|
||||
Services.console.logStringMessage("WeaveCrypto: " + message);
|
||||
},
|
||||
|
||||
// /!\ Only use this for tests! /!\
|
||||
_getCrypto() {
|
||||
return crypto;
|
||||
},
|
||||
|
||||
encrypt(clearTextUCS2, symmetricKey, iv) {
|
||||
this.log("encrypt() called");
|
||||
let clearTextBuffer = this.encoder.encode(clearTextUCS2).buffer;
|
||||
let encrypted = this._commonCrypt(clearTextBuffer, symmetricKey, iv, OPERATIONS.ENCRYPT);
|
||||
return this.encodeBase64(encrypted);
|
||||
},
|
||||
|
||||
decrypt(cipherText, symmetricKey, iv) {
|
||||
this.log("decrypt() called");
|
||||
if (cipherText.length) {
|
||||
cipherText = atob(cipherText);
|
||||
}
|
||||
let cipherTextBuffer = this.byteCompressInts(cipherText);
|
||||
let decrypted = this._commonCrypt(cipherTextBuffer, symmetricKey, iv, OPERATIONS.DECRYPT);
|
||||
return this.decoder.decode(decrypted);
|
||||
},
|
||||
|
||||
/**
|
||||
* _commonCrypt
|
||||
*
|
||||
* @args
|
||||
* data: data to encrypt/decrypt (ArrayBuffer)
|
||||
* symKeyStr: symmetric key (Base64 String)
|
||||
* ivStr: initialization vector (Base64 String)
|
||||
* operation: operation to apply (either OPERATIONS.ENCRYPT or OPERATIONS.DECRYPT)
|
||||
* @returns
|
||||
* the encrypted/decrypted data (ArrayBuffer)
|
||||
*/
|
||||
_commonCrypt(data, symKeyStr, ivStr, operation) {
|
||||
this.log("_commonCrypt() called");
|
||||
ivStr = atob(ivStr);
|
||||
|
||||
if (operation !== OPERATIONS.ENCRYPT && operation !== OPERATIONS.DECRYPT) {
|
||||
throw new Error("Unsupported operation in _commonCrypt.");
|
||||
}
|
||||
// We never want an IV longer than the block size, which is 16 bytes
|
||||
// for AES, neither do we want one smaller; throw in both cases.
|
||||
if (ivStr.length !== AES_CBC_IV_SIZE) {
|
||||
throw "Invalid IV size; must be " + AES_CBC_IV_SIZE + " bytes.";
|
||||
}
|
||||
|
||||
let iv = this.byteCompressInts(ivStr);
|
||||
let symKey = this.importSymKey(symKeyStr, operation);
|
||||
let cryptMethod = (operation === OPERATIONS.ENCRYPT
|
||||
? crypto.subtle.encrypt
|
||||
: crypto.subtle.decrypt)
|
||||
.bind(crypto.subtle);
|
||||
let algo = { name: CRYPT_ALGO, iv: iv };
|
||||
|
||||
|
||||
return Async.promiseSpinningly(
|
||||
cryptMethod(algo, symKey, data)
|
||||
.then(keyBytes => new Uint8Array(keyBytes))
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
generateRandomKey() {
|
||||
this.log("generateRandomKey() called");
|
||||
let algo = {
|
||||
name: CRYPT_ALGO,
|
||||
length: CRYPT_ALGO_LENGTH
|
||||
};
|
||||
return Async.promiseSpinningly(
|
||||
crypto.subtle.generateKey(algo, true, [])
|
||||
.then(key => crypto.subtle.exportKey("raw", key))
|
||||
.then(keyBytes => {
|
||||
keyBytes = new Uint8Array(keyBytes);
|
||||
return this.encodeBase64(keyBytes);
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
generateRandomIV() {
|
||||
return this.generateRandomBytes(AES_CBC_IV_SIZE);
|
||||
},
|
||||
|
||||
generateRandomBytes(byteCount) {
|
||||
this.log("generateRandomBytes() called");
|
||||
|
||||
let randBytes = new Uint8Array(byteCount);
|
||||
crypto.getRandomValues(randBytes);
|
||||
|
||||
return this.encodeBase64(randBytes);
|
||||
},
|
||||
|
||||
//
|
||||
// SymKey CryptoKey memoization.
|
||||
//
|
||||
|
||||
// Memoize the import of symmetric keys. We do this by using the base64
|
||||
// string itself as a key.
|
||||
_encryptionSymKeyMemo: {},
|
||||
_decryptionSymKeyMemo: {},
|
||||
importSymKey(encodedKeyString, operation) {
|
||||
let memo;
|
||||
|
||||
// We use two separate memos for thoroughness: operation is an input to
|
||||
// key import.
|
||||
switch (operation) {
|
||||
case OPERATIONS.ENCRYPT:
|
||||
memo = this._encryptionSymKeyMemo;
|
||||
break;
|
||||
case OPERATIONS.DECRYPT:
|
||||
memo = this._decryptionSymKeyMemo;
|
||||
break;
|
||||
default:
|
||||
throw "Unsupported operation in importSymKey.";
|
||||
}
|
||||
|
||||
if (encodedKeyString in memo)
|
||||
return memo[encodedKeyString];
|
||||
|
||||
let symmetricKeyBuffer = this.makeUint8Array(encodedKeyString, true);
|
||||
let algo = { name: CRYPT_ALGO };
|
||||
let usages = [operation === OPERATIONS.ENCRYPT ? "encrypt" : "decrypt"];
|
||||
|
||||
return Async.promiseSpinningly(
|
||||
crypto.subtle.importKey("raw", symmetricKeyBuffer, algo, false, usages)
|
||||
.then(symKey => {
|
||||
memo[encodedKeyString] = symKey;
|
||||
return symKey;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Utility functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Returns an Uint8Array filled with a JS string,
|
||||
* which means we only keep utf-16 characters from 0x00 to 0xFF.
|
||||
*/
|
||||
byteCompressInts(str) {
|
||||
let arrayBuffer = new Uint8Array(str.length);
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
arrayBuffer[i] = str.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return arrayBuffer;
|
||||
},
|
||||
|
||||
expandData(data) {
|
||||
let expanded = "";
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
expanded += String.fromCharCode(data[i]);
|
||||
}
|
||||
return expanded;
|
||||
},
|
||||
|
||||
encodeBase64(data) {
|
||||
return btoa(this.expandData(data));
|
||||
},
|
||||
|
||||
makeUint8Array(input, isEncoded) {
|
||||
if (isEncoded) {
|
||||
input = atob(input);
|
||||
}
|
||||
return this.byteCompressInts(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the expanded data string for the derived key.
|
||||
*/
|
||||
deriveKeyFromPassphrase(passphrase, saltStr, keyLength = 32) {
|
||||
this.log("deriveKeyFromPassphrase() called.");
|
||||
let keyData = this.makeUint8Array(passphrase, false);
|
||||
let salt = this.makeUint8Array(saltStr, true);
|
||||
let importAlgo = { name: KEY_DERIVATION_ALGO };
|
||||
let deriveAlgo = {
|
||||
name: KEY_DERIVATION_ALGO,
|
||||
salt: salt,
|
||||
iterations: KEY_DERIVATION_ITERATIONS,
|
||||
hash: { name: KEY_DERIVATION_HASHING_ALGO },
|
||||
};
|
||||
let derivedKeyType = {
|
||||
name: DERIVED_KEY_ALGO,
|
||||
length: keyLength * 8,
|
||||
};
|
||||
return Async.promiseSpinningly(
|
||||
crypto.subtle.importKey("raw", keyData, importAlgo, false, ["deriveKey"])
|
||||
.then(key => crypto.subtle.deriveKey(deriveAlgo, key, derivedKeyType, true, []))
|
||||
.then(derivedKey => crypto.subtle.exportKey("raw", derivedKey))
|
||||
.then(keyBytes => {
|
||||
keyBytes = new Uint8Array(keyBytes);
|
||||
return this.expandData(keyBytes);
|
||||
})
|
||||
);
|
||||
},
|
||||
};
|
||||
584
services/crypto/modules/utils.js
Normal file
584
services/crypto/modules/utils.js
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
/* 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/. */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["CryptoUtils"];
|
||||
|
||||
Cu.import("resource://services-common/observers.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
this.CryptoUtils = {
|
||||
xor: function xor(a, b) {
|
||||
let bytes = [];
|
||||
|
||||
if (a.length != b.length) {
|
||||
throw new Error("can't xor unequal length strings: "+a.length+" vs "+b.length);
|
||||
}
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
||||
}
|
||||
|
||||
return String.fromCharCode.apply(String, bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a string of random bytes.
|
||||
*/
|
||||
generateRandomBytes: function generateRandomBytes(length) {
|
||||
let rng = Cc["@mozilla.org/security/random-generator;1"]
|
||||
.createInstance(Ci.nsIRandomGenerator);
|
||||
let bytes = rng.generateRandomBytes(length);
|
||||
return CommonUtils.byteArrayToString(bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* UTF8-encode a message and hash it with the given hasher. Returns a
|
||||
* string containing bytes. The hasher is reset if it's an HMAC hasher.
|
||||
*/
|
||||
digestUTF8: function digestUTF8(message, hasher) {
|
||||
let data = this._utf8Converter.convertToByteArray(message, {});
|
||||
hasher.update(data, data.length);
|
||||
let result = hasher.finish(false);
|
||||
if (hasher instanceof Ci.nsICryptoHMAC) {
|
||||
hasher.reset();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Treat the given message as a bytes string and hash it with the given
|
||||
* hasher. Returns a string containing bytes. The hasher is reset if it's
|
||||
* an HMAC hasher.
|
||||
*/
|
||||
digestBytes: function digestBytes(message, hasher) {
|
||||
// No UTF-8 encoding for you, sunshine.
|
||||
let bytes = Array.prototype.slice.call(message).map(b => b.charCodeAt(0));
|
||||
hasher.update(bytes, bytes.length);
|
||||
let result = hasher.finish(false);
|
||||
if (hasher instanceof Ci.nsICryptoHMAC) {
|
||||
hasher.reset();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Encode the message into UTF-8 and feed the resulting bytes into the
|
||||
* given hasher. Does not return a hash. This can be called multiple times
|
||||
* with a single hasher, but eventually you must extract the result
|
||||
* yourself.
|
||||
*/
|
||||
updateUTF8: function(message, hasher) {
|
||||
let bytes = this._utf8Converter.convertToByteArray(message, {});
|
||||
hasher.update(bytes, bytes.length);
|
||||
},
|
||||
|
||||
/**
|
||||
* UTF-8 encode a message and perform a SHA-1 over it.
|
||||
*
|
||||
* @param message
|
||||
* (string) Buffer to perform operation on. Should be a JS string.
|
||||
* It is possible to pass in a string representing an array
|
||||
* of bytes. But, you probably don't want to UTF-8 encode
|
||||
* such data and thus should not be using this function.
|
||||
*
|
||||
* @return string
|
||||
* Raw bytes constituting SHA-1 hash. Value is a JS string. Each
|
||||
* character is the byte value for that offset. Returned string
|
||||
* always has .length == 20.
|
||||
*/
|
||||
UTF8AndSHA1: function UTF8AndSHA1(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
|
||||
return CryptoUtils.digestUTF8(message, hasher);
|
||||
},
|
||||
|
||||
sha1: function sha1(message) {
|
||||
return CommonUtils.bytesAsHex(CryptoUtils.UTF8AndSHA1(message));
|
||||
},
|
||||
|
||||
sha1Base32: function sha1Base32(message) {
|
||||
return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(message));
|
||||
},
|
||||
|
||||
sha256(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher));
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC key object from a key string.
|
||||
*/
|
||||
makeHMACKey: function makeHMACKey(str) {
|
||||
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC hasher and initialize it with the given HMAC key.
|
||||
*/
|
||||
makeHMACHasher: function makeHMACHasher(type, key) {
|
||||
let hasher = Cc["@mozilla.org/security/hmac;1"]
|
||||
.createInstance(Ci.nsICryptoHMAC);
|
||||
hasher.init(type, key);
|
||||
return hasher;
|
||||
},
|
||||
|
||||
/**
|
||||
* HMAC-based Key Derivation (RFC 5869).
|
||||
*/
|
||||
hkdf: function hkdf(ikm, xts, info, len) {
|
||||
const BLOCKSIZE = 256 / 8;
|
||||
if (typeof xts === undefined)
|
||||
xts = String.fromCharCode(0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0);
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
||||
CryptoUtils.makeHMACKey(xts));
|
||||
let prk = CryptoUtils.digestBytes(ikm, h);
|
||||
return CryptoUtils.hkdfExpand(prk, info, len);
|
||||
},
|
||||
|
||||
/**
|
||||
* HMAC-based Key Derivation Step 2 according to RFC 5869.
|
||||
*/
|
||||
hkdfExpand: function hkdfExpand(prk, info, len) {
|
||||
const BLOCKSIZE = 256 / 8;
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
||||
CryptoUtils.makeHMACKey(prk));
|
||||
let T = "";
|
||||
let Tn = "";
|
||||
let iterations = Math.ceil(len/BLOCKSIZE);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
|
||||
T += Tn;
|
||||
}
|
||||
return T.slice(0, len);
|
||||
},
|
||||
|
||||
/**
|
||||
* PBKDF2 implementation in Javascript.
|
||||
*
|
||||
* The arguments to this function correspond to items in
|
||||
* PKCS #5, v2.0 pp. 9-10
|
||||
*
|
||||
* P: the passphrase, an octet string: e.g., "secret phrase"
|
||||
* S: the salt, an octet string: e.g., "DNXPzPpiwn"
|
||||
* c: the number of iterations, a positive integer: e.g., 4096
|
||||
* dkLen: the length in octets of the destination
|
||||
* key, a positive integer: e.g., 16
|
||||
* hmacAlg: The algorithm to use for hmac
|
||||
* hmacLen: The hmac length
|
||||
*
|
||||
* The default value of 20 for hmacLen is appropriate for SHA1. For SHA256,
|
||||
* hmacLen should be 32.
|
||||
*
|
||||
* The output is an octet string of length dkLen, which you
|
||||
* can encode as you wish.
|
||||
*/
|
||||
pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen,
|
||||
hmacAlg=Ci.nsICryptoHMAC.SHA1, hmacLen=20) {
|
||||
|
||||
// We don't have a default in the algo itself, as NSS does.
|
||||
// Use the constant.
|
||||
if (!dkLen) {
|
||||
dkLen = SYNC_KEY_DECODED_LENGTH;
|
||||
}
|
||||
|
||||
function F(S, c, i, h) {
|
||||
|
||||
function XOR(a, b, isA) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let val = [];
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (isA) {
|
||||
val[i] = a[i] ^ b[i];
|
||||
} else {
|
||||
val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
let ret;
|
||||
let U = [];
|
||||
|
||||
/* Encode i into 4 octets: _INT */
|
||||
let I = [];
|
||||
I[0] = String.fromCharCode((i >> 24) & 0xff);
|
||||
I[1] = String.fromCharCode((i >> 16) & 0xff);
|
||||
I[2] = String.fromCharCode((i >> 8) & 0xff);
|
||||
I[3] = String.fromCharCode(i & 0xff);
|
||||
|
||||
U[0] = CryptoUtils.digestBytes(S + I.join(''), h);
|
||||
for (let j = 1; j < c; j++) {
|
||||
U[j] = CryptoUtils.digestBytes(U[j - 1], h);
|
||||
}
|
||||
|
||||
ret = U[0];
|
||||
for (let j = 1; j < c; j++) {
|
||||
ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let l = Math.ceil(dkLen / hmacLen);
|
||||
let r = dkLen - ((l - 1) * hmacLen);
|
||||
|
||||
// Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
|
||||
let h = CryptoUtils.makeHMACHasher(hmacAlg,
|
||||
CryptoUtils.makeHMACKey(P));
|
||||
|
||||
let T = [];
|
||||
for (let i = 0; i < l;) {
|
||||
T[i] = F(S, c, ++i, h);
|
||||
}
|
||||
|
||||
let ret = "";
|
||||
for (let i = 0; i < l-1;) {
|
||||
ret += T[i++];
|
||||
}
|
||||
ret += T[l - 1].substr(0, r);
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase,
|
||||
salt,
|
||||
keyLength,
|
||||
forceJS) {
|
||||
if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
|
||||
return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
|
||||
}
|
||||
else {
|
||||
// Fall back to JS implementation.
|
||||
// 4096 is hardcoded in WeaveCrypto, so do so here.
|
||||
return CryptoUtils.pbkdf2Generate(passphrase, atob(salt), 4096,
|
||||
keyLength);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the HTTP MAC SHA-1 for an HTTP request.
|
||||
*
|
||||
* @param identifier
|
||||
* (string) MAC Key Identifier.
|
||||
* @param key
|
||||
* (string) MAC Key.
|
||||
* @param method
|
||||
* (string) HTTP request method.
|
||||
* @param URI
|
||||
* (nsIURI) HTTP request URI.
|
||||
* @param extra
|
||||
* (object) Optional extra parameters. Valid keys are:
|
||||
* nonce_bytes - How many bytes the nonce should be. This defaults
|
||||
* to 8. Note that this many bytes are Base64 encoded, so the
|
||||
* string length of the nonce will be longer than this value.
|
||||
* ts - Timestamp to use. Should only be defined for testing.
|
||||
* nonce - String nonce. Should only be defined for testing as this
|
||||
* function will generate a cryptographically secure random one
|
||||
* if not defined.
|
||||
* ext - Extra string to be included in MAC. Per the HTTP MAC spec,
|
||||
* the format is undefined and thus application specific.
|
||||
* @returns
|
||||
* (object) Contains results of operation and input arguments (for
|
||||
* symmetry). The object has the following keys:
|
||||
*
|
||||
* identifier - (string) MAC Key Identifier (from arguments).
|
||||
* key - (string) MAC Key (from arguments).
|
||||
* method - (string) HTTP request method (from arguments).
|
||||
* hostname - (string) HTTP hostname used (derived from arguments).
|
||||
* port - (string) HTTP port number used (derived from arguments).
|
||||
* mac - (string) Raw HMAC digest bytes.
|
||||
* getHeader - (function) Call to obtain the string Authorization
|
||||
* header value for this invocation.
|
||||
* nonce - (string) Nonce value used.
|
||||
* ts - (number) Integer seconds since Unix epoch that was used.
|
||||
*/
|
||||
computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
|
||||
uri, extra) {
|
||||
let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
|
||||
let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
|
||||
|
||||
// We are allowed to use more than the Base64 alphabet if we want.
|
||||
let nonce = (extra && extra.nonce)
|
||||
? extra.nonce
|
||||
: btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
|
||||
|
||||
let host = uri.asciiHost;
|
||||
let port;
|
||||
let usedMethod = method.toUpperCase();
|
||||
|
||||
if (uri.port != -1) {
|
||||
port = uri.port;
|
||||
} else if (uri.scheme == "http") {
|
||||
port = "80";
|
||||
} else if (uri.scheme == "https") {
|
||||
port = "443";
|
||||
} else {
|
||||
throw new Error("Unsupported URI scheme: " + uri.scheme);
|
||||
}
|
||||
|
||||
let ext = (extra && extra.ext) ? extra.ext : "";
|
||||
|
||||
let requestString = ts.toString(10) + "\n" +
|
||||
nonce + "\n" +
|
||||
usedMethod + "\n" +
|
||||
uri.path + "\n" +
|
||||
host + "\n" +
|
||||
port + "\n" +
|
||||
ext + "\n";
|
||||
|
||||
let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
|
||||
CryptoUtils.makeHMACKey(key));
|
||||
let mac = CryptoUtils.digestBytes(requestString, hasher);
|
||||
|
||||
function getHeader() {
|
||||
return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
|
||||
this.nonce, this.mac, this.ext);
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: identifier,
|
||||
key: key,
|
||||
method: usedMethod,
|
||||
hostname: host,
|
||||
port: port,
|
||||
mac: mac,
|
||||
nonce: nonce,
|
||||
ts: ts,
|
||||
ext: ext,
|
||||
getHeader: getHeader
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the HTTP MAC Authorization header value from fields.
|
||||
*
|
||||
* @param identifier
|
||||
* (string) MAC key identifier.
|
||||
* @param ts
|
||||
* (number) Integer seconds since Unix epoch.
|
||||
* @param nonce
|
||||
* (string) Nonce value.
|
||||
* @param mac
|
||||
* (string) Computed HMAC digest (raw bytes).
|
||||
* @param ext
|
||||
* (optional) (string) Extra string content.
|
||||
* @returns
|
||||
* (string) Value to put in Authorization header.
|
||||
*/
|
||||
getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
|
||||
mac, ext) {
|
||||
let header ='MAC id="' + identifier + '", ' +
|
||||
'ts="' + ts + '", ' +
|
||||
'nonce="' + nonce + '", ' +
|
||||
'mac="' + btoa(mac) + '"';
|
||||
|
||||
if (!ext) {
|
||||
return header;
|
||||
}
|
||||
|
||||
return header += ', ext="' + ext +'"';
|
||||
},
|
||||
|
||||
/**
|
||||
* Given an HTTP header value, strip out any attributes.
|
||||
*/
|
||||
|
||||
stripHeaderAttributes: function(value) {
|
||||
value = value || "";
|
||||
let i = value.indexOf(";");
|
||||
return value.substring(0, (i >= 0) ? i : undefined).trim().toLowerCase();
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the HAWK client values (mostly the header) for an HTTP request.
|
||||
*
|
||||
* @param URI
|
||||
* (nsIURI) HTTP request URI.
|
||||
* @param method
|
||||
* (string) HTTP request method.
|
||||
* @param options
|
||||
* (object) extra parameters (all but "credentials" are optional):
|
||||
* credentials - (object, mandatory) HAWK credentials object.
|
||||
* All three keys are required:
|
||||
* id - (string) key identifier
|
||||
* key - (string) raw key bytes
|
||||
* algorithm - (string) which hash to use: "sha1" or "sha256"
|
||||
* ext - (string) application-specific data, included in MAC
|
||||
* localtimeOffsetMsec - (number) local clock offset (vs server)
|
||||
* payload - (string) payload to include in hash, containing the
|
||||
* HTTP request body. If not provided, the HAWK hash
|
||||
* will not cover the request body, and the server
|
||||
* should not check it either. This will be UTF-8
|
||||
* encoded into bytes before hashing. This function
|
||||
* cannot handle arbitrary binary data, sorry (the
|
||||
* UTF-8 encoding process will corrupt any codepoints
|
||||
* between U+0080 and U+00FF). Callers must be careful
|
||||
* to use an HTTP client function which encodes the
|
||||
* payload exactly the same way, otherwise the hash
|
||||
* will not match.
|
||||
* contentType - (string) payload Content-Type. This is included
|
||||
* (without any attributes like "charset=") in the
|
||||
* HAWK hash. It does *not* affect interpretation
|
||||
* of the "payload" property.
|
||||
* hash - (base64 string) pre-calculated payload hash. If
|
||||
* provided, "payload" is ignored.
|
||||
* ts - (number) pre-calculated timestamp, secs since epoch
|
||||
* now - (number) current time, ms-since-epoch, for tests
|
||||
* nonce - (string) pre-calculated nonce. Should only be defined
|
||||
* for testing as this function will generate a
|
||||
* cryptographically secure random one if not defined.
|
||||
* @returns
|
||||
* (object) Contains results of operation. The object has the
|
||||
* following keys:
|
||||
* field - (string) HAWK header, to use in Authorization: header
|
||||
* artifacts - (object) other generated values:
|
||||
* ts - (number) timestamp, in seconds since epoch
|
||||
* nonce - (string)
|
||||
* method - (string)
|
||||
* resource - (string) path plus querystring
|
||||
* host - (string)
|
||||
* port - (number)
|
||||
* hash - (string) payload hash (base64)
|
||||
* ext - (string) app-specific data
|
||||
* MAC - (string) request MAC (base64)
|
||||
*/
|
||||
computeHAWK: function(uri, method, options) {
|
||||
let credentials = options.credentials;
|
||||
let ts = options.ts || Math.floor(((options.now || Date.now()) +
|
||||
(options.localtimeOffsetMsec || 0))
|
||||
/ 1000);
|
||||
|
||||
let hash_algo, hmac_algo;
|
||||
if (credentials.algorithm == "sha1") {
|
||||
hash_algo = Ci.nsICryptoHash.SHA1;
|
||||
hmac_algo = Ci.nsICryptoHMAC.SHA1;
|
||||
} else if (credentials.algorithm == "sha256") {
|
||||
hash_algo = Ci.nsICryptoHash.SHA256;
|
||||
hmac_algo = Ci.nsICryptoHMAC.SHA256;
|
||||
} else {
|
||||
throw new Error("Unsupported algorithm: " + credentials.algorithm);
|
||||
}
|
||||
|
||||
let port;
|
||||
if (uri.port != -1) {
|
||||
port = uri.port;
|
||||
} else if (uri.scheme == "http") {
|
||||
port = 80;
|
||||
} else if (uri.scheme == "https") {
|
||||
port = 443;
|
||||
} else {
|
||||
throw new Error("Unsupported URI scheme: " + uri.scheme);
|
||||
}
|
||||
|
||||
let artifacts = {
|
||||
ts: ts,
|
||||
nonce: options.nonce || btoa(CryptoUtils.generateRandomBytes(8)),
|
||||
method: method.toUpperCase(),
|
||||
resource: uri.path, // This includes both path and search/queryarg.
|
||||
host: uri.asciiHost.toLowerCase(), // This includes punycoding.
|
||||
port: port.toString(10),
|
||||
hash: options.hash,
|
||||
ext: options.ext,
|
||||
};
|
||||
|
||||
let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);
|
||||
|
||||
if (!artifacts.hash && options.hasOwnProperty("payload")
|
||||
&& options.payload) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hash_algo);
|
||||
CryptoUtils.updateUTF8("hawk.1.payload\n", hasher);
|
||||
CryptoUtils.updateUTF8(contentType+"\n", hasher);
|
||||
CryptoUtils.updateUTF8(options.payload, hasher);
|
||||
CryptoUtils.updateUTF8("\n", hasher);
|
||||
let hash = hasher.finish(false);
|
||||
// HAWK specifies this .hash to use +/ (not _-) and include the
|
||||
// trailing "==" padding.
|
||||
let hash_b64 = btoa(hash);
|
||||
artifacts.hash = hash_b64;
|
||||
}
|
||||
|
||||
let requestString = ("hawk.1.header" + "\n" +
|
||||
artifacts.ts.toString(10) + "\n" +
|
||||
artifacts.nonce + "\n" +
|
||||
artifacts.method + "\n" +
|
||||
artifacts.resource + "\n" +
|
||||
artifacts.host + "\n" +
|
||||
artifacts.port + "\n" +
|
||||
(artifacts.hash || "") + "\n");
|
||||
if (artifacts.ext) {
|
||||
requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
|
||||
}
|
||||
requestString += "\n";
|
||||
|
||||
let hasher = CryptoUtils.makeHMACHasher(hmac_algo,
|
||||
CryptoUtils.makeHMACKey(credentials.key));
|
||||
artifacts.mac = btoa(CryptoUtils.digestBytes(requestString, hasher));
|
||||
// The output MAC uses "+" and "/", and padded== .
|
||||
|
||||
function escape(attribute) {
|
||||
// This is used for "x=y" attributes inside HTTP headers.
|
||||
return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
|
||||
}
|
||||
let header = ('Hawk id="' + credentials.id + '", ' +
|
||||
'ts="' + artifacts.ts + '", ' +
|
||||
'nonce="' + artifacts.nonce + '", ' +
|
||||
(artifacts.hash ? ('hash="' + artifacts.hash + '", ') : "") +
|
||||
(artifacts.ext ? ('ext="' + escape(artifacts.ext) + '", ') : "") +
|
||||
'mac="' + artifacts.mac + '"');
|
||||
return {
|
||||
artifacts: artifacts,
|
||||
field: header,
|
||||
};
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
return converter;
|
||||
});
|
||||
|
||||
var Svc = {};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(Svc,
|
||||
"KeyFactory",
|
||||
"@mozilla.org/security/keyobjectfactory;1",
|
||||
"nsIKeyObjectFactory");
|
||||
|
||||
Svc.__defineGetter__("Crypto", function() {
|
||||
let ns = {};
|
||||
Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
|
||||
|
||||
let wc = new ns.WeaveCrypto();
|
||||
delete Svc.Crypto;
|
||||
return Svc.Crypto = wc;
|
||||
});
|
||||
|
||||
Observers.add("xpcom-shutdown", function unloadServices() {
|
||||
Observers.remove("xpcom-shutdown", unloadServices);
|
||||
|
||||
for (let k in Svc) {
|
||||
delete Svc[k];
|
||||
}
|
||||
});
|
||||
21
services/crypto/moz.build
Normal file
21
services/crypto/moz.build
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Mozilla Services', 'Firefox Sync: Crypto')
|
||||
|
||||
DIRS += ['component']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
EXTRA_JS_MODULES['services-crypto'] += [
|
||||
'modules/utils.js',
|
||||
'modules/WeaveCrypto.js',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'cryptoComponents.manifest',
|
||||
]
|
||||
55
services/crypto/tests/unit/head_helpers.js
Normal file
55
services/crypto/tests/unit/head_helpers.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
var Cc = Components.classes;
|
||||
var Ci = Components.interfaces;
|
||||
var Cr = Components.results;
|
||||
var Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
try {
|
||||
// In the context of xpcshell tests, there won't be a default AppInfo
|
||||
Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
|
||||
}
|
||||
catch(ex) {
|
||||
|
||||
// Make sure to provide the right OS so crypto loads the right binaries
|
||||
var OS = "XPCShell";
|
||||
if (mozinfo.os == "win")
|
||||
OS = "WINNT";
|
||||
else if (mozinfo.os == "mac")
|
||||
OS = "Darwin";
|
||||
else
|
||||
OS = "Linux";
|
||||
|
||||
Cu.import("resource://testing-common/AppInfo.jsm", this);
|
||||
updateAppInfo({
|
||||
name: "XPCShell",
|
||||
ID: "{3e3ba16c-1675-4e88-b9c8-afef81b3d2ef}",
|
||||
version: "1",
|
||||
platformVersion: "",
|
||||
OS: OS,
|
||||
});
|
||||
}
|
||||
|
||||
// Register resource alias. Normally done in SyncComponents.manifest.
|
||||
function addResourceAlias() {
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
const resProt = Services.io.getProtocolHandler("resource")
|
||||
.QueryInterface(Ci.nsIResProtocolHandler);
|
||||
let uri = Services.io.newURI("resource://gre/modules/services-crypto/",
|
||||
null, null);
|
||||
resProt.setSubstitution("services-crypto", uri);
|
||||
}
|
||||
addResourceAlias();
|
||||
|
||||
/**
|
||||
* Print some debug message to the console. All arguments will be printed,
|
||||
* separated by spaces.
|
||||
*
|
||||
* @param [arg0, arg1, arg2, ...]
|
||||
* Any number of arguments to print out
|
||||
* @usage _("Hello World") -> prints "Hello World"
|
||||
* @usage _(1, 2, 3) -> prints "1 2 3"
|
||||
*/
|
||||
var _ = function(some, debug, text, to) {
|
||||
print(Array.slice(arguments).join(" "));
|
||||
};
|
||||
213
services/crypto/tests/unit/test_crypto_crypt.js
Normal file
213
services/crypto/tests/unit/test_crypto_crypt.js
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
Cu.import("resource://services-crypto/WeaveCrypto.js");
|
||||
Cu.importGlobalProperties(['crypto']);
|
||||
|
||||
var cryptoSvc = new WeaveCrypto();
|
||||
|
||||
add_task(function* test_key_memoization() {
|
||||
let cryptoGlobal = cryptoSvc._getCrypto();
|
||||
let oldImport = cryptoGlobal.subtle.importKey;
|
||||
if (!oldImport) {
|
||||
_("Couldn't swizzle crypto.subtle.importKey; returning.");
|
||||
return;
|
||||
}
|
||||
|
||||
let iv = cryptoSvc.generateRandomIV();
|
||||
let key = cryptoSvc.generateRandomKey();
|
||||
let c = 0;
|
||||
cryptoGlobal.subtle.importKey = function(format, keyData, algo, extractable, usages) {
|
||||
c++;
|
||||
return oldImport.call(cryptoGlobal.subtle, format, keyData, algo, extractable, usages);
|
||||
}
|
||||
|
||||
// Encryption should cause a single counter increment.
|
||||
do_check_eq(c, 0);
|
||||
let cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
|
||||
do_check_eq(c, 1);
|
||||
cipherText = cryptoSvc.encrypt("Hello, world.", key, iv);
|
||||
do_check_eq(c, 1);
|
||||
|
||||
// ... as should decryption.
|
||||
cryptoSvc.decrypt(cipherText, key, iv);
|
||||
cryptoSvc.decrypt(cipherText, key, iv);
|
||||
cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(c, 2);
|
||||
|
||||
// Un-swizzle.
|
||||
cryptoGlobal.subtle.importKey = oldImport;
|
||||
});
|
||||
|
||||
// Just verify that it gets populated with the correct bytes.
|
||||
add_task(function* test_makeUint8Array() {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
|
||||
let item1 = cryptoSvc.makeUint8Array("abcdefghi", false);
|
||||
do_check_true(item1);
|
||||
for (let i = 0; i < 8; ++i)
|
||||
do_check_eq(item1[i], "abcdefghi".charCodeAt(i));
|
||||
});
|
||||
|
||||
add_task(function* test_encrypt_decrypt() {
|
||||
// First, do a normal run with expected usage... Generate a random key and
|
||||
// iv, encrypt and decrypt a string.
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
var key = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(key.length, 44);
|
||||
|
||||
var mySecret = "bacon is a vegetable";
|
||||
var cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
do_check_eq(cipherText.length, 44);
|
||||
|
||||
var clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(clearText.length, 20);
|
||||
|
||||
// Did the text survive the encryption round-trip?
|
||||
do_check_eq(clearText, mySecret);
|
||||
do_check_neq(cipherText, mySecret); // just to be explicit
|
||||
|
||||
|
||||
// Do some more tests with a fixed key/iv, to check for reproducable results.
|
||||
key = "St1tFCor7vQEJNug/465dQ==";
|
||||
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
|
||||
|
||||
_("Testing small IV.");
|
||||
mySecret = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=";
|
||||
let shortiv = "YWJj";
|
||||
let err;
|
||||
try {
|
||||
cryptoSvc.encrypt(mySecret, key, shortiv);
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_true(!!err);
|
||||
|
||||
_("Testing long IV.");
|
||||
let longiv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk=";
|
||||
try {
|
||||
cryptoSvc.encrypt(mySecret, key, longiv);
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_true(!!err);
|
||||
|
||||
// Test small input sizes
|
||||
mySecret = "";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "OGQjp6mK1a3fs9k9Ml4L3w==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "x";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "96iMl4vhOxFUW/lVHHzVqg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "xx";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "olpPbETRYROCSqFWcH2SWg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "xxx";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "rRbpHGyVSZizLX/x43Wm+Q==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "xxxx";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "HeC7miVGDcpxae9RmiIKAw==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
// Test non-ascii input
|
||||
// ("testuser1" using similar-looking glyphs)
|
||||
mySecret = String.fromCharCode(355, 277, 349, 357, 533, 537, 101, 345, 185);
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "Pj4ixByXoH3SU3JkOXaEKPgwRAWplAWFLQZkpJd5Kr4=");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
// Tests input spanning a block boundary (AES block size is 16 bytes)
|
||||
mySecret = "123456789012345";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "e6c5hwphe45/3VN/M0bMUA==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "1234567890123456";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP1JOIQF87E2vTUvBUQnyV04=");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "12345678901234567";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP5GvxWJ9+GIAS6lXw+5fHTI=");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
|
||||
key = "iz35tuIMq4/H+IYw2KTgow==";
|
||||
iv = "TJYrvva2KxvkM8hvOIvWp3==";
|
||||
mySecret = "i like pie";
|
||||
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "DLGx8BWqSCLGG7i/xwvvxg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI=";
|
||||
iv = "gsgLRDaxWvIfKt75RjuvFW==";
|
||||
mySecret = "i like pie";
|
||||
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "o+ADtdMd8ubzNWurS6jt0Q==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
key = "St1tFCor7vQEJNug/465dQ==";
|
||||
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
|
||||
mySecret = "does thunder read testcases?";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
do_check_eq(cipherText, "T6fik9Ros+DB2ablH9zZ8FWZ0xm/szSwJjIHZu7sjPs=");
|
||||
|
||||
var badkey = "badkeybadkeybadkeybadk==";
|
||||
var badiv = "badivbadivbadivbadivbad=";
|
||||
var badcipher = "crapinputcrapinputcrapinputcrapinputcrapinp=";
|
||||
var failure;
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(cipherText, badkey, iv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, badiv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(cipherText, badkey, badiv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(badcipher, key, iv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
});
|
||||
28
services/crypto/tests/unit/test_crypto_deriveKey.js
Normal file
28
services/crypto/tests/unit/test_crypto_deriveKey.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
Components.utils.import("resource://services-crypto/WeaveCrypto.js");
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc = new WeaveCrypto();
|
||||
// Extracted from test_utils_deriveKey.
|
||||
let pp = "secret phrase";
|
||||
let salt = "RE5YUHpQcGl3bg=="; // btoa("DNXPzPpiwn")
|
||||
|
||||
// 16-byte, extract key data.
|
||||
let k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, 16);
|
||||
do_check_eq(16, k.length);
|
||||
do_check_eq(btoa(k), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
|
||||
// Test different key lengths.
|
||||
k = cryptoSvc.deriveKeyFromPassphrase(pp, salt, 32);
|
||||
do_check_eq(32, k.length);
|
||||
do_check_eq(btoa(k), "d2zG0d2cBfXnRwMUGyMwyroRXtnrSIeLwSDvReSfcyA=");
|
||||
let encKey = btoa(k);
|
||||
|
||||
// Test via encryption.
|
||||
let iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(cryptoSvc.decrypt(cryptoSvc.encrypt("bacon", encKey, iv), encKey, iv), "bacon");
|
||||
|
||||
// Test default length (32).
|
||||
k = cryptoSvc.deriveKeyFromPassphrase(pp, salt);
|
||||
do_check_eq(32, k.length);
|
||||
do_check_eq(encKey, btoa(k));
|
||||
}
|
||||
58
services/crypto/tests/unit/test_crypto_random.js
Normal file
58
services/crypto/tests/unit/test_crypto_random.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
var WeaveCryptoModule = Cu.import("resource://services-crypto/WeaveCrypto.js");
|
||||
|
||||
var cryptoSvc = new WeaveCrypto();
|
||||
|
||||
function run_test() {
|
||||
if (this.gczeal) {
|
||||
_("Running crypto random tests with gczeal(2).");
|
||||
gczeal(2);
|
||||
}
|
||||
|
||||
// Test salt generation.
|
||||
var salt;
|
||||
|
||||
salt = cryptoSvc.generateRandomBytes(0);
|
||||
do_check_eq(salt.length, 0);
|
||||
salt = cryptoSvc.generateRandomBytes(1);
|
||||
do_check_eq(salt.length, 4);
|
||||
salt = cryptoSvc.generateRandomBytes(2);
|
||||
do_check_eq(salt.length, 4);
|
||||
salt = cryptoSvc.generateRandomBytes(3);
|
||||
do_check_eq(salt.length, 4);
|
||||
salt = cryptoSvc.generateRandomBytes(4);
|
||||
do_check_eq(salt.length, 8);
|
||||
salt = cryptoSvc.generateRandomBytes(8);
|
||||
do_check_eq(salt.length, 12);
|
||||
|
||||
// sanity check to make sure salts seem random
|
||||
var salt2 = cryptoSvc.generateRandomBytes(8);
|
||||
do_check_eq(salt2.length, 12);
|
||||
do_check_neq(salt, salt2);
|
||||
|
||||
salt = cryptoSvc.generateRandomBytes(1024);
|
||||
do_check_eq(salt.length, 1368);
|
||||
salt = cryptoSvc.generateRandomBytes(16);
|
||||
do_check_eq(salt.length, 24);
|
||||
|
||||
|
||||
// Test random key generation
|
||||
var keydata, keydata2, iv;
|
||||
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 44);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
cryptoSvc.algorithm = WeaveCryptoModule.AES_256_CBC;
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 44);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
if (this.gczeal)
|
||||
gczeal(0);
|
||||
}
|
||||
16
services/crypto/tests/unit/test_load_modules.js
Normal file
16
services/crypto/tests/unit/test_load_modules.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const modules = [
|
||||
"utils.js",
|
||||
"WeaveCrypto.js",
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for (let m of modules) {
|
||||
let resource = "resource://services-crypto/" + m;
|
||||
_("Attempting to import: " + resource);
|
||||
Components.utils.import(resource, {});
|
||||
}
|
||||
}
|
||||
|
||||
301
services/crypto/tests/unit/test_utils_hawk.js
Normal file
301
services/crypto/tests/unit/test_utils_hawk.js
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
initTestLogging();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_hawk() {
|
||||
let compute = CryptoUtils.computeHAWK;
|
||||
|
||||
// vectors copied from the HAWK (node.js) tests
|
||||
let credentials_sha1 = {
|
||||
id: "123456",
|
||||
key: "2983d45yun89q",
|
||||
algorithm: "sha1",
|
||||
};
|
||||
|
||||
let method = "POST";
|
||||
let ts = 1353809207;
|
||||
let nonce = "Ygvqdz";
|
||||
let result;
|
||||
|
||||
let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
|
||||
let sha1_opts = { credentials: credentials_sha1,
|
||||
ext: "Bazinga!",
|
||||
ts: ts,
|
||||
nonce: nonce,
|
||||
payload: "something to write about",
|
||||
};
|
||||
result = compute(uri_http, method, sha1_opts);
|
||||
|
||||
// The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
|
||||
do_check_eq(result.field,
|
||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||
'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
|
||||
'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
|
||||
);
|
||||
do_check_eq(result.artifacts.ts, ts);
|
||||
do_check_eq(result.artifacts.nonce, nonce);
|
||||
do_check_eq(result.artifacts.method, method);
|
||||
do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
|
||||
do_check_eq(result.artifacts.host, "example.net");
|
||||
do_check_eq(result.artifacts.port, 80);
|
||||
// artifacts.hash is the *payload* hash, not the overall request MAC.
|
||||
do_check_eq(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
|
||||
do_check_eq(result.artifacts.ext, "Bazinga!");
|
||||
|
||||
let credentials_sha256 = {
|
||||
id: "123456",
|
||||
key: "2983d45yun89q",
|
||||
algorithm: "sha256",
|
||||
};
|
||||
|
||||
let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
|
||||
let sha256_opts = { credentials: credentials_sha256,
|
||||
ext: "Bazinga!",
|
||||
ts: ts,
|
||||
nonce: nonce,
|
||||
payload: "something to write about",
|
||||
contentType: "text/plain",
|
||||
};
|
||||
|
||||
result = compute(uri_https, method, sha256_opts);
|
||||
do_check_eq(result.field,
|
||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||
'ext="Bazinga!", ' +
|
||||
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
|
||||
);
|
||||
do_check_eq(result.artifacts.ts, ts);
|
||||
do_check_eq(result.artifacts.nonce, nonce);
|
||||
do_check_eq(result.artifacts.method, method);
|
||||
do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
|
||||
do_check_eq(result.artifacts.host, "example.net");
|
||||
do_check_eq(result.artifacts.port, 443);
|
||||
do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
|
||||
do_check_eq(result.artifacts.ext, "Bazinga!");
|
||||
|
||||
let sha256_opts_noext = { credentials: credentials_sha256,
|
||||
ts: ts,
|
||||
nonce: nonce,
|
||||
payload: "something to write about",
|
||||
contentType: "text/plain",
|
||||
};
|
||||
result = compute(uri_https, method, sha256_opts_noext);
|
||||
do_check_eq(result.field,
|
||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||
'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
|
||||
);
|
||||
do_check_eq(result.artifacts.ts, ts);
|
||||
do_check_eq(result.artifacts.nonce, nonce);
|
||||
do_check_eq(result.artifacts.method, method);
|
||||
do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
|
||||
do_check_eq(result.artifacts.host, "example.net");
|
||||
do_check_eq(result.artifacts.port, 443);
|
||||
do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
|
||||
|
||||
/* Leaving optional fields out should work, although of course then we can't
|
||||
* assert much about the resulting hashes. The resulting header should look
|
||||
* roughly like:
|
||||
* Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
|
||||
*/
|
||||
|
||||
result = compute(uri_https, method, { credentials: credentials_sha256 });
|
||||
let fields = result.field.split(" ");
|
||||
do_check_eq(fields[0], "Hawk");
|
||||
do_check_eq(fields[1], 'id="123456",'); // from creds.id
|
||||
do_check_true(fields[2].startsWith('ts="'));
|
||||
/* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
|
||||
* Warning: this test will fail in the year 33658, and for time travellers
|
||||
* who journey earlier than 2001. Please plan accordingly. */
|
||||
do_check_true(result.artifacts.ts > 1000*1000*1000);
|
||||
do_check_true(result.artifacts.ts < 1000*1000*1000*1000);
|
||||
do_check_true(fields[3].startsWith('nonce="'));
|
||||
do_check_eq(fields[3].length, ('nonce="12345678901=",').length);
|
||||
do_check_eq(result.artifacts.nonce.length, ("12345678901=").length);
|
||||
|
||||
let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
|
||||
do_check_neq(result.artifacts.nonce, result2.artifacts.nonce);
|
||||
|
||||
/* Using an upper-case URI hostname shouldn't affect the hash. */
|
||||
|
||||
let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
|
||||
result = compute(uri_https_upper, method, sha256_opts);
|
||||
do_check_eq(result.field,
|
||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||
'ext="Bazinga!", ' +
|
||||
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
|
||||
);
|
||||
|
||||
/* Using a lower-case method name shouldn't affect the hash. */
|
||||
result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
|
||||
do_check_eq(result.field,
|
||||
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
||||
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
||||
'ext="Bazinga!", ' +
|
||||
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
|
||||
);
|
||||
|
||||
/* The localtimeOffsetMsec field should be honored. HAWK uses this to
|
||||
* compensate for clock skew between client and server: if the request is
|
||||
* rejected with a timestamp out-of-range error, the error includes the
|
||||
* server's time, and the client computes its clock offset and tries again.
|
||||
* Clients can remember this offset for a while.
|
||||
*/
|
||||
|
||||
result = compute(uri_https, method, { credentials: credentials_sha256,
|
||||
now: 1378848968650,
|
||||
});
|
||||
do_check_eq(result.artifacts.ts, 1378848968);
|
||||
|
||||
result = compute(uri_https, method, { credentials: credentials_sha256,
|
||||
now: 1378848968650,
|
||||
localtimeOffsetMsec: 1000*1000,
|
||||
});
|
||||
do_check_eq(result.artifacts.ts, 1378848968 + 1000);
|
||||
|
||||
/* Search/query-args in URIs should be included in the hash. */
|
||||
let makeURI = CommonUtils.makeURI;
|
||||
result = compute(makeURI("http://example.net/path"), method, sha256_opts);
|
||||
do_check_eq(result.artifacts.resource, "/path");
|
||||
do_check_eq(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
|
||||
|
||||
result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
|
||||
do_check_eq(result.artifacts.resource, "/path/");
|
||||
do_check_eq(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
|
||||
|
||||
result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
|
||||
do_check_eq(result.artifacts.resource, "/path?query=search");
|
||||
do_check_eq(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
|
||||
|
||||
/* Test handling of the payload, which is supposed to be a bytestring
|
||||
(String with codepoints from U+0000 to U+00FF, pre-encoded). */
|
||||
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
});
|
||||
do_check_eq(result.artifacts.hash, undefined);
|
||||
do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
|
||||
|
||||
// Empty payload changes nothing.
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
payload: null,
|
||||
});
|
||||
do_check_eq(result.artifacts.hash, undefined);
|
||||
do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
|
||||
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
payload: "hello",
|
||||
});
|
||||
do_check_eq(result.artifacts.hash, "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=");
|
||||
do_check_eq(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
|
||||
|
||||
// update, utf-8 payload
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
payload: "andré@example.org", // non-ASCII
|
||||
});
|
||||
do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
|
||||
do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
|
||||
|
||||
/* If "hash" is provided, "payload" is ignored. */
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
|
||||
payload: "something else",
|
||||
});
|
||||
do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
|
||||
do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
|
||||
|
||||
// the payload "hash" is also non-urlsafe base64 (+/)
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
payload: "something else",
|
||||
});
|
||||
do_check_eq(result.artifacts.hash, "lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8=");
|
||||
do_check_eq(result.artifacts.mac, "jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ=");
|
||||
|
||||
/* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
|
||||
* "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
|
||||
* punycode was a bad joke that got out of the lab and into a spec.
|
||||
*/
|
||||
|
||||
result = compute(makeURI("http://ëxample.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
});
|
||||
do_check_eq(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
|
||||
do_check_eq(result.artifacts.host, "xn--xample-ova.net");
|
||||
|
||||
result = compute(makeURI("http://example.net/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
ext: "backslash=\\ quote=\" EOF",
|
||||
});
|
||||
do_check_eq(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
|
||||
do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
|
||||
|
||||
result = compute(makeURI("http://example.net:1234/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
});
|
||||
do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
|
||||
do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
|
||||
|
||||
/* HAWK (the node.js library) uses a URL parser which stores the "port"
|
||||
* field as a string, but makeURI() gives us an integer. So we'll diverge
|
||||
* on ports with a leading zero. This test vector would fail on the node.js
|
||||
* library (HAWK-1.1.1), where they get a MAC of
|
||||
* "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
|
||||
* updated to do what we do here, so port="01234" should get the same hash
|
||||
* as port="1234".
|
||||
*/
|
||||
result = compute(makeURI("http://example.net:01234/path"), method,
|
||||
{ credentials: credentials_sha256,
|
||||
ts: 1353809207,
|
||||
nonce: "Ygvqdz",
|
||||
});
|
||||
do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
|
||||
do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
|
||||
add_test(function test_strip_header_attributes() {
|
||||
let strip = CryptoUtils.stripHeaderAttributes;
|
||||
|
||||
do_check_eq(strip(undefined), "");
|
||||
do_check_eq(strip("text/plain"), "text/plain");
|
||||
do_check_eq(strip("TEXT/PLAIN"), "text/plain");
|
||||
do_check_eq(strip(" text/plain "), "text/plain");
|
||||
do_check_eq(strip("text/plain ; charset=utf-8 "), "text/plain");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
120
services/crypto/tests/unit/test_utils_hkdfExpand.js
Normal file
120
services/crypto/tests/unit/test_utils_hkdfExpand.js
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
// Test vectors from RFC 5869
|
||||
|
||||
// Test case 1
|
||||
|
||||
var tc1 = {
|
||||
IKM: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
||||
salt: "000102030405060708090a0b0c",
|
||||
info: "f0f1f2f3f4f5f6f7f8f9",
|
||||
L: 42,
|
||||
PRK: "077709362c2e32df0ddc3f0dc47bba63" +
|
||||
"90b6c73bb50f9c3122ec844ad7c2b3e5",
|
||||
OKM: "3cb25f25faacd57a90434f64d0362f2a" +
|
||||
"2d2d0a90cf1a5a4c5db02d56ecc4c5bf" +
|
||||
"34007208d5b887185865"
|
||||
};
|
||||
|
||||
// Test case 2
|
||||
|
||||
var tc2 = {
|
||||
IKM: "000102030405060708090a0b0c0d0e0f" +
|
||||
"101112131415161718191a1b1c1d1e1f" +
|
||||
"202122232425262728292a2b2c2d2e2f" +
|
||||
"303132333435363738393a3b3c3d3e3f" +
|
||||
"404142434445464748494a4b4c4d4e4f",
|
||||
salt: "606162636465666768696a6b6c6d6e6f" +
|
||||
"707172737475767778797a7b7c7d7e7f" +
|
||||
"808182838485868788898a8b8c8d8e8f" +
|
||||
"909192939495969798999a9b9c9d9e9f" +
|
||||
"a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
|
||||
info: "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" +
|
||||
"c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" +
|
||||
"d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" +
|
||||
"e0e1e2e3e4e5e6e7e8e9eaebecedeeef" +
|
||||
"f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
|
||||
L: 82,
|
||||
PRK: "06a6b88c5853361a06104c9ceb35b45c" +
|
||||
"ef760014904671014a193f40c15fc244",
|
||||
OKM: "b11e398dc80327a1c8e7f78c596a4934" +
|
||||
"4f012eda2d4efad8a050cc4c19afa97c" +
|
||||
"59045a99cac7827271cb41c65e590e09" +
|
||||
"da3275600c2f09b8367793a9aca3db71" +
|
||||
"cc30c58179ec3e87c14c01d5c1f3434f" +
|
||||
"1d87"
|
||||
};
|
||||
|
||||
// Test case 3
|
||||
|
||||
var tc3 = {
|
||||
IKM: "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
|
||||
salt: "",
|
||||
info: "",
|
||||
L: 42,
|
||||
PRK: "19ef24a32c717b167f33a91d6f648bdf" +
|
||||
"96596776afdb6377ac434c1c293ccb04",
|
||||
OKM: "8da4e775a563c18f715f802a063c5a31" +
|
||||
"b8a11f5c5ee1879ec3454e5f3c738d2d" +
|
||||
"9d201395faa4b61a96c8"
|
||||
};
|
||||
|
||||
function sha256HMAC(message, key) {
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return CryptoUtils.digestBytes(message, h);
|
||||
}
|
||||
|
||||
function _hexToString(hex) {
|
||||
let ret = "";
|
||||
if (hex.length % 2 != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i = 0; i < hex.length; i += 2) {
|
||||
let cur = hex[i] + hex[i + 1];
|
||||
ret += String.fromCharCode(parseInt(cur, 16));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function extract_hex(salt, ikm) {
|
||||
salt = _hexToString(salt);
|
||||
ikm = _hexToString(ikm);
|
||||
return CommonUtils.bytesAsHex(sha256HMAC(ikm, CryptoUtils.makeHMACKey(salt)));
|
||||
}
|
||||
|
||||
function expand_hex(prk, info, len) {
|
||||
prk = _hexToString(prk);
|
||||
info = _hexToString(info);
|
||||
return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
|
||||
}
|
||||
|
||||
function hkdf_hex(ikm, salt, info, len) {
|
||||
ikm = _hexToString(ikm);
|
||||
if (salt)
|
||||
salt = _hexToString(salt);
|
||||
info = _hexToString(info);
|
||||
return CommonUtils.bytesAsHex(CryptoUtils.hkdf(ikm, salt, info, len));
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
_("Verifying Test Case 1");
|
||||
do_check_eq(extract_hex(tc1.salt, tc1.IKM), tc1.PRK);
|
||||
do_check_eq(expand_hex(tc1.PRK, tc1.info, tc1.L), tc1.OKM);
|
||||
do_check_eq(hkdf_hex(tc1.IKM, tc1.salt, tc1.info, tc1.L), tc1.OKM);
|
||||
|
||||
_("Verifying Test Case 2");
|
||||
do_check_eq(extract_hex(tc2.salt, tc2.IKM), tc2.PRK);
|
||||
do_check_eq(expand_hex(tc2.PRK, tc2.info, tc2.L), tc2.OKM);
|
||||
do_check_eq(hkdf_hex(tc2.IKM, tc2.salt, tc2.info, tc2.L), tc2.OKM);
|
||||
|
||||
_("Verifying Test Case 3");
|
||||
do_check_eq(extract_hex(tc3.salt, tc3.IKM), tc3.PRK);
|
||||
do_check_eq(expand_hex(tc3.PRK, tc3.info, tc3.L), tc3.OKM);
|
||||
do_check_eq(hkdf_hex(tc3.IKM, tc3.salt, tc3.info, tc3.L), tc3.OKM);
|
||||
do_check_eq(hkdf_hex(tc3.IKM, undefined, tc3.info, tc3.L), tc3.OKM);
|
||||
}
|
||||
69
services/crypto/tests/unit/test_utils_httpmac.js
Normal file
69
services/crypto/tests/unit/test_utils_httpmac.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
initTestLogging();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_sha1() {
|
||||
_("Ensure HTTP MAC SHA1 generation works as expected.");
|
||||
|
||||
let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
|
||||
let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz";
|
||||
let ts = 1329181221;
|
||||
let method = "GET";
|
||||
let nonce = "wGX71";
|
||||
let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
|
||||
|
||||
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||
{ts: ts, nonce: nonce});
|
||||
|
||||
do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
||||
|
||||
do_check_eq(result.getHeader(),
|
||||
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
||||
'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="');
|
||||
|
||||
let ext = "EXTRA DATA; foo,bar=1";
|
||||
|
||||
result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||
{ts: ts, nonce: nonce, ext: ext});
|
||||
do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
||||
do_check_eq(result.getHeader(),
|
||||
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
||||
'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
|
||||
'ext="EXTRA DATA; foo,bar=1"');
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_nonce_length() {
|
||||
_("Ensure custom nonce lengths are honoured.");
|
||||
|
||||
function get_mac(length) {
|
||||
let uri = CommonUtils.makeURI("http://example.com/");
|
||||
return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
||||
nonce_bytes: length
|
||||
});
|
||||
}
|
||||
|
||||
let result = get_mac(12);
|
||||
do_check_eq(12, atob(result.nonce).length);
|
||||
|
||||
result = get_mac(2);
|
||||
do_check_eq(2, atob(result.nonce).length);
|
||||
|
||||
result = get_mac(0);
|
||||
do_check_eq(8, atob(result.nonce).length);
|
||||
|
||||
result = get_mac(-1);
|
||||
do_check_eq(8, atob(result.nonce).length);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
162
services/crypto/tests/unit/test_utils_pbkdf2.js
Normal file
162
services/crypto/tests/unit/test_utils_pbkdf2.js
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// XXX until bug 937114 is fixed
|
||||
Cu.importGlobalProperties(['btoa']);
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
var {bytesAsHex: b2h} = CommonUtils;
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function test_pbkdf2() {
|
||||
let symmKey16 = CryptoUtils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
||||
do_check_eq(symmKey16.length, 16);
|
||||
do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
do_check_eq(CommonUtils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
||||
let symmKey32 = CryptoUtils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
||||
do_check_eq(symmKey32.length, 32);
|
||||
});
|
||||
|
||||
// http://tools.ietf.org/html/rfc6070
|
||||
// PBKDF2 HMAC-SHA1 Test Vectors
|
||||
add_task(function test_pbkdf2_hmac_sha1() {
|
||||
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
||||
let vectors = [
|
||||
{P: "password", // (8 octets)
|
||||
S: "salt", // (4 octets)
|
||||
c: 1,
|
||||
dkLen: 20,
|
||||
DK: h("0c 60 c8 0f 96 1f 0e 71"+
|
||||
"f3 a9 b5 24 af 60 12 06"+
|
||||
"2f e0 37 a6"), // (20 octets)
|
||||
},
|
||||
|
||||
{P: "password", // (8 octets)
|
||||
S: "salt", // (4 octets)
|
||||
c: 2,
|
||||
dkLen: 20,
|
||||
DK: h("ea 6c 01 4d c7 2d 6f 8c"+
|
||||
"cd 1e d9 2a ce 1d 41 f0"+
|
||||
"d8 de 89 57"), // (20 octets)
|
||||
},
|
||||
|
||||
{P: "password", // (8 octets)
|
||||
S: "salt", // (4 octets)
|
||||
c: 4096,
|
||||
dkLen: 20,
|
||||
DK: h("4b 00 79 01 b7 65 48 9a"+
|
||||
"be ad 49 d9 26 f7 21 d0"+
|
||||
"65 a4 29 c1"), // (20 octets)
|
||||
},
|
||||
|
||||
// XXX Uncomment the following test after Bug 968567 lands
|
||||
//
|
||||
// XXX As it stands, I estimate that the CryptoUtils implementation will
|
||||
// take approximately 16 hours in my 2.3GHz MacBook to perform this many
|
||||
// rounds.
|
||||
//
|
||||
// {P: "password", // (8 octets)
|
||||
// S: "salt" // (4 octets)
|
||||
// c: 16777216,
|
||||
// dkLen = 20,
|
||||
// DK: h("ee fe 3d 61 cd 4d a4 e4"+
|
||||
// "e9 94 5b 3d 6b a2 15 8c"+
|
||||
// "26 34 e9 84"), // (20 octets)
|
||||
// },
|
||||
|
||||
{P: "passwordPASSWORDpassword", // (24 octets)
|
||||
S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets)
|
||||
c: 4096,
|
||||
dkLen: 25,
|
||||
DK: h("3d 2e ec 4f e4 1c 84 9b"+
|
||||
"80 c8 d8 36 62 c0 e4 4a"+
|
||||
"8b 29 1a 96 4c f2 f0 70"+
|
||||
"38"), // (25 octets)
|
||||
|
||||
},
|
||||
|
||||
{P: "pass\0word", // (9 octets)
|
||||
S: "sa\0lt", // (5 octets)
|
||||
c: 4096,
|
||||
dkLen: 16,
|
||||
DK: h("56 fa 6a a7 55 48 09 9d"+
|
||||
"cc 37 d7 f0 34 25 e0 c3"), // (16 octets)
|
||||
},
|
||||
];
|
||||
|
||||
for (let v of vectors) {
|
||||
do_check_eq(v.DK, b2h(pbkdf2(v.P, v.S, v.c, v.dkLen)));
|
||||
}
|
||||
});
|
||||
|
||||
// I can't find any normative ietf test vectors for pbkdf2 hmac-sha256.
|
||||
// The following vectors are derived with the same inputs as above (the sha1
|
||||
// test). Results verified by users here:
|
||||
// https://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
|
||||
add_task(function test_pbkdf2_hmac_sha256() {
|
||||
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
||||
let vectors = [
|
||||
{P: "password", // (8 octets)
|
||||
S: "salt", // (4 octets)
|
||||
c: 1,
|
||||
dkLen: 32,
|
||||
DK: h("12 0f b6 cf fc f8 b3 2c"+
|
||||
"43 e7 22 52 56 c4 f8 37"+
|
||||
"a8 65 48 c9 2c cc 35 48"+
|
||||
"08 05 98 7c b7 0b e1 7b"), // (32 octets)
|
||||
},
|
||||
|
||||
{P: "password", // (8 octets)
|
||||
S: "salt", // (4 octets)
|
||||
c: 2,
|
||||
dkLen: 32,
|
||||
DK: h("ae 4d 0c 95 af 6b 46 d3"+
|
||||
"2d 0a df f9 28 f0 6d d0"+
|
||||
"2a 30 3f 8e f3 c2 51 df"+
|
||||
"d6 e2 d8 5a 95 47 4c 43"), // (32 octets)
|
||||
},
|
||||
|
||||
{P: "password", // (8 octets)
|
||||
S: "salt", // (4 octets)
|
||||
c: 4096,
|
||||
dkLen: 32,
|
||||
DK: h("c5 e4 78 d5 92 88 c8 41"+
|
||||
"aa 53 0d b6 84 5c 4c 8d"+
|
||||
"96 28 93 a0 01 ce 4e 11"+
|
||||
"a4 96 38 73 aa 98 13 4a"), // (32 octets)
|
||||
},
|
||||
|
||||
{P: "passwordPASSWORDpassword", // (24 octets)
|
||||
S: "saltSALTsaltSALTsaltSALTsaltSALTsalt", // (36 octets)
|
||||
c: 4096,
|
||||
dkLen: 40,
|
||||
DK: h("34 8c 89 db cb d3 2b 2f"+
|
||||
"32 d8 14 b8 11 6e 84 cf"+
|
||||
"2b 17 34 7e bc 18 00 18"+
|
||||
"1c 4e 2a 1f b8 dd 53 e1"+
|
||||
"c6 35 51 8c 7d ac 47 e9"), // (40 octets)
|
||||
},
|
||||
|
||||
{P: "pass\0word", // (9 octets)
|
||||
S: "sa\0lt", // (5 octets)
|
||||
c: 4096,
|
||||
dkLen: 16,
|
||||
DK: h("89 b6 9d 05 16 f8 29 89"+
|
||||
"3c 69 62 26 65 0a 86 87"), // (16 octets)
|
||||
},
|
||||
];
|
||||
|
||||
for (let v of vectors) {
|
||||
do_check_eq(v.DK,
|
||||
b2h(pbkdf2(v.P, v.S, v.c, v.dkLen, Ci.nsICryptoHMAC.SHA256, 32)));
|
||||
}
|
||||
});
|
||||
|
||||
// turn formatted test vectors into normal hex strings
|
||||
function h(hexStr) {
|
||||
return hexStr.replace(/\s+/g, "");
|
||||
}
|
||||
37
services/crypto/tests/unit/test_utils_sha1.js
Normal file
37
services/crypto/tests/unit/test_utils_sha1.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
_("Make sure sha1 digests works with various messages");
|
||||
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
let mes1 = "hello";
|
||||
let mes2 = "world";
|
||||
|
||||
let dig0 = CryptoUtils.UTF8AndSHA1(mes1);
|
||||
do_check_eq(dig0,
|
||||
"\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f\x3b\x48\x2c\xd9\xae\xa9\x43\x4d");
|
||||
|
||||
_("Make sure right sha1 digests are generated");
|
||||
let dig1 = CryptoUtils.sha1(mes1);
|
||||
do_check_eq(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
|
||||
let dig2 = CryptoUtils.sha1(mes2);
|
||||
do_check_eq(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
|
||||
let dig12 = CryptoUtils.sha1(mes1 + mes2);
|
||||
do_check_eq(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
|
||||
let dig21 = CryptoUtils.sha1(mes2 + mes1);
|
||||
do_check_eq(dig21, "5715790a892990382d98858c4aa38d0617151575");
|
||||
|
||||
_("Repeated sha1s shouldn't change the digest");
|
||||
do_check_eq(CryptoUtils.sha1(mes1), dig1);
|
||||
do_check_eq(CryptoUtils.sha1(mes2), dig2);
|
||||
do_check_eq(CryptoUtils.sha1(mes1 + mes2), dig12);
|
||||
do_check_eq(CryptoUtils.sha1(mes2 + mes1), dig21);
|
||||
|
||||
_("Nested sha1 should work just fine");
|
||||
let nest1 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes1)))));
|
||||
do_check_eq(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
|
||||
let nest2 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes2)))));
|
||||
do_check_eq(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
|
||||
}
|
||||
20
services/crypto/tests/unit/xpcshell.ini
Normal file
20
services/crypto/tests/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
[DEFAULT]
|
||||
head = head_helpers.js ../../../common/tests/unit/head_helpers.js
|
||||
tail =
|
||||
firefox-appdir = browser
|
||||
support-files =
|
||||
!/services/common/tests/unit/head_helpers.js
|
||||
|
||||
[test_load_modules.js]
|
||||
|
||||
[test_crypto_crypt.js]
|
||||
[test_crypto_deriveKey.js]
|
||||
[test_crypto_random.js]
|
||||
# Bug 676977: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
|
||||
[test_utils_hawk.js]
|
||||
[test_utils_hkdfExpand.js]
|
||||
[test_utils_httpmac.js]
|
||||
[test_utils_pbkdf2.js]
|
||||
[test_utils_sha1.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue