import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View 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'

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

View 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, &paramsItem,
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,
&paramsItem,
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,
&paramsItem, 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,
&paramsItem, 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, &paramsItem,
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;

View 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__

View 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();
}

View file

@ -0,0 +1,6 @@
[DEFAULT]
head =
tail =
firefox-appdir = browser
[test_jpake.js]

View file

@ -0,0 +1 @@
resource services-crypto resource://gre/modules/services-crypto/

View 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);
})
);
},
};

View 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
View 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',
]

View 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(" "));
};

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

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

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

View 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, {});
}
}

View 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();
});

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

View 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();
});

View 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, "");
}

View 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");
}

View 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]