228 строки
5.7 KiB
JavaScript
228 строки
5.7 KiB
JavaScript
(function () {
|
|
for (var key in sjcl.beware) {
|
|
if (sjcl.beware.hasOwnProperty(key)) {
|
|
sjcl.beware[key]();
|
|
}
|
|
}
|
|
})();
|
|
|
|
var Braintree = {
|
|
sjcl: sjcl,
|
|
version: "1.3.10"
|
|
};
|
|
|
|
Braintree.generateAesKey = function () {
|
|
return {
|
|
key: sjcl.random.randomWords(8, 0),
|
|
encrypt: function (plainText) {
|
|
return this.encryptWithIv(plainText, sjcl.random.randomWords(4, 0));
|
|
},
|
|
encryptWithIv: function (plaintext, iv) {
|
|
var aes = new sjcl.cipher.aes(this.key),
|
|
plaintextBits = sjcl.codec.utf8String.toBits(plaintext),
|
|
ciphertextBits = sjcl.mode.cbc.encrypt(aes, plaintextBits, iv),
|
|
ciphertextAndIvBits = sjcl.bitArray.concat(iv, ciphertextBits);
|
|
|
|
return sjcl.codec.base64.fromBits(ciphertextAndIvBits);
|
|
}
|
|
};
|
|
};
|
|
|
|
Braintree.create = function (publicKey) {
|
|
return new Braintree.EncryptionClient(publicKey);
|
|
};
|
|
|
|
Braintree.EncryptionClient = function (publicKey) {
|
|
var self = this,
|
|
hiddenFields = [];
|
|
|
|
self.publicKey = publicKey;
|
|
self.version = Braintree.version;
|
|
|
|
var createElement = function (tagName, attrs) {
|
|
var element, attr, value;
|
|
|
|
element = document.createElement(tagName);
|
|
|
|
for (attr in attrs) {
|
|
if (attrs.hasOwnProperty(attr)) {
|
|
value = attrs[attr];
|
|
element.setAttribute(attr, value);
|
|
}
|
|
}
|
|
|
|
return element;
|
|
};
|
|
|
|
var extractForm = function (object) {
|
|
if (window.jQuery && object instanceof jQuery) {
|
|
return object[0];
|
|
} else if (object.nodeType && object.nodeType === 1) {
|
|
return object;
|
|
} else {
|
|
return document.getElementById(object);
|
|
}
|
|
};
|
|
|
|
var extractIntegers = function (asn1) {
|
|
var parts = [],
|
|
start, end, data,
|
|
i;
|
|
|
|
if (asn1.typeName() === "INTEGER") {
|
|
start = asn1.posContent();
|
|
end = asn1.posEnd();
|
|
data = asn1.stream.hexDump(start, end).replace(/[ \n]/g, "");
|
|
parts.push(data);
|
|
}
|
|
|
|
if (asn1.sub !== null) {
|
|
for (i = 0; i < asn1.sub.length; i++) {
|
|
parts = parts.concat(extractIntegers(asn1.sub[i]));
|
|
}
|
|
}
|
|
|
|
return parts;
|
|
};
|
|
|
|
var findInputs = function (element) {
|
|
var found = [],
|
|
children = element.children,
|
|
child, i;
|
|
|
|
for (i = 0; i < children.length; i++) {
|
|
child = children[i];
|
|
|
|
if (child.nodeType === 1 && child.attributes["data-encrypted-name"]) {
|
|
found.push(child);
|
|
} else if (child.children && child.children.length > 0) {
|
|
found = found.concat(findInputs(child));
|
|
}
|
|
}
|
|
|
|
return found;
|
|
};
|
|
|
|
var generateRsaKey = function () {
|
|
var asn1, exponent, parts, modulus, rawKey, rsa;
|
|
|
|
try {
|
|
rawKey = b64toBA(publicKey);
|
|
asn1 = ASN1.decode(rawKey);
|
|
} catch (e) {
|
|
throw "Invalid encryption key. Please use the key labeled 'Client-Side Encryption Key'";
|
|
}
|
|
|
|
parts = extractIntegers(asn1);
|
|
|
|
if (parts.length !== 2) {
|
|
throw "Invalid encryption key. Please use the key labeled 'Client-Side Encryption Key'";
|
|
}
|
|
|
|
modulus = parts[0];
|
|
exponent = parts[1];
|
|
|
|
rsa = new RSAKey();
|
|
rsa.setPublic(modulus, exponent);
|
|
|
|
return rsa;
|
|
};
|
|
|
|
var generateHmacKey = function () {
|
|
return {
|
|
key: sjcl.random.randomWords(8, 0),
|
|
sign: function (message) {
|
|
var hmac = new sjcl.misc.hmac(this.key, sjcl.hash.sha256),
|
|
signature = hmac.encrypt(message);
|
|
|
|
return sjcl.codec.base64.fromBits(signature);
|
|
}
|
|
};
|
|
};
|
|
|
|
self.encrypt = function (plaintext) {
|
|
var rsa = generateRsaKey(),
|
|
aes = Braintree.generateAesKey(),
|
|
hmac = generateHmacKey(),
|
|
ciphertext = aes.encrypt(plaintext),
|
|
signature = hmac.sign(sjcl.codec.base64.toBits(ciphertext)),
|
|
combinedKey = sjcl.bitArray.concat(aes.key, hmac.key),
|
|
encodedKey = sjcl.codec.base64.fromBits(combinedKey),
|
|
hexEncryptedKey = rsa.encrypt(encodedKey),
|
|
prefix = "$bt4|javascript_" + self.version.replace(/\./g, "_") + "$",
|
|
encryptedKey = null;
|
|
|
|
if(hexEncryptedKey) {
|
|
encryptedKey = hex2b64(hexEncryptedKey);
|
|
}
|
|
|
|
return prefix + encryptedKey + "$" + ciphertext + "$" + signature;
|
|
};
|
|
|
|
self.encryptForm = function (form) {
|
|
var element, encryptedValue,
|
|
fieldName, hiddenField,
|
|
i, inputs;
|
|
|
|
form = extractForm(form);
|
|
inputs = findInputs(form);
|
|
|
|
while (hiddenFields.length > 0) {
|
|
try {
|
|
form.removeChild(hiddenFields[0]);
|
|
} catch (err) {}
|
|
hiddenFields.splice(0, 1);
|
|
}
|
|
|
|
for (i = 0; i < inputs.length; i++) {
|
|
element = inputs[i];
|
|
fieldName = element.getAttribute("data-encrypted-name");
|
|
encryptedValue = self.encrypt(element.value);
|
|
element.removeAttribute("name");
|
|
hiddenField = createElement("input", {
|
|
value: encryptedValue,
|
|
type: "hidden",
|
|
name: fieldName
|
|
});
|
|
hiddenFields.push(hiddenField);
|
|
form.appendChild(hiddenField);
|
|
}
|
|
};
|
|
|
|
self.onSubmitEncryptForm = function (form, callback) {
|
|
var wrappedCallback;
|
|
|
|
form = extractForm(form);
|
|
|
|
wrappedCallback = function (e) {
|
|
self.encryptForm(form);
|
|
return (!!callback) ? callback(e) : e;
|
|
};
|
|
|
|
if (window.jQuery) {
|
|
window.jQuery(form).submit(wrappedCallback);
|
|
} else if (form.addEventListener) {
|
|
form.addEventListener("submit", wrappedCallback, false);
|
|
} else if (form.attachEvent) {
|
|
form.attachEvent("onsubmit", wrappedCallback);
|
|
}
|
|
};
|
|
|
|
// backwards compatibility
|
|
self.formEncrypter = {
|
|
encryptForm: self.encryptForm,
|
|
extractForm: extractForm,
|
|
onSubmitEncryptForm: self.onSubmitEncryptForm
|
|
};
|
|
|
|
sjcl.random.startCollectors();
|
|
};
|
|
|
|
window.Braintree = Braintree;
|
|
|
|
if (typeof define === "function") {
|
|
define("braintree", function () {
|
|
return Braintree;
|
|
});
|
|
}
|