Source code
Revision control
Copy as Markdown
Other Tools
/* 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
#include "KeyedUUIDMapper.h"
#include "mozilla/EndianUtils.h"
#include "nsID.h"
#include "nsString.h"
namespace mozilla {
NS_IMPL_ISUPPORTS(KeyedUUIDMapper, nsIKeyedUUIDMapper)
const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL;
static void FormatUUID(const uint8_t (&aBytes)[16], nsACString& aOut) {
nsID id;
id.m0 = BigEndian::readUint32(&aBytes[0]);
id.m1 = BigEndian::readUint16(&aBytes[4]);
id.m2 = BigEndian::readUint16(&aBytes[6]);
// m3 are bytes, we can memcpy without worrying about endianness:
memcpy(id.m3, &aBytes[8], sizeof(id.m3));
char buf[NSID_LENGTH];
id.ToProvidedString(buf);
// +1 to skip the "{" that nsID::ToProvidedString always prepends.
aOut.Assign(buf + 1, 36);
}
static bool ParseUUID(const nsACString& aUUID, uint8_t (&aOut)[16]) {
if (aUUID.Length() != 36) {
// nsID is too permissive, it accepts UUID with "{" and "}", and it also
// ensures that we only accept bracketless UUID inputs.
return false;
}
nsID id;
if (!id.Parse(PromiseFlatCString(aUUID).get())) {
return false;
}
BigEndian::writeUint32(&aOut[0], id.m0);
BigEndian::writeUint16(&aOut[4], id.m1);
BigEndian::writeUint16(&aOut[6], id.m2);
// m3 are bytes, we can memcpy without worrying about endianness:
memcpy(&aOut[8], id.m3, sizeof(id.m3));
return true;
}
NS_IMETHODIMP KeyedUUIDMapper::Init(const nsTArray<uint8_t>& aKey) {
NS_ASSERT_OWNINGTHREAD(KeyedUUIDMapper); // Avoid concurrent mSymKey changes.
if (aKey.Length() != 16) {
return NS_ERROR_INVALID_ARG;
}
if (!EnsureNSSInitializedChromeOrContent()) {
return NS_ERROR_NOT_AVAILABLE;
}
UniquePK11SlotInfo slot(PK11_GetInternalSlot());
if (!slot) {
return NS_ERROR_NOT_AVAILABLE;
}
SECItem keyItem = {siBuffer, const_cast<uint8_t*>(aKey.Elements()), 16};
mSymKey = UniquePK11SymKey(
PK11_ImportSymKey(slot.get(), CKM_AES_ECB, PK11_OriginUnwrap,
CKA_ENCRYPT | CKA_DECRYPT, &keyItem, nullptr));
return mSymKey ? NS_OK : NS_ERROR_FAILURE;
}
NS_IMETHODIMP KeyedUUIDMapper::ToUUID(uint64_t aValue, nsACString& aResult) {
NS_ASSERT_OWNINGTHREAD(KeyedUUIDMapper); // Avoid concurrent mSymKey changes.
if (!mSymKey) {
return NS_ERROR_NOT_INITIALIZED;
}
if (aValue > kJS_MAX_SAFE_UINTEGER) {
return NS_ERROR_INVALID_ARG;
}
// value -> uuid is just AES encryption of value (with the key from Init()).
uint8_t plaintext[16] = {};
BigEndian::writeUint64(&plaintext[0], aValue);
uint8_t ciphertext[16];
unsigned int outLen = 0;
SECItem params = {siBuffer, nullptr, 0};
if (PK11_Encrypt(mSymKey.get(), CKM_AES_ECB, ¶ms, ciphertext, &outLen,
sizeof(ciphertext), plaintext,
sizeof(plaintext)) != SECSuccess) {
return NS_ERROR_UNEXPECTED;
}
FormatUUID(ciphertext, aResult);
return NS_OK;
}
NS_IMETHODIMP KeyedUUIDMapper::FromUUID(const nsACString& aUUID,
uint64_t* aResult) {
NS_ASSERT_OWNINGTHREAD(KeyedUUIDMapper); // Avoid concurrent mSymKey changes.
if (!mSymKey) {
return NS_ERROR_NOT_INITIALIZED;
}
uint8_t uuidBytes[16];
if (!ParseUUID(aUUID, uuidBytes)) {
return NS_ERROR_INVALID_ARG;
}
// uuid -> value is just AES decryption of uuid (with the key from Init()).
uint8_t plaintext[16];
unsigned int outLen = 0;
SECItem params = {siBuffer, nullptr, 0};
if (PK11_Decrypt(mSymKey.get(), CKM_AES_ECB, ¶ms, plaintext, &outLen,
sizeof(plaintext), uuidBytes,
sizeof(uuidBytes)) != SECSuccess) {
return NS_ERROR_UNEXPECTED;
}
if (BigEndian::readUint64(plaintext + 8) != 0) {
// ToUUID only ever fills 8 out of 16 bytes. The other 8 should be 0.
// If we find a value with non-zeroes here, then the input UUID is bogus
// value that could never have been produced by us. Reject it.
return NS_ERROR_INVALID_ARG;
}
uint64_t result = BigEndian::readUint64(plaintext);
if (result > kJS_MAX_SAFE_UINTEGER) {
// ToUUID rejects inputs beyond kJS_MAX_SAFE_UINTEGER, so we should not
// receive it here either.
return NS_ERROR_INVALID_ARG;
}
*aResult = result;
return NS_OK;
}
} // namespace mozilla