зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1256488 - Add a Base64 URL-decoder for C++ and chrome JS callers. r=mt,baku
MozReview-Commit-ID: IrDwImYfHRu --HG-- extra : rebase_source : ed7da7447e5d70c596234961625fcd4b8139814f
This commit is contained in:
Родитель
033ba99b0d
Коммит
d21d6da5e8
|
@ -4427,10 +4427,12 @@ this.DOMApplicationRegistry = {
|
|||
return "INVALID_SEGMENTS_NUMBER";
|
||||
}
|
||||
|
||||
// We need to translate the base64 alphabet used in JWT to our base64 alphabet
|
||||
// before calling atob.
|
||||
let decodedReceipt = JSON.parse(atob(segments[1].replace(/-/g, '+')
|
||||
.replace(/_/g, '/')));
|
||||
let jwtBuffer = ChromeUtils.base64URLDecode(segments[1], {
|
||||
// JWT/JWS prohibits padding per RFC 7515, section 2.
|
||||
padding: "reject",
|
||||
});
|
||||
let textDecoder = new TextDecoder("utf-8");
|
||||
let decodedReceipt = JSON.parse(textDecoder.decode(jwtBuffer));
|
||||
if (!decodedReceipt) {
|
||||
return "INVALID_RECEIPT_ENCODING";
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "ChromeUtils.h"
|
||||
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/BasePrincipal.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -50,6 +51,61 @@ ThreadSafeChromeUtils::NondeterministicGetWeakSetKeys(GlobalObject& aGlobal,
|
|||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ThreadSafeChromeUtils::Base64URLEncode(GlobalObject& aGlobal,
|
||||
const ArrayBufferViewOrArrayBuffer& aSource,
|
||||
const Base64URLEncodeOptions& aOptions,
|
||||
nsACString& aResult,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
size_t length = 0;
|
||||
uint8_t* data = nullptr;
|
||||
if (aSource.IsArrayBuffer()) {
|
||||
const ArrayBuffer& buffer = aSource.GetAsArrayBuffer();
|
||||
buffer.ComputeLengthAndData();
|
||||
length = buffer.Length();
|
||||
data = buffer.Data();
|
||||
} else if (aSource.IsArrayBufferView()) {
|
||||
const ArrayBufferView& view = aSource.GetAsArrayBufferView();
|
||||
view.ComputeLengthAndData();
|
||||
length = view.Length();
|
||||
data = view.Data();
|
||||
} else {
|
||||
MOZ_CRASH("Uninitialized union: expected buffer or view");
|
||||
}
|
||||
|
||||
nsresult rv = mozilla::Base64URLEncode(length, data, aOptions, aResult);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aResult.Truncate();
|
||||
aRv.Throw(rv);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ThreadSafeChromeUtils::Base64URLDecode(GlobalObject& aGlobal,
|
||||
const nsACString& aString,
|
||||
const Base64URLDecodeOptions& aOptions,
|
||||
JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
FallibleTArray<uint8_t> data;
|
||||
nsresult rv = mozilla::Base64URLDecode(aString, aOptions, data);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JSObject*> buffer(aGlobal.Context(),
|
||||
ArrayBuffer::Create(aGlobal.Context(),
|
||||
data.Length(),
|
||||
data.Elements()));
|
||||
if (NS_WARN_IF(!buffer)) {
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
aRetval.set(buffer);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
|
||||
const dom::OriginAttributesDictionary& aAttrs,
|
||||
|
|
|
@ -20,6 +20,8 @@ class HeapSnapshot;
|
|||
|
||||
namespace dom {
|
||||
|
||||
class ArrayBufferViewOrArrayBuffer;
|
||||
|
||||
class ThreadSafeChromeUtils
|
||||
{
|
||||
public:
|
||||
|
@ -43,6 +45,18 @@ public:
|
|||
JS::Handle<JS::Value> aSet,
|
||||
JS::MutableHandle<JS::Value> aRetval,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static void Base64URLEncode(GlobalObject& aGlobal,
|
||||
const ArrayBufferViewOrArrayBuffer& aSource,
|
||||
const Base64URLEncodeOptions& aOptions,
|
||||
nsACString& aResult,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static void Base64URLDecode(GlobalObject& aGlobal,
|
||||
const nsACString& aString,
|
||||
const Base64URLDecodeOptions& aOptions,
|
||||
JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aRv);
|
||||
};
|
||||
|
||||
class ChromeUtils : public ThreadSafeChromeUtils
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
"use strict";
|
||||
|
||||
function run_test() {
|
||||
test_base64URLEncode();
|
||||
test_base64URLDecode();
|
||||
}
|
||||
|
||||
// Test vectors from RFC 4648, section 10.
|
||||
let textTests = {
|
||||
"": "",
|
||||
"f": "Zg",
|
||||
"fo": "Zm8",
|
||||
"foo": "Zm9v",
|
||||
"foob": "Zm9vYg",
|
||||
"fooba": "Zm9vYmE",
|
||||
"foobar": "Zm9vYmFy",
|
||||
}
|
||||
|
||||
// Examples from RFC 4648, section 9.
|
||||
let binaryTests = [{
|
||||
decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]),
|
||||
encoded: "FPucA9l-",
|
||||
}, {
|
||||
decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]),
|
||||
encoded: "FPucA9k",
|
||||
}, {
|
||||
decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03]),
|
||||
encoded: "FPucAw",
|
||||
}];
|
||||
|
||||
function padEncodedValue(value) {
|
||||
switch (value.length % 4) {
|
||||
case 0:
|
||||
return value;
|
||||
case 2:
|
||||
return value + "==";
|
||||
case 3:
|
||||
return value + "=";
|
||||
default:
|
||||
throw new TypeError("Invalid encoded value");
|
||||
}
|
||||
}
|
||||
|
||||
function testEncode(input, encoded) {
|
||||
equal(ChromeUtils.base64URLEncode(input, { pad: false }),
|
||||
encoded, encoded + " without padding");
|
||||
equal(ChromeUtils.base64URLEncode(input, { pad: true }),
|
||||
padEncodedValue(encoded), encoded + " with padding");
|
||||
}
|
||||
|
||||
function test_base64URLEncode() {
|
||||
throws(_ => ChromeUtils.base64URLEncode(new Uint8Array(0)), /TypeError/,
|
||||
"Should require encoding options");
|
||||
throws(_ => ChromeUtils.base64URLEncode(new Uint8Array(0), {}), /TypeError/,
|
||||
"Encoding should require the padding option");
|
||||
|
||||
for (let {decoded, encoded} of binaryTests) {
|
||||
testEncode(decoded, encoded);
|
||||
}
|
||||
|
||||
let textEncoder = new TextEncoder("utf-8");
|
||||
for (let decoded of Object.keys(textTests)) {
|
||||
let input = textEncoder.encode(decoded);
|
||||
testEncode(input, textTests[decoded]);
|
||||
}
|
||||
}
|
||||
|
||||
function testDecode(input, decoded) {
|
||||
let buffer = ChromeUtils.base64URLDecode(input, { padding: "reject" });
|
||||
deepEqual(new Uint8Array(buffer), decoded, input + " with padding rejected");
|
||||
|
||||
let paddedValue = padEncodedValue(input);
|
||||
buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "ignore" });
|
||||
deepEqual(new Uint8Array(buffer), decoded, input + " with padding ignored");
|
||||
|
||||
if (paddedValue.length > input.length) {
|
||||
throws(_ => ChromeUtils.base64URLDecode(paddedValue, { padding: "reject" }),
|
||||
paddedValue + " with padding rejected should throw");
|
||||
|
||||
throws(_ => ChromeUtils.base64URLDecode(input, { padding: "require" }),
|
||||
input + " with padding required should throw");
|
||||
|
||||
buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "require" });
|
||||
deepEqual(new Uint8Array(buffer), decoded, paddedValue + " with padding required");
|
||||
}
|
||||
}
|
||||
|
||||
function test_base64URLDecode() {
|
||||
throws(_ => ChromeUtils.base64URLDecode(""), /TypeError/,
|
||||
"Should require decoding options");
|
||||
throws(_ => ChromeUtils.base64URLEncode("", {}), /TypeError/,
|
||||
"Decoding should require the padding option");
|
||||
|
||||
for (let {decoded, encoded} of binaryTests) {
|
||||
testDecode(encoded, decoded);
|
||||
}
|
||||
|
||||
let textEncoder = new TextEncoder("utf-8");
|
||||
for (let decoded of Object.keys(textTests)) {
|
||||
let expectedBuffer = textEncoder.encode(decoded);
|
||||
testDecode(textTests[decoded], expectedBuffer);
|
||||
}
|
||||
}
|
|
@ -51,3 +51,4 @@ head = head_xml.js
|
|||
head = head_xml.js
|
||||
[test_xmlserializer.js]
|
||||
[test_cancelPrefetch.js]
|
||||
[test_chromeutils_base64.js]
|
||||
|
|
|
@ -10,8 +10,7 @@ const Cu = Components.utils;
|
|||
Cu.importGlobalProperties(['crypto']);
|
||||
|
||||
this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
|
||||
'getCryptoParams',
|
||||
'base64UrlDecode'];
|
||||
'getCryptoParams'];
|
||||
|
||||
var UTF8 = new TextEncoder('utf-8');
|
||||
|
||||
|
@ -118,34 +117,6 @@ function chunkArray(array, size) {
|
|||
return result;
|
||||
}
|
||||
|
||||
this.base64UrlDecode = function(s) {
|
||||
s = s.replace(/-/g, '+').replace(/_/g, '/');
|
||||
|
||||
// Replace padding if it was stripped by the sender.
|
||||
// See http://tools.ietf.org/html/rfc4648#section-4
|
||||
switch (s.length % 4) {
|
||||
case 0:
|
||||
break; // No pad chars in this case
|
||||
case 2:
|
||||
s += '==';
|
||||
break; // Two pad chars
|
||||
case 3:
|
||||
s += '=';
|
||||
break; // One pad char
|
||||
default:
|
||||
throw new Error('Illegal base64url string!');
|
||||
}
|
||||
|
||||
// With correct padding restored, apply the standard base64 decoder
|
||||
var decoded = atob(s);
|
||||
|
||||
var array = new Uint8Array(new ArrayBuffer(decoded.length));
|
||||
for (var i = 0; i < decoded.length; i++) {
|
||||
array[i] = decoded.charCodeAt(i);
|
||||
}
|
||||
return array;
|
||||
};
|
||||
|
||||
this.concatArray = function(arrays) {
|
||||
var size = arrays.reduce((total, a) => total + a.byteLength, 0);
|
||||
var index = 0;
|
||||
|
@ -226,7 +197,11 @@ this.PushCrypto = {
|
|||
return Promise.reject(new Error('Data truncated'));
|
||||
}
|
||||
|
||||
let senderKey = base64UrlDecode(aSenderPublicKey)
|
||||
let senderKey = ChromeUtils.base64URLDecode(aSenderPublicKey, {
|
||||
// draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
|
||||
padding: "reject",
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
crypto.subtle.importKey('raw', senderKey, ECDH_KEY,
|
||||
false, ['deriveBits']),
|
||||
|
@ -238,7 +213,8 @@ this.PushCrypto = {
|
|||
subscriptionPrivateKey, 256))
|
||||
.then(ikm => this._deriveKeyAndNonce(aPadSize,
|
||||
new Uint8Array(ikm),
|
||||
base64UrlDecode(aSalt),
|
||||
ChromeUtils.base64URLDecode(aSalt,
|
||||
{ padding: "reject" }),
|
||||
aPublicKey,
|
||||
senderKey,
|
||||
aAuthenticationSecret))
|
||||
|
|
|
@ -22,7 +22,6 @@ const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bi
|
|||
|
||||
const {
|
||||
PushCrypto,
|
||||
base64UrlDecode,
|
||||
concatArray,
|
||||
getCryptoParams,
|
||||
} = Cu.import("resource://gre/modules/PushCrypto.jsm");
|
||||
|
@ -118,7 +117,10 @@ this.PushServiceAndroidGCM = {
|
|||
};
|
||||
cryptoParams = getCryptoParams(headers);
|
||||
// Ciphertext is (urlsafe) Base 64 encoded.
|
||||
message = base64UrlDecode(data.message);
|
||||
message = ChromeUtils.base64URLDecode(data.message, {
|
||||
// The Push server may append padding.
|
||||
padding: "ignore",
|
||||
});
|
||||
}
|
||||
|
||||
console.debug("Delivering message to main PushService:", message, cryptoParams);
|
||||
|
|
|
@ -21,7 +21,6 @@ const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
|
|||
const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
|
||||
const {
|
||||
PushCrypto,
|
||||
base64UrlDecode,
|
||||
getCryptoParams,
|
||||
} = Cu.import("resource://gre/modules/PushCrypto.jsm");
|
||||
|
||||
|
@ -936,7 +935,10 @@ this.PushServiceWebSocket = {
|
|||
} else {
|
||||
let params = getCryptoParams(update.headers);
|
||||
if (params) {
|
||||
let message = base64UrlDecode(update.data);
|
||||
let message = ChromeUtils.base64URLDecode(update.data, {
|
||||
// The Push server may append padding.
|
||||
padding: "ignore",
|
||||
});
|
||||
promise = this._mainPushService.receivedPushMessage(
|
||||
update.channelID,
|
||||
update.version,
|
||||
|
|
|
@ -331,21 +331,31 @@ PushSubscription::GetKey(JSContext* aCx,
|
|||
}
|
||||
|
||||
void
|
||||
PushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
|
||||
PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv)
|
||||
{
|
||||
aJSON.mEndpoint.Construct();
|
||||
aJSON.mEndpoint.Value() = mEndpoint;
|
||||
|
||||
Base64URLEncodeOptions encodeOptions;
|
||||
encodeOptions.mPad = false;
|
||||
|
||||
aJSON.mKeys.mP256dh.Construct();
|
||||
nsresult rv = Base64URLEncode(mRawP256dhKey.Length(),
|
||||
mRawP256dhKey.Elements(),
|
||||
encodeOptions,
|
||||
aJSON.mKeys.mP256dh.Value());
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
aJSON.mKeys.mAuth.Construct();
|
||||
rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
|
||||
aJSON.mKeys.mAuth.Value());
|
||||
Unused << NS_WARN_IF(NS_FAILED(rv));
|
||||
encodeOptions, aJSON.mKeys.mAuth.Value());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
|
|
|
@ -73,7 +73,7 @@ public:
|
|||
Unsubscribe(ErrorResult& aRv);
|
||||
|
||||
void
|
||||
ToJSON(PushSubscriptionJSON& aJSON);
|
||||
ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
~PushSubscription();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
base64UrlDecode,
|
||||
getCryptoParams,
|
||||
PushCrypto,
|
||||
} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
|
||||
|
@ -138,7 +137,9 @@ add_task(function* test_crypto_decodeMsg() {
|
|||
x: 'sd85ZCbEG6dEkGMCmDyGBIt454Qy-Yo-1xhbaT2Jlk4',
|
||||
y: 'vr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs',
|
||||
};
|
||||
let publicKey = base64UrlDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs');
|
||||
let publicKey = ChromeUtils.base64URLDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs', {
|
||||
padding: "reject",
|
||||
});
|
||||
|
||||
let expectedSuccesses = [{
|
||||
desc: 'padSize = 2, rs = 24, pad = 0',
|
||||
|
@ -177,8 +178,13 @@ add_task(function* test_crypto_decodeMsg() {
|
|||
padSize: 2,
|
||||
}];
|
||||
for (let test of expectedSuccesses) {
|
||||
let authSecret = test.authSecret ? base64UrlDecode(test.authSecret) : null;
|
||||
let result = yield PushCrypto.decodeMsg(base64UrlDecode(test.data),
|
||||
let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, {
|
||||
padding: "reject",
|
||||
}) : null;
|
||||
let data = ChromeUtils.base64URLDecode(test.data, {
|
||||
padding: "reject",
|
||||
});
|
||||
let result = yield PushCrypto.decodeMsg(data,
|
||||
privateKey, publicKey,
|
||||
test.senderPublicKey, test.salt,
|
||||
test.rs, authSecret, test.padSize);
|
||||
|
@ -223,9 +229,14 @@ add_task(function* test_crypto_decodeMsg() {
|
|||
rs: 25,
|
||||
}];
|
||||
for (let test of expectedFailures) {
|
||||
let authSecret = test.authSecret ? base64UrlDecode(test.authSecret) : null;
|
||||
let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, {
|
||||
padding: "reject",
|
||||
}) : null;
|
||||
let data = ChromeUtils.base64URLDecode(test.data, {
|
||||
padding: "reject",
|
||||
});
|
||||
yield rejects(
|
||||
PushCrypto.decodeMsg(base64UrlDecode(test.data), privateKey, publicKey,
|
||||
PushCrypto.decodeMsg(data, privateKey, publicKey,
|
||||
test.senderPublicKey, test.salt, test.rs,
|
||||
authSecret, test.padSize),
|
||||
test.desc
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
'use strict';
|
||||
|
||||
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
|
||||
const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
|
||||
|
||||
let db;
|
||||
let userAgentID = 'f5b47f8d-771f-4ea3-b999-91c135f8766d';
|
||||
|
@ -27,9 +26,13 @@ function putRecord(channelID, scope, publicKey, privateKey, authSecret) {
|
|||
originAttributes: '',
|
||||
quota: Infinity,
|
||||
systemRecord: true,
|
||||
p256dhPublicKey: base64UrlDecode(publicKey),
|
||||
p256dhPublicKey: ChromeUtils.base64URLDecode(publicKey, {
|
||||
padding: "reject",
|
||||
}),
|
||||
p256dhPrivateKey: privateKey,
|
||||
authenticationSecret: base64UrlDecode(authSecret),
|
||||
authenticationSecret: ChromeUtils.base64URLDecode(authSecret, {
|
||||
padding: "reject",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const {PushDB, PushService, PushServiceHttp2} = serviceExports;
|
||||
const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
|
||||
|
||||
var prefs;
|
||||
var tlsProfile;
|
||||
|
@ -129,7 +128,9 @@ add_task(function* test_pushNotifications() {
|
|||
pushEndpoint: serverURL + '/pushEndpoint4',
|
||||
pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint4',
|
||||
scope: 'https://example.com/page/4',
|
||||
p256dhPublicKey: base64UrlDecode('BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU'),
|
||||
p256dhPublicKey: ChromeUtils.base64URLDecode('BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU', {
|
||||
padding: "reject",
|
||||
}),
|
||||
p256dhPrivateKey: {
|
||||
crv: 'P-256',
|
||||
d: 'fWi7tZaX0Pk6WnLrjQ3kiRq_g5XStL5pdH4pllNCqXw',
|
||||
|
@ -139,7 +140,9 @@ add_task(function* test_pushNotifications() {
|
|||
x: 'Ry8PORYKtS2NT_DKAv3yxtAJCtbWVj2Ku2AaeUJzgHQ',
|
||||
y: 'JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU'
|
||||
},
|
||||
authenticationSecret: base64UrlDecode('cwDVC1iwAn8E37mkR3tMSg'),
|
||||
authenticationSecret: ChromeUtils.base64URLDecode('cwDVC1iwAn8E37mkR3tMSg', {
|
||||
padding: "reject",
|
||||
}),
|
||||
originAttributes: ChromeUtils.originAttributesToSuffix(
|
||||
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
|
||||
quota: Infinity,
|
||||
|
|
|
@ -38,5 +38,6 @@ interface PushSubscription
|
|||
Promise<boolean> unsubscribe();
|
||||
|
||||
// Implements the custom serializer specified in Push API, section 9.
|
||||
[Throws]
|
||||
PushSubscriptionJSON toJSON();
|
||||
};
|
||||
|
|
|
@ -55,6 +55,28 @@ interface ThreadSafeChromeUtils {
|
|||
*/
|
||||
[Throws, NewObject]
|
||||
static any nondeterministicGetWeakSetKeys(any aSet);
|
||||
|
||||
/**
|
||||
* Converts a buffer to a Base64 URL-encoded string per RFC 4648.
|
||||
*
|
||||
* @param source The buffer to encode.
|
||||
* @param options Additional encoding options.
|
||||
* @returns The encoded string.
|
||||
*/
|
||||
[Throws]
|
||||
static ByteString base64URLEncode(BufferSource source,
|
||||
Base64URLEncodeOptions options);
|
||||
|
||||
/**
|
||||
* Decodes a Base64 URL-encoded string per RFC 4648.
|
||||
*
|
||||
* @param string The string to decode.
|
||||
* @param options Additional decoding options.
|
||||
* @returns The decoded buffer.
|
||||
*/
|
||||
[Throws, NewObject]
|
||||
static ArrayBuffer base64URLDecode(ByteString string,
|
||||
Base64URLDecodeOptions options);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -88,3 +110,31 @@ dictionary HeapSnapshotBoundaries {
|
|||
object debugger;
|
||||
boolean runtime;
|
||||
};
|
||||
|
||||
dictionary Base64URLEncodeOptions {
|
||||
/** Specifies whether the output should be padded with "=" characters. */
|
||||
required boolean pad;
|
||||
};
|
||||
|
||||
enum Base64URLDecodePadding {
|
||||
/**
|
||||
* Fails decoding if the input is unpadded. RFC 4648, section 3.2 requires
|
||||
* padding, unless the referring specification prohibits it.
|
||||
*/
|
||||
"require",
|
||||
|
||||
/** Tolerates padded and unpadded input. */
|
||||
"ignore",
|
||||
|
||||
/**
|
||||
* Fails decoding if the input is padded. This follows the strict base64url
|
||||
* variant used in JWS (RFC 7515, Appendix C) and HTTP Encrypted
|
||||
* Content-Encoding (draft-ietf-httpbis-encryption-encoding-01).
|
||||
*/
|
||||
"reject"
|
||||
};
|
||||
|
||||
dictionary Base64URLDecodeOptions {
|
||||
/** Specifies the padding mode for decoding the input. */
|
||||
required Base64URLDecodePadding padding;
|
||||
};
|
||||
|
|
|
@ -1002,10 +1002,12 @@ this.MobileIdentityManager = {
|
|||
return Promise.reject(ERROR_INVALID_ASSERTION);
|
||||
}
|
||||
|
||||
// We need to translate the base64 alphabet used in JWT to our base64
|
||||
// alphabet before calling atob.
|
||||
let decodedPayload = JSON.parse(atob(segments[1].replace(/-/g, '+')
|
||||
.replace(/_/g, '/')));
|
||||
let payloadBuffer = ChromeUtils.base64URLDecode(segments[1], {
|
||||
// `IdentityCryptoService` pads output.
|
||||
padding: "require",
|
||||
});
|
||||
let textDecoder = new TextDecoder("utf-8");
|
||||
let decodedPayload = JSON.parse(textDecoder.decode(payloadBuffer));
|
||||
|
||||
if (!decodedPayload || !decodedPayload.verifiedMSISDN) {
|
||||
return Promise.reject(ERROR_INVALID_ASSERTION);
|
||||
|
|
|
@ -44,28 +44,6 @@ HexEncode(const SECItem * it, nsACString & result)
|
|||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
Base64UrlEncodeImpl(const nsACString & utf8Input, nsACString & result)
|
||||
{
|
||||
nsresult rv = Base64Encode(utf8Input, result);
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsACString::char_type * out = result.BeginWriting();
|
||||
nsACString::size_type length = result.Length();
|
||||
// base64url encoding is defined in RFC 4648. It replaces the last two
|
||||
// alphabet characters of base64 encoding with '-' and '_' respectively.
|
||||
for (unsigned int i = 0; i < length; ++i) {
|
||||
if (out[i] == '+') {
|
||||
out[i] = '-';
|
||||
} else if (out[i] == '/') {
|
||||
out[i] = '_';
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#define DSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("DS160"))
|
||||
#define RSA_KEY_TYPE_STRING (NS_LITERAL_CSTRING("RS256"))
|
||||
|
||||
|
@ -237,7 +215,11 @@ NS_IMETHODIMP
|
|||
IdentityCryptoService::Base64UrlEncode(const nsACString & utf8Input,
|
||||
nsACString & result)
|
||||
{
|
||||
return Base64UrlEncodeImpl(utf8Input, result);
|
||||
dom::Base64URLEncodeOptions options;
|
||||
options.mPad = true;
|
||||
return Base64URLEncode(utf8Input.Length(),
|
||||
reinterpret_cast<const uint8_t*>(utf8Input.BeginReading()), options,
|
||||
result);
|
||||
}
|
||||
|
||||
KeyPair::KeyPair(SECKEYPrivateKey * privateKey, SECKEYPublicKey * publicKey)
|
||||
|
@ -531,9 +513,9 @@ SignRunnable::Run()
|
|||
mRv = MapSECStatus(PK11_Sign(mPrivateKey, &sig, &hashItem));
|
||||
}
|
||||
if (NS_SUCCEEDED(mRv)) {
|
||||
nsDependentCSubstring sigString(
|
||||
reinterpret_cast<const char*>(sig.data), sig.len);
|
||||
mRv = Base64UrlEncodeImpl(sigString, mSignature);
|
||||
dom::Base64URLEncodeOptions encodeOptions;
|
||||
encodeOptions.mPad = true;
|
||||
mRv = Base64URLEncode(sig.len, sig.data, encodeOptions, mSignature);
|
||||
}
|
||||
SECITEM_FreeItem(&sig, false);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ interface nsIIdentitySignCallback;
|
|||
* byte sequence.
|
||||
* e.g. "eyJhbGciOiJSUzI1NiJ9"
|
||||
* http://en.wikipedia.org/wiki/Base64#Variants_summary_table
|
||||
* we use the no-padding approach to base64-url-encoding
|
||||
* we use the padded approach to base64-url-encoding
|
||||
*
|
||||
* Callbacks take an "in nsresult rv" argument that indicates whether the async
|
||||
* operation succeeded. On success, rv will be a success code
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsString.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
#include "plbase64.h"
|
||||
|
||||
|
@ -228,6 +229,36 @@ EncodeInputStream(nsIInputStream* aInputStream,
|
|||
static const char kBase64URLAlphabet[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
||||
|
||||
// Maps an encoded character to a value in the Base64 URL alphabet, per
|
||||
// RFC 4648, Table 2. Invalid input characters map to UINT8_MAX.
|
||||
static const uint8_t kBase64URLDecodeTable[] = {
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 255, 255, 255, 255,
|
||||
62 /* - */,
|
||||
255, 255,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */
|
||||
255, 255, 255, 255, 255, 255, 255,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* A - Z */
|
||||
255, 255, 255, 255,
|
||||
63 /* _ */,
|
||||
255,
|
||||
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
|
||||
42, 43, 44, 45, 46, 47, 48, 49, 50, 51, /* a - z */
|
||||
255, 255, 255, 255,
|
||||
};
|
||||
|
||||
bool
|
||||
Base64URLCharToValue(char aChar, uint8_t* aValue) {
|
||||
uint8_t index = static_cast<uint8_t>(aChar);
|
||||
*aValue = kBase64URLDecodeTable[index & 0x7f];
|
||||
return (*aValue != 255) && !(index & ~0x7f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -359,7 +390,105 @@ Base64Decode(const nsAString& aBinaryData, nsAString& aString)
|
|||
}
|
||||
|
||||
nsresult
|
||||
Base64URLEncode(uint32_t aLength, const uint8_t* aData, nsACString& aString)
|
||||
Base64URLDecode(const nsACString& aString,
|
||||
const dom::Base64URLDecodeOptions& aOptions,
|
||||
FallibleTArray<uint8_t>& aOutput)
|
||||
{
|
||||
// Don't decode empty strings.
|
||||
if (aString.IsEmpty()) {
|
||||
aOutput.Clear();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Check for overflow.
|
||||
uint32_t sourceLength = aString.Length();
|
||||
if (sourceLength > UINT32_MAX / 3) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
const char* source = aString.BeginReading();
|
||||
|
||||
// The decoded length may be 1-2 bytes over, depending on the final quantum.
|
||||
uint32_t decodedLength = (sourceLength * 3) / 4;
|
||||
|
||||
// Determine whether to check for and ignore trailing padding.
|
||||
bool maybePadded = false;
|
||||
switch (aOptions.mPadding) {
|
||||
case dom::Base64URLDecodePadding::Require:
|
||||
if (sourceLength % 4) {
|
||||
// Padded input length must be a multiple of 4.
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
maybePadded = true;
|
||||
break;
|
||||
|
||||
case dom::Base64URLDecodePadding::Ignore:
|
||||
// Check for padding only if the length is a multiple of 4.
|
||||
maybePadded = !(sourceLength % 4);
|
||||
break;
|
||||
|
||||
// If we're expecting unpadded input, no need for additional checks.
|
||||
// `=` isn't in the decode table, so padded strings will fail to decode.
|
||||
default:
|
||||
MOZ_FALLTHROUGH_ASSERT("Invalid decode padding option");
|
||||
case dom::Base64URLDecodePadding::Reject:
|
||||
break;
|
||||
}
|
||||
if (maybePadded && source[sourceLength - 1] == '=') {
|
||||
if (source[sourceLength - 2] == '=') {
|
||||
sourceLength -= 2;
|
||||
} else {
|
||||
sourceLength -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!aOutput.SetCapacity(decodedLength, mozilla::fallible))) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
aOutput.SetLengthAndRetainStorage(decodedLength);
|
||||
uint8_t* output = aOutput.Elements();
|
||||
|
||||
for (; sourceLength >= 4; sourceLength -= 4) {
|
||||
uint8_t w, x, y, z;
|
||||
if (!Base64URLCharToValue(*source++, &w) ||
|
||||
!Base64URLCharToValue(*source++, &x) ||
|
||||
!Base64URLCharToValue(*source++, &y) ||
|
||||
!Base64URLCharToValue(*source++, &z)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
*output++ = w << 2 | x >> 4;
|
||||
*output++ = x << 4 | y >> 2;
|
||||
*output++ = y << 6 | z;
|
||||
}
|
||||
|
||||
if (sourceLength == 3) {
|
||||
uint8_t w, x, y;
|
||||
if (!Base64URLCharToValue(*source++, &w) ||
|
||||
!Base64URLCharToValue(*source++, &x) ||
|
||||
!Base64URLCharToValue(*source++, &y)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
*output++ = w << 2 | x >> 4;
|
||||
*output++ = x << 4 | y >> 2;
|
||||
} else if (sourceLength == 2) {
|
||||
uint8_t w, x;
|
||||
if (!Base64URLCharToValue(*source++, &w) ||
|
||||
!Base64URLCharToValue(*source++, &x)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
*output++ = w << 2 | x >> 4;
|
||||
} else if (sourceLength) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Set the length to the actual number of decoded bytes.
|
||||
aOutput.TruncateLength(output - aOutput.Elements());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Base64URLEncode(uint32_t aLength, const uint8_t* aData,
|
||||
const dom::Base64URLEncodeOptions& aOptions,
|
||||
nsACString& aString)
|
||||
{
|
||||
// Don't encode empty strings.
|
||||
if (aLength == 0) {
|
||||
|
@ -368,11 +497,14 @@ Base64URLEncode(uint32_t aLength, const uint8_t* aData, nsACString& aString)
|
|||
}
|
||||
|
||||
// Check for overflow.
|
||||
if ((static_cast<uint64_t>(aLength) * 6 + 7) / 8 > UINT32_MAX) {
|
||||
if (aLength > (UINT32_MAX / 4) * 3) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!aString.SetLength((aLength * 8 + 5) / 6, fallible)) {
|
||||
// Allocate a buffer large enough to hold the encoded string with padding.
|
||||
// Add one byte for null termination.
|
||||
uint32_t encodedLength = ((aLength + 2) / 3) * 4;
|
||||
if (NS_WARN_IF(!aString.SetCapacity(encodedLength + 1, fallible))) {
|
||||
aString.Truncate();
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -400,6 +532,22 @@ Base64URLEncode(uint32_t aLength, const uint8_t* aData, nsACString& aString)
|
|||
*rawBuffer++ = kBase64URLAlphabet[((aData[index + 1] & 0xf) << 2)];
|
||||
}
|
||||
|
||||
uint32_t length = rawBuffer - aString.BeginWriting();
|
||||
if (aOptions.mPad) {
|
||||
if (length % 4 == 2) {
|
||||
*rawBuffer++ = '=';
|
||||
*rawBuffer++ = '=';
|
||||
length += 2;
|
||||
} else if (length % 4 == 3) {
|
||||
*rawBuffer++ = '=';
|
||||
length += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Null terminate and truncate to the actual number of characters.
|
||||
*rawBuffer = '\0';
|
||||
aString.SetLength(length);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
|
||||
#include "nsString.h"
|
||||
|
||||
#include "mozilla/dom/ThreadSafeChromeUtilsBinding.h"
|
||||
|
||||
class nsIInputStream;
|
||||
|
||||
namespace mozilla {
|
||||
|
@ -36,11 +38,21 @@ Base64Decode(const nsAString& aBinaryData, nsAString& aString);
|
|||
|
||||
/**
|
||||
* Converts |aData| to an unpadded, Base64 URL-encoded string per RFC 4648.
|
||||
* Aims to encode the data in constant time. The caller may free |aData| once
|
||||
* this function returns.
|
||||
* Aims to encode the data in constant time. The caller retains ownership
|
||||
* of |aData|.
|
||||
*/
|
||||
nsresult
|
||||
Base64URLEncode(uint32_t aLength, const uint8_t* aData, nsACString& aString);
|
||||
Base64URLEncode(uint32_t aLength, const uint8_t* aData,
|
||||
const dom::Base64URLEncodeOptions& aOptions,
|
||||
nsACString& aString);
|
||||
|
||||
/**
|
||||
* Decodes a Base64 URL-encoded |aString| into |aOutput|.
|
||||
*/
|
||||
nsresult
|
||||
Base64URLDecode(const nsACString& aString,
|
||||
const dom::Base64URLDecodeOptions& aOptions,
|
||||
FallibleTArray<uint8_t>& aOutput);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче