зеркало из https://github.com/mozilla/gecko-dev.git
Bug 440046 - expose secure PRNG in the DOM (window.crypto.getRandomValues) r=cviecco r=bsmith
This commit is contained in:
Родитель
52c440b803
Коммит
6743609509
|
@ -3,7 +3,13 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "Crypto.h"
|
||||
#include "nsIDOMClassInfo.h"
|
||||
#include "DOMError.h"
|
||||
#include "nsString.h"
|
||||
#include "nsIRandomGenerator.h"
|
||||
#include "jsapi.h"
|
||||
#include "jsfriendapi.h"
|
||||
|
||||
using namespace js::ArrayBufferView;
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -27,6 +33,72 @@ Crypto::~Crypto()
|
|||
MOZ_COUNT_DTOR(Crypto);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
Crypto::GetRandomValues(const jsval& aData, JSContext *cx, jsval* _retval)
|
||||
{
|
||||
// Make sure this is a JavaScript object
|
||||
if (!aData.isObject()) {
|
||||
return NS_ERROR_DOM_NOT_OBJECT_ERR;
|
||||
}
|
||||
|
||||
JSObject* view = &aData.toObject();
|
||||
|
||||
// Make sure this object is an ArrayBufferView
|
||||
if (!JS_IsTypedArrayObject(view)) {
|
||||
return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
|
||||
}
|
||||
|
||||
// Throw if the wrong type of ArrayBufferView is passed in
|
||||
// (Part of the Web Crypto API spec)
|
||||
switch (JS_GetArrayBufferViewType(view)) {
|
||||
case TYPE_INT8:
|
||||
case TYPE_UINT8:
|
||||
case TYPE_UINT8_CLAMPED:
|
||||
case TYPE_INT16:
|
||||
case TYPE_UINT16:
|
||||
case TYPE_INT32:
|
||||
case TYPE_UINT32:
|
||||
break;
|
||||
default:
|
||||
return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
|
||||
}
|
||||
|
||||
uint32_t dataLen = JS_GetTypedArrayByteLength(view);
|
||||
|
||||
if (dataLen == 0) {
|
||||
NS_WARNING("ArrayBufferView length is 0, cannot continue");
|
||||
return NS_OK;
|
||||
} else if (dataLen > 65536) {
|
||||
return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIRandomGenerator> randomGenerator;
|
||||
nsresult rv;
|
||||
randomGenerator =
|
||||
do_GetService("@mozilla.org/security/random-generator;1", &rv);
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("unable to continue without random number generator");
|
||||
return rv;
|
||||
}
|
||||
|
||||
void *dataptr = JS_GetArrayBufferViewData(view);
|
||||
NS_ENSURE_TRUE(dataptr, NS_ERROR_FAILURE);
|
||||
|
||||
unsigned char* data =
|
||||
static_cast<unsigned char*>(dataptr);
|
||||
|
||||
uint8_t *buf;
|
||||
rv = randomGenerator->GenerateRandomBytes(dataLen, &buf);
|
||||
NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
|
||||
|
||||
memcpy(data, buf, dataLen);
|
||||
NS_Free(buf);
|
||||
|
||||
*_retval = OBJECT_TO_JSVAL(view);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#ifndef MOZ_DISABLE_CRYPTOLEGACY
|
||||
// Stub out the legacy nsIDOMCrypto methods. The actual
|
||||
// implementations are in security/manager/ssl/src/nsCrypto.{cpp,h}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(eadb45d6-aec2-4b70-95f4-ffdf1f86738f)]
|
||||
[scriptable, uuid(a0a3bc68-eab3-4e66-b5cb-b1d86765119c)]
|
||||
interface nsIDOMCrypto : nsISupports
|
||||
{
|
||||
[implicit_jscontext] jsval getRandomValues(in jsval aData);
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
interface nsIDOMCRMFObject;
|
||||
|
||||
[scriptable, uuid(b50312fa-06ec-460c-b5e9-5dbb009eb457)]
|
||||
[scriptable, uuid(e1df1d4d-41ef-4225-934a-107c5d612686)]
|
||||
interface nsIDOMCrypto : nsISupports
|
||||
{
|
||||
readonly attribute DOMString version;
|
||||
|
@ -23,4 +23,6 @@ interface nsIDOMCrypto : nsISupports
|
|||
in DOMString caOption /* ... */);
|
||||
void logout();
|
||||
void disableRightClick();
|
||||
|
||||
[implicit_jscontext] jsval getRandomValues(in jsval aData);
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@ include $(DEPTH)/config/autoconf.mk
|
|||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
_TEST_FILES = \
|
||||
test_getRandomValues.html \
|
||||
$(NULL)
|
||||
|
||||
ifndef MOZ_DISABLE_CRYPTOLEGACY
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html><head>
|
||||
<title>Test window.crypto.getRandomValues</title>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body onload="onWindowLoad()">
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var testData = [ { len: 32, type: "Int8", pass: true },
|
||||
{ len: 32, type: "Int16", pass: true },
|
||||
{ len: 32, type: "Int32", pass: true },
|
||||
{ len: 32, type: "Uint8", pass: true },
|
||||
{ len: 32, type: "Uint16", pass: true },
|
||||
{ len: 32, type: "Uint32", pass: true },
|
||||
{ len: 65536, type: "Uint8", pass: true },
|
||||
{ len: 32, type: "Uint8Clamped", pass: true },
|
||||
{ len: 65537, type: "Uint8", pass: false },
|
||||
{ len: 32, type: "Float32", pass: false },
|
||||
{ len: 32, type: "Float64", pass: false } ];
|
||||
|
||||
|
||||
var testCount = 0;
|
||||
|
||||
function testNsCryptoGetRandomValues(aLength, aType)
|
||||
{
|
||||
var arrayTypes = {
|
||||
Int8: Int8Array,
|
||||
Int16: Int16Array,
|
||||
Int32: Int32Array,
|
||||
Uint8: Uint8Array,
|
||||
Uint16: Uint16Array,
|
||||
Uint32: Uint32Array,
|
||||
Float32: Float32Array,
|
||||
Float64: Float64Array,
|
||||
Uint8Clamped: Uint8ClampedArray,
|
||||
};
|
||||
|
||||
testCount++;
|
||||
|
||||
var buf = new ArrayBuffer(aLength);
|
||||
var arBuf = new arrayTypes[aType](buf);
|
||||
|
||||
var pass = false;
|
||||
var b = window.crypto.getRandomValues(arBuf);
|
||||
ok(b === arBuf, "ArrayBuffer result is argument buffer");
|
||||
|
||||
for (var i = 0; i < arBuf.length; i++) {
|
||||
if (arBuf.length > 6) {
|
||||
// XXXddahl: THIS MIGHT FAIL EVERY FULL MOON, SORRY QA!!!
|
||||
if (arBuf[i] != 0) {
|
||||
pass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
pass = true;
|
||||
}
|
||||
}
|
||||
is(pass, true, "Non-zero result: " + i + " found in the " + aType + ": " + aLength + " ArrayBufferView");
|
||||
}
|
||||
|
||||
function onWindowLoad()
|
||||
{
|
||||
window.removeEventListener("load", onWindowLoad, false);
|
||||
var failedWithCorrectError = false;
|
||||
try {
|
||||
for (var i = 0; i < testData.length; i++) {
|
||||
if (testData[i].pass) {
|
||||
try {
|
||||
testNsCryptoGetRandomValues(testData[i].len, testData[i].type, testData[i].pass);
|
||||
}
|
||||
catch (ex) {
|
||||
ok(false, "testNsCryptoGetRandomValues failed, test should have passed: " + testData[i].type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// failing tests are dealt with here
|
||||
if (i == 8) {
|
||||
try {
|
||||
testNsCryptoGetRandomValues(testData[i].len, testData[i].type, testData[i].pass);
|
||||
}
|
||||
catch (ex) {
|
||||
todo("QuotaExceededError" in window && ex instanceof QuotaExceededError,
|
||||
"Exception was the correct type");
|
||||
failedWithCorrectError = ex.toString().search(/QUOTA_EXCEEDED_ERR/);
|
||||
ok(failedWithCorrectError, "Extended length array buffer fails, NS_ERROR_DOM_QUOTA_EXCEEDED_ERR thrown");
|
||||
}
|
||||
} // 8
|
||||
|
||||
if (i == 9) {
|
||||
try {
|
||||
testNsCryptoGetRandomValues(testData[i].len, testData[i].type, testData[i].pass);
|
||||
}
|
||||
catch (ex) {
|
||||
failedWithCorrectError = ex.toString().search(/TYPE_MISMATCH_ERR/);
|
||||
ok(failedWithCorrectError,
|
||||
"Expected TYPE_MISMATCH_ERR: Float32Array is not valid, got " + ex + ".");
|
||||
}
|
||||
} // 9
|
||||
|
||||
if (i == 10) {
|
||||
try {
|
||||
testNsCryptoGetRandomValues(testData[i].len, testData[i].type, testData[i].pass);
|
||||
}
|
||||
catch (ex) {
|
||||
failedWithCorrectError = ex.toString().search(/TYPE_MISMATCH_ERR/);
|
||||
ok(failedWithCorrectError,
|
||||
"Expected TYPE_MISMATCH_ERR: Float64Array is not valid, got " + ex + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
} // end main for loop
|
||||
}
|
||||
catch (ex) {
|
||||
ok(false, "Unexpected Error: " + ex);
|
||||
}
|
||||
// Count the tests in the testData array
|
||||
ok(testCount == 11, "11 tests run via testData");
|
||||
|
||||
// Test a null argument
|
||||
try {
|
||||
window.crypto.getRandomValues(null);
|
||||
}
|
||||
catch (ex) {
|
||||
var test = ex.toString().search(/1003/);
|
||||
ok((test > -1), "Expected TYPE_ERR, got " + ex + ".");
|
||||
}
|
||||
|
||||
// Test a zero-length buffer view
|
||||
try {
|
||||
var a = new Int8Array(0);
|
||||
window.crypto.getRandomValues(a);
|
||||
ok(a[0] === undefined, "The array buffer is unchanged, still 0 length");
|
||||
}
|
||||
catch (ex) {
|
||||
ok(false, "A zero-length array buffer view should not fail");
|
||||
}
|
||||
|
||||
// Test a one-length buffer view
|
||||
try {
|
||||
var a = new Int8Array(1);
|
||||
var b = window.crypto.getRandomValues(a);
|
||||
ok(a[0] !== 0, "The array buffer has one random value");
|
||||
ok(a === b, "ArrayBuffer result is argument buffer");
|
||||
}
|
||||
catch (ex) {
|
||||
ok(false, "A one-length array buffer view should not fail");
|
||||
}
|
||||
|
||||
// Test a 16 byte length buffer
|
||||
var testConfig = { len: 16, type: "Int8", pass: true };
|
||||
testNsCryptoGetRandomValues(testConfig.len,
|
||||
testConfig.type,
|
||||
testConfig.pass);
|
||||
|
||||
// Test a 31 byte length buffer
|
||||
testConfig = { len: 31, type: "Int8", pass: true };
|
||||
testNsCryptoGetRandomValues(testConfig.len,
|
||||
testConfig.type,
|
||||
testConfig.pass);
|
||||
|
||||
// Test a 33 byte length buffer
|
||||
testConfig = { len: 33, type: "Int8", pass: true };
|
||||
testNsCryptoGetRandomValues(testConfig.len,
|
||||
testConfig.type,
|
||||
testConfig.pass);
|
||||
|
||||
// Test a range of an array buffer view
|
||||
var buffer = new ArrayBuffer(32);
|
||||
var view = new Int8Array(buffer, 0, 16);
|
||||
var view2 = new Int8Array(buffer, 16, 16);
|
||||
for (var i = 0; i < view2.byteLength; i++) {
|
||||
view2[i] = 1;
|
||||
}
|
||||
var b = window.crypto.getRandomValues(view);
|
||||
ok(b === view, "ArrayBuffer result is argument buffer");
|
||||
for (var i = 0; i < view.byteLength; i++) {
|
||||
is(view2[i], 1, "view2 is unchanged");
|
||||
}
|
||||
|
||||
// test an offset view
|
||||
var result = false;
|
||||
var b = window.crypto.getRandomValues(view2);
|
||||
for (var i = 0; i < view2.length; i++) {
|
||||
if (view2[i] != 1) {
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ok(result, "view2 has been updated correctly");
|
||||
ok(b === view2, "ArrayBuffer result is argument buffer");
|
||||
// test the return value
|
||||
buffer = new ArrayBuffer(32);
|
||||
view = new Int8Array(buffer, 0, 16);
|
||||
var retval = window.crypto.getRandomValues(view);
|
||||
ok(view === retval, "The view and return value are the same");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
</script>
|
||||
</body></html>
|
|
@ -99,6 +99,7 @@ EXPORTS += \
|
|||
CryptoTask.h \
|
||||
nsNSSShutDown.h \
|
||||
ScopedNSSTypes.h \
|
||||
nsRandomGenerator.h \
|
||||
$(NULL)
|
||||
|
||||
EXPORTS_NAMESPACES = mozilla
|
||||
|
|
|
@ -2856,6 +2856,12 @@ nsCrypto::DisableRightClick()
|
|||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCrypto::GetRandomValues(const jsval& aData, JSContext *cx, jsval* _retval)
|
||||
{
|
||||
return mozilla::dom::Crypto::GetRandomValues(aData, cx, _retval);
|
||||
}
|
||||
|
||||
nsCRMFObject::nsCRMFObject()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
#include "nsRandomGenerator.h"
|
||||
#include "pk11pub.h"
|
||||
#include "secerr.h"
|
||||
#include "prerror.h"
|
||||
#include "nsNSSComponent.h"
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// nsRandomGenerator
|
||||
|
@ -25,7 +28,12 @@ nsRandomGenerator::GenerateRandomBytes(uint32_t aLength,
|
|||
if (!buf)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
SECStatus srv = PK11_GenerateRandom(buf, aLength);
|
||||
mozilla::ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
|
||||
if (slot == NULL) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
SECStatus srv = PK11_GenerateRandomOnSlot(slot, buf, aLength);
|
||||
|
||||
if (SECSuccess != srv) {
|
||||
NS_Free(buf);
|
||||
return NS_ERROR_FAILURE;
|
||||
|
|
Загрузка…
Ссылка в новой задаче