Bug 433949 - Use WeaveCrypto component (NSS) instead of OpenSSL

This commit is contained in:
Justin Dolske 2008-06-23 16:23:57 -07:00
parent bb55bba909
commit 6ed3755104
13 changed files with 524 additions and 463 deletions

View File

@ -19,6 +19,7 @@
*
* Contributor(s):
* Dan Mills <thunder@mozilla.com>
* Justin Dolske <dolske@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -65,6 +66,15 @@ CryptoSvc.prototype = {
return this.__os;
},
__crypto: null,
get _crypto() {
if (!this.__crypto)
this.__crypto = Cc["@labs.mozilla.com/Weave/Crypto;1"].
createInstance(Ci.IWeaveCrypto);
return this.__crypto;
},
get defaultAlgorithm() {
return Utils.prefs.getCharPref("encryption");
},
@ -82,271 +92,6 @@ CryptoSvc.prototype = {
branch.addObserver("extensions.weave.encryption", this, false);
},
_openssl: function Crypto__openssl() {
let extMgr = Cc["@mozilla.org/extensions/manager;1"]
.getService(Ci.nsIExtensionManager);
let loc = extMgr.getInstallLocation("{340c2bbc-ce74-4362-90b5-7c26312808ef}");
let wrap = loc.getItemLocation("{340c2bbc-ce74-4362-90b5-7c26312808ef}");
wrap.append("openssl");
let bin;
let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
switch(os) {
case "WINNT":
wrap.append("win32");
wrap.append("exec.bat");
bin = wrap.parent.path + "\\openssl.exe";
break;
case "Linux":
case "Darwin":
wrap.append("unix");
wrap.append("exec.sh");
bin = "openssl";
break;
default:
throw "encryption not supported on this platform: " + os;
}
let args = Array.prototype.slice.call(arguments);
args.unshift(wrap, Utils.getTmp().path, bin);
let rv = Utils.runCmd.apply(null, args);
if (rv != 0)
throw "openssl did not run successfully, error code " + rv;
},
_opensslPBE: function Crypto__openssl(op, algorithm, input, password) {
let inputFile = Utils.getTmp("input");
let [inputFOS] = Utils.open(inputFile, ">");
inputFOS.writeString(input);
inputFOS.close();
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.writeString(password);
passFOS.close();
try {
this._openssl(algorithm, op, "-a", "-salt", "-in", "input",
"-out", "output", "-pass", "file:pass");
} catch (e) {
throw e;
} finally {
passFile.remove(false);
inputFile.remove(false);
}
let outputFile = Utils.getTmp("output");
let [outputFIS] = Utils.open(outputFile, "<");
let ret = Utils.readStream(outputFIS);
outputFIS.close();
outputFile.remove(false);
return ret;
},
// generates a random string that can be used as a passphrase
_opensslRand: function Crypto__opensslRand(length) {
if (!length)
length = 128;
let outputFile = Utils.getTmp("output");
if (outputFile.exists())
outputFile.remove(false);
this._openssl("rand", "-base64", "-out", "output", length);
let [outputFIS] = Utils.open(outputFile, "<");
let ret = Utils.readStream(outputFIS);
outputFIS.close();
outputFile.remove(false);
return ret;
},
// generates an rsa public/private key pair, with the private key encrypted
_opensslRSAKeyGen: function Crypto__opensslRSAKeyGen(identity, algorithm, bits) {
if (!algorithm)
algorithm = "aes-256-cbc";
if (!bits)
bits = "2048";
let privKeyF = Utils.getTmp("privkey.pem");
if (privKeyF.exists())
privKeyF.remove(false);
this._openssl("genrsa", "-out", "privkey.pem", bits);
let pubKeyF = Utils.getTmp("pubkey.pem");
if (pubKeyF.exists())
pubKeyF.remove(false);
this._openssl("rsa", "-in", "privkey.pem", "-out", "pubkey.pem",
"-outform", "PEM", "-pubout");
let cryptedKeyF = Utils.getTmp("enckey.pem");
if (cryptedKeyF.exists())
cryptedKeyF.remove(false);
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.writeString(identity.password);
passFOS.close();
try {
this._openssl("pkcs8", "-in", "privkey.pem", "-out", "enckey.pem",
"-topk8", "-v2", algorithm, "-passout", "file:pass");
} catch (e) {
throw e;
} finally {
passFile.remove(false);
privKeyF.remove(false);
}
let [cryptedKeyFIS] = Utils.open(cryptedKeyF, "<");
let cryptedKey = Utils.readStream(cryptedKeyFIS);
cryptedKeyFIS.close();
cryptedKeyF.remove(false);
let [pubKeyFIS] = Utils.open(pubKeyF, "<");
let pubKey = Utils.readStream(pubKeyFIS);
pubKeyFIS.close();
pubKeyF.remove(false);
return [cryptedKey, pubKey];
},
// returns 'input' encrypted with the 'pubkey' public RSA key
_opensslRSAencrypt: function Crypto__opensslRSAencrypt(input, identity) {
let inputFile = Utils.getTmp("input");
let [inputFOS] = Utils.open(inputFile, ">");
inputFOS.writeString(input);
inputFOS.close();
let keyFile = Utils.getTmp("key");
let [keyFOS] = Utils.open(keyFile, ">");
keyFOS.writeString(identity.pubkey);
keyFOS.close();
let tmpFile = Utils.getTmp("tmp-output");
if (tmpFile.exists())
tmpFile.remove(false);
let outputFile = Utils.getTmp("output");
if (outputFile.exists())
outputFile.remove(false);
this._openssl("rsautl", "-encrypt", "-pubin", "-inkey", "key",
"-in", "input", "-out", "tmp-output");
this._openssl("base64", "-in", "tmp-output", "-out", "output");
let [outputFIS] = Utils.open(outputFile, "<");
let output = Utils.readStream(outputFIS);
outputFIS.close();
outputFile.remove(false);
return output;
},
// returns 'input' decrypted with the 'privkey' private RSA key and password
_opensslRSAdecrypt: function Crypto__opensslRSAdecrypt(input, identity) {
let inputFile = Utils.getTmp("input");
let [inputFOS] = Utils.open(inputFile, ">");
inputFOS.writeString(input);
inputFOS.close();
let keyFile = Utils.getTmp("key");
let [keyFOS] = Utils.open(keyFile, ">");
keyFOS.writeString(identity.privkey);
keyFOS.close();
let tmpKeyFile = Utils.getTmp("tmp-key");
if (tmpKeyFile.exists())
tmpKeyFile.remove(false);
let tmpFile = Utils.getTmp("tmp-output");
if (tmpFile.exists())
tmpFile.remove(false);
let outputFile = Utils.getTmp("output");
if (outputFile.exists())
outputFile.remove(false);
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.writeString(identity.password);
passFOS.close();
try {
this._openssl("base64", "-d", "-in", "input", "-out", "tmp-output");
// FIXME: this is because openssl.exe (in windows only) doesn't
// seem to support -passin for rsautl, but it works for rsa.
this._openssl("rsa", "-in", "key", "-out", "tmp-key", "-passin", "file:pass");
this._openssl("rsautl", "-decrypt", "-inkey", "tmp-key",
"-in", "tmp-output", "-out", "output");
} catch(e) {
throw e;
} finally {
passFile.remove(false);
tmpKeyFile.remove(false);
tmpFile.remove(false);
keyFile.remove(false);
}
let [outputFIS] = Utils.open(outputFile, "<");
let output = Utils.readStream(outputFIS);
outputFIS.close();
outputFile.remove(false);
return output;
},
// returns the public key from the private key
_opensslRSAkeydecrypt: function Crypto__opensslRSAkeydecrypt(identity) {
let keyFile = Utils.getTmp("key");
let [keyFOS] = Utils.open(keyFile, ">");
keyFOS.writeString(identity.privkey);
keyFOS.close();
let outputFile = Utils.getTmp("output");
if (outputFile.exists())
outputFile.remove(false);
// nsIProcess doesn't support stdin, so we write a file instead
let passFile = Utils.getTmp("pass");
let [passFOS] = Utils.open(passFile, ">", PERMS_PASSFILE);
passFOS.writeString(identity.password);
passFOS.close();
try {
this._openssl("rsa", "-in", "key", "-pubout", "-out", "output",
"-passin", "file:pass");
} catch(e) {
throw e;
} finally {
passFile.remove(false);
}
let [outputFIS] = Utils.open(outputFile, "<");
let output = Utils.readStream(outputFIS);
outputFIS.close();
outputFile.remove(false);
return output;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]),
// nsIObserver
@ -381,99 +126,107 @@ CryptoSvc.prototype = {
// Crypto
PBEencrypt: function Crypto_PBEencrypt(data, identity, algorithm) {
encryptData: function Crypto_encryptData(data, identity) {
let self = yield;
let ret;
if (!algorithm)
algorithm = this.defaultAlgorithm;
this._log.trace("encrypt called. [id=" + identity.realm + "]");
if (algorithm != "none")
this._log.debug("Encrypting data");
switch (algorithm) {
case "none":
if ("none" == this.defaultAlgorithm) {
this._log.debug("NOT encrypting data");
ret = data;
break;
case "aes-128-cbc":
case "aes-192-cbc":
case "aes-256-cbc":
case "bf-cbc":
case "des-ede3-cbc":
ret = this._opensslPBE("-e", algorithm, data, identity.password);
break;
default:
throw "Unknown encryption algorithm: " + algorithm;
} else {
let symkey = identity.bulkKey;
let iv = identity.bulkIV;
ret = this._crypto.encrypt(data, symkey, iv);
}
if (algorithm != "none")
this._log.debug("Done encrypting data");
self.done(ret);
},
PBEdecrypt: function Crypto_PBEdecrypt(data, identity, algorithm) {
decryptData: function Crypto_decryptData(data, identity) {
let self = yield;
let ret;
if (!algorithm)
algorithm = this.defaultAlgorithm;
this._log.trace("decrypt called. [id=" + identity.realm + "]");
if (algorithm != "none")
this._log.debug("Decrypting data");
switch (algorithm) {
case "none":
if ("none" == this.defaultAlgorithm) {
this._log.debug("NOT decrypting data");
ret = data;
break;
case "aes-128-cbc":
case "aes-192-cbc":
case "aes-256-cbc":
case "bf-cbc":
case "des-ede3-cbc":
ret = this._opensslPBE("-d", algorithm, data, identity.password);
break;
default:
throw "Unknown encryption algorithm: " + algorithm;
} else {
let symkey = identity.bulkKey;
let iv = identity.bulkIV;
ret = this._crypto.decrypt(data, symkey, iv);
}
if (algorithm != "none")
this._log.debug("Done decrypting data");
self.done(ret);
},
PBEkeygen: function Crypto_PBEkeygen() {
/*
* randomKeyGen
*
* Generates a random symmetric key and IV, and puts them in the specified
* identity.
*/
randomKeyGen: function Crypto_randomKeyGen(identity) {
let self = yield;
let ret = this._opensslRand();
self.done(ret);
this._log.trace("randomKeyGen called. [id=" + identity.realm + "]");
let symkey = this._crypto.generateRandomKey();
let iv = this._crypto.generateRandomIV();
identity.bulkKey = symkey;
identity.bulkIV = iv;
},
/*
* RSAkeygen
*
* Generates a new RSA keypair, as well as the salt/IV used for protecting
* the private key, and puts them in the specified identity.
*/
RSAkeygen: function Crypto_RSAkeygen(identity) {
let self = yield;
let ret = this._opensslRSAKeyGen(identity);
this._log.trace("RSAkeygen called. [id=" + identity.realm + "]");
let privOut = {};
let pubOut = {};
// Generate a blob of random data for salting the passphrase used to
// encrypt the private key.
let salt = this._crypto.generateRandomBytes(32);
let iv = this._crypto.generateRandomIV();
this._crypto.generateKeypair(identity.password,
salt, iv,
pubOut, privOut);
identity.keypairAlg = "RSA";
identity.pubkey = pubOut.value;
identity.privkey = privOut.value;
identity.passphraseSalt = salt;
identity.privkeyWrapIV = iv;
},
wrapKey : function Crypto_wrapKey(data, identity) {
let self = yield;
this._log.trace("wrapKey called. [id=" + identity.realm + "]");
let ret = this._crypto.wrapSymmetricKey(data, identity.pubkey);
self.done(ret);
},
RSAencrypt: function Crypto_RSAencrypt(data, identity) {
unwrapKey: function Crypto_unwrapKey(data, identity) {
let self = yield;
let ret = this._opensslRSAencrypt(data, identity);
this._log.trace("upwrapKey called. [id=" + identity.realm + "]");
let ret = this._crypto.unwrapSymmetricKey(data,
identity.privkey,
identity.password,
identity.passphraseSalt,
identity.privkeyWrapIV);
self.done(ret);
},
RSAdecrypt: function Crypto_RSAdecrypt(data, identity) {
let self = yield;
let ret = this._opensslRSAdecrypt(data, identity);
self.done(ret);
},
RSAkeydecrypt: function Crypto_RSAkeydecrypt(identity) {
let self = yield;
let ret = this._opensslRSAkeydecrypt(identity);
self.done(ret);
}
};

View File

@ -161,24 +161,13 @@ Engine.prototype = {
this.__snapshot = value;
},
get pbeId() {
let id = ID.get('Engine:PBE:' + this.name);
if (!id)
id = ID.get('Engine:PBE:default');
if (!id)
throw "No identity found for engine PBE!";
return id;
},
get engineId() {
let id = ID.get('Engine:' + this.name);
if (!id ||
id.username != this.pbeId.username || id.realm != this.pbeId.realm) {
let password = null;
if (id)
password = id.password;
id = new Identity(this.pbeId.realm + ' - ' + this.logName,
this.pbeId.username, password);
if (!id) {
// Copy the service login from WeaveID
let masterID = ID.get('WeaveID');
id = new Identity(this.logName, masterID.username, masterID.password);
ID.set('Engine:' + this.name, id);
}
return id;
@ -271,10 +260,8 @@ Engine.prototype = {
this._log.info("Local snapshot version: " + this._snapshot.version);
this._log.info("Server maxVersion: " + this._remote.status.data.maxVersion);
if ("none" != Utils.prefs.getCharPref("encryption")) {
let symkey = yield this._remote.keys.getKey(self.cb, this.pbeId);
this.engineId.setTempPassword(symkey);
}
if ("none" != Utils.prefs.getCharPref("encryption"))
yield this._remote.keys.getKeyAndIV(self.cb, this.engineId);
// 1) Fetch server deltas
let server = {};

View File

@ -341,28 +341,40 @@ BookmarksEngine.prototype = {
this._annoSvc.EXPIRE_NEVER);
// Create a new symmetric key, to be used only for encrypting this share.
Crypto.PBEkeygen.async(Crypto, self.cb);
let newSymKey = yield;
// XXX HACK. Seems like the engine shouldn't have to be doing any of this, or
// should use its own identity here.
let tmpIdentity = {
realm : "temp ID",
bulkKey : null,
bulkIV : null
};
Crypto.randomKeyGen.async(Crypto, self.cb, tmpIdentity);
yield;
let bulkKey = tmpIdentity.bulkKey;
let bulkIV = tmpIdentity.bulkIV;
/* Get public keys for me and the user I'm sharing with.
Each user's public key is stored in /user/username/public/pubkey. */
let myPubKeyFile = new Resource("/user/" + myUserName + "/public/pubkey");
myPubKeyFile.get(self.cb);
let myPubKey = yield;
let idRSA = ID.get('WeaveCryptoID');
let userPubKeyFile = new Resource("/user/" + username + "/public/pubkey");
userPubKeyFile.get(self.cb);
let userPubKey = yield;
/* Create the keyring, containing the sym key encrypted with each
of our public keys: */
Crypto.RSAencrypt.async(Crypto, self.cb, symKey, {pubkey: myPubKey} );
Crypto.wrapKey.async(Crypto, self.cb, bulkKey, {realm : "tmpWrapID", pubkey: idRSA.pubkey} );
let encryptedForMe = yield;
Crypto.RSAencrypt.async(Crypto, self.cb, symKey, {pubkey: userPubKey} );
Crypto.wrapKey.async(Crypto, self.cb, bulkKey, {realm : "tmpWrapID", pubkey: userPubKey} );
let encryptedForYou = yield;
let keyring = { myUserName: encryptedForMe,
username: encryptedForYou };
let keys = {
ring : { },
bulkIV : bulkIV
};
keys.ring[myUserName] = encryptedForMe;
keys.ring[username] = encryptedForYou;
let keyringFile = new Resource( serverPath + "/" + KEYRING_FILE_NAME );
keyringFile.put( self.cb, this._json.encode( keyring ) );
keyringFile.put( self.cb, this._json.encode( keys ) );
yield;
// Call Atul's js api for setting htaccess:
@ -390,8 +402,14 @@ BookmarksEngine.prototype = {
// key that we'll use to encrypt.
let keyringFile = new Resource(serverPath + "/" + KEYRING_FILE_NAME);
keyringFile.get(self.cb);
let keyring = yield;
let symKey = keyring[ myUserName ];
let keys = yield;
// Unwrap (decrypt) the key with the user's private key.
let idRSA = ID.get('WeaveCryptoID');
let bulkKey = yield Crypto.unwrapKey.async(Crypto, self.cb,
keys.ring[myUserName], idRSA);
let bulkIV = keys.bulkIV;
// Get the json-wrapped contents of everything in the folder:
let json = this._store._wrapMount( folderNode, myUserName );
/* TODO what does wrapMount do with this username? Should I be passing
@ -399,7 +417,12 @@ BookmarksEngine.prototype = {
// Encrypt it with the symkey and put it into the shared-bookmark file.
let bmkFile = new Resource(serverPath + "/" + SHARED_BOOKMARK_FILE_NAME);
Crypto.PBEencrypt.async( Crypto, self.cb, json, {password:symKey} );
let tmpIdentity = {
realm : "temp ID",
bulkKey : bulkKey,
bulkIV : bulkIV
};
Crypto.encryptData.async( Crypto, self.cb, json, tmpIdentity );
let cyphertext = yield;
bmkFile.put( self.cb, cyphertext );
yield;
@ -545,14 +568,18 @@ BookmarksEngine.prototype = {
// key that we'll use to encrypt.
let keyringFile = new Resource(serverPath + "/" + KEYRING_FILE_NAME);
keyringFile.get(self.cb);
let keyring = yield;
let symKey = keyring[ myUserName ];
let keys = yield;
// Decrypt the contents of the bookmark file with the symmetric key:
let bmkFile = new Resource(serverPath + "/" + SHARED_BOOKMARK_FILE_NAME);
bmkFile.get(self.cb);
let cyphertext = yield;
Crypto.PBEdecrypt.async( Crypto, self.cb, cyphertext, {password:symKey} );
let tmpIdentity = {
realm : "temp ID",
bulkKey : keys.ring[myUserName],
bulkIV : keys.bulkIV
};
Crypto.decryptData.async( Crypto, self.cb, cyphertext, tmpIdentity );
let json = yield;
// TODO error handling (see what Resource can throw or return...)

View File

@ -87,28 +87,26 @@ IDManager.prototype = {
*/
function Identity(realm, username, password) {
this._realm = realm;
this._username = username;
this.realm = realm;
this.username = username;
this._password = password;
}
Identity.prototype = {
get realm() { return this._realm; },
set realm(value) { this._realm = value; },
realm : null,
get username() { return this._username; },
set username(value) { this._username = value; },
// Only the "WeaveCryptoID" realm uses these:
privkey : null,
pubkey : null,
passphraseSalt : null,
privkeyWrapIV : null,
// Only the per-engine identity uses these:
bulkKey : null,
bulkIV : null,
username : null,
get userHash() { return Utils.sha1(this.username); },
_privkey: null,
get privkey() { return this._privkey; },
set privkey(value) { this._privkey = value; },
// FIXME: get this from the privkey using crypto.js?
_pubkey: null,
get pubkey() { return this._pubkey; },
set pubkey(value) { this._pubkey = value; },
_password: null,
get password() {
if (!this._password)

View File

@ -282,7 +282,7 @@ CryptoFilter.prototype = {
beforePUT: function CryptoFilter_beforePUT(data) {
let self = yield;
this._log.debug("Encrypting data");
Crypto.PBEencrypt.async(Crypto, self.cb, data, this._remote.engineId);
Crypto.encryptData.async(Crypto, self.cb, data, this._remote.engineId);
let ret = yield;
self.done(ret);
},
@ -292,7 +292,7 @@ CryptoFilter.prototype = {
this._log.debug("Decrypting data");
if (!this._remote.status.data)
throw "Remote status must be initialized before crypto filter can be used"
Crypto.PBEdecrypt.async(Crypto, self.cb, data, this._remote.engineId);
Crypto.decryptData.async(Crypto, self.cb, data, this._remote.engineId);
let ret = yield;
self.done(ret);
}
@ -320,21 +320,25 @@ Keychain.prototype = {
this.__proto__.__proto__._init.call(this, this._remote.serverPrefix + "keys.json");
this.pushFilter(new JsonFilter());
},
_getKey: function Keychain__getKey(identity) {
_getKeyAndIV: function Keychain__getKeyAndIV(identity) {
let self = yield;
this.get(self.cb);
yield;
if (!this.data || !this.data.ring || !this.data.ring[identity.username])
throw "Keyring does not contain a key for this user";
Crypto.RSAdecrypt.async(Crypto, self.cb,
this.data.ring[identity.username], identity);
let symkey = yield;
self.done(symkey);
// Unwrap (decrypt) the key with the user's private key.
let idRSA = ID.get('WeaveCryptoID');
let symkey = yield Crypto.unwrapKey.async(Crypto, self.cb,
this.data.ring[identity.username], idRSA);
let iv = this.data.bulkIV;
identity.bulkKey = symkey;
identity.bulkIV = iv;
},
getKey: function Keychain_getKey(onComplete, identity) {
this._getKey.async(this, onComplete, identity);
getKeyAndIV: function Keychain_getKeyAndIV(onComplete, identity) {
this._getKeyAndIV.async(this, onComplete, identity);
}
// FIXME: implement setKey()
};
@ -346,7 +350,6 @@ function RemoteStore(engine) {
RemoteStore.prototype = {
get serverPrefix() this._engine.serverPrefix,
get engineId() this._engine.engineId,
get pbeId() this._engine.pbeId,
get status() {
let status = new Status(this);
@ -420,23 +423,20 @@ RemoteStore.prototype = {
// Does a fresh upload of the given snapshot to a new store
_initialize: function RStore__initialize(snapshot) {
let self = yield;
let symkey;
let wrappedSymkey;
if ("none" != Utils.prefs.getCharPref("encryption")) {
symkey = yield Crypto.PBEkeygen.async(Crypto, self.cb);
if (!symkey)
throw "Could not generate a symmetric encryption key";
this.engineId.setTempPassword(symkey);
Crypto.randomKeyGen.async(Crypto, self.cb, this.engineId);
yield;
symkey = yield Crypto.RSAencrypt.async(Crypto, self.cb,
this.engineId.password,
this.pbeId);
if (!symkey)
throw "Could not encrypt symmetric encryption key";
// Wrap (encrypt) this key with the user's public key.
let idRSA = ID.get('WeaveCryptoID');
wrappedSymkey = yield Crypto.wrapKey.async(Crypto, self.cb,
this.engineId.bulkKey, idRSA);
}
let keys = {ring: {}};
keys.ring[this.engineId.username] = symkey;
let keys = {ring: {}, bulkIV: this.engineId.bulkIV};
keys.ring[this.engineId.username] = wrappedSymkey;
yield this.keys.put(self.cb, keys);
yield this._snapshot.put(self.cb, snapshot.data);
@ -505,6 +505,7 @@ RemoteStore.prototype = {
snap.version = this.status.data.maxVersion;
if (lastSyncSnap.version < this.status.data.snapVersion) {
this._log.trace("Getting latest from snap --> scratch");
self.done(yield this._getLatestFromScratch.async(this, self.cb));
return;

View File

@ -163,6 +163,14 @@ WeaveSvc.prototype = {
return this.__dirSvc;
},
__json: null,
get _json() {
if (!this.__json)
this.__json = Cc["@mozilla.org/dom/json;1"].
createInstance(Ci.nsIJSON);
return this.__json;
},
// Timer object for automagically syncing
_scheduleTimer: null,
@ -357,25 +365,53 @@ WeaveSvc.prototype = {
finally { DAV.defaultPrefix = prefix; }
},
_keyCheck: function WeaveSvc__keyCheck() {
_getKeypair : function WeaveSync__getKeypair() {
let self = yield;
if ("none" != Utils.prefs.getCharPref("encryption")) {
DAV.GET("private/privkey", self.cb);
let keyResp = yield;
Utils.ensureStatus(keyResp.status,
"Could not get private key from server", [[200,300],404]);
if ("none" == Utils.prefs.getCharPref("encryption"))
return;
if (keyResp.status != 404) {
let id = ID.get('WeaveCryptoID');
id.privkey = keyResp.responseText;
Crypto.RSAkeydecrypt.async(Crypto, self.cb, id);
id.pubkey = yield;
} else {
this._log.trace("Retrieving keypair from server");
// XXX this kind of replaces _keyCheck
// seems like key generation should only happen during setup?
DAV.GET("private/privkey", self.cb);
let privkeyResp = yield;
Utils.ensureStatus(privkeyResp.status,
"Could not get private key from server", [[200,300],404]);
DAV.GET("public/pubkey", self.cb);
let pubkeyResp = yield;
Utils.ensureStatus(pubkeyResp.status,
"Could not get public key from server", [[200,300],404]);
if (privkeyResp.status == 404 || pubkeyResp.status == 404) {
this._generateKeys.async(this, self.cb);
yield;
}
return;
}
let privkeyData = this._json.decode(privkeyResp.responseText);
let pubkeyData = this._json.decode(pubkeyResp.responseText);
if (!privkeyData || !pubkeyData)
throw "Bad keypair JSON";
if (privkeyData.version != 1 || pubkeyData.version != 1)
throw "Unexpected keypair data version";
if (privkeyData.algorithm != "RSA" || pubkeyData.algorithm != "RSA")
throw "Only RSA keys currently supported";
let id = ID.get('WeaveCryptoID');
id.keypairAlg = privkeyData.algorithm;
id.privkey = privkeyData.privkey;
id.privkeyWrapIV = privkeyData.privkeyIV;
id.passphraseSalt = privkeyData.privkeySalt;
id.pubkey = pubkeyData.pubkey;
// XXX note that we have not used the private key, so we don't yet
// know if the user's passphrase works or not.
},
_generateKeys: function WeaveSync__generateKeys() {
@ -383,12 +419,10 @@ WeaveSvc.prototype = {
this._log.debug("Generating new RSA key");
// RSAkeygen will set the needed |id| properties.
let id = ID.get('WeaveCryptoID');
Crypto.RSAkeygen.async(Crypto, self.cb, id);
let [privkey, pubkey] = yield;
id.privkey = privkey;
id.pubkey = pubkey;
yield;
DAV.MKCOL("private/", self.cb);
let ret = yield;
@ -400,11 +434,26 @@ WeaveSvc.prototype = {
if (!ret)
throw "Could not create public key directory";
DAV.PUT("private/privkey", privkey, self.cb);
let privkeyData = { version : 1,
algorithm : id.keypairAlg,
privkey : id.privkey,
privkeyIV : id.privkeyWrapIV,
privkeySalt : id.passphraseSalt
};
let data = this._json.encode(privkeyData);
DAV.PUT("private/privkey", data, self.cb);
ret = yield;
Utils.ensureStatus(ret.status, "Could not upload private key");
DAV.PUT("public/pubkey", pubkey, self.cb);
let pubkeyData = { version : 1,
algorithm : id.keypairAlg,
pubkey : id.pubkey,
};
data = this._json.encode(pubkeyData);
DAV.PUT("public/pubkey", data, self.cb);
ret = yield;
Utils.ensureStatus(ret.status, "Could not upload public key");
},
@ -491,7 +540,7 @@ WeaveSvc.prototype = {
this._versionCheck.async(this, self.cb);
yield;
this._keyCheck.async(this, self.cb);
this._getKeypair.async(this, self.cb);
yield;
this._loggedIn = true;
@ -559,7 +608,7 @@ WeaveSvc.prototype = {
this._versionCheck.async(this, self.cb);
yield;
this._keyCheck.async(this, self.cb);
this._getKeypair.async(this, self.cb);
yield;
let engines = Engines.getAll();
@ -586,7 +635,7 @@ WeaveSvc.prototype = {
this._versionCheck.async(this, self.cb);
yield;
this._keyCheck.async(this, self.cb);
this._getKeypair.async(this, self.cb);
yield;
let engines = Engines.getAll();

View File

@ -5,15 +5,20 @@ const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
// initialize nss
let ch = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
let ds = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
let provider = {
getFile: function(prop, persistent) {
persistent.value = true;
if (prop == "ExtPrefDL") {
if (prop == "ExtPrefDL")
return [ds.get("CurProcD", Ci.nsIFile)];
}
else if (prop == "ProfD")
return ds.get("CurProcD", Ci.nsIFile);
throw Cr.NS_ERROR_FAILURE;
},
QueryInterface: function(iid) {

View File

@ -0,0 +1,149 @@
function run_test() {
var cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"].
getService(Ci.IWeaveCrypto);
// 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.
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
key = "St1tFCor7vQEJNug/465dQ==";
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
// 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);
// 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);
// Test with 192 bit key.
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
key = "iz35tuIMq4/H+IYw2KTgow==";
iv = "TJYrvva2KxvkM8hvOIvWp3xgjTXgq5Ss";
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);
// Test with 256 bit key.
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_256_CBC;
key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI=";
iv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk=";
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);
// Test with bogus inputs
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
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,63 @@
function run_test() {
var cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"].
getService(Ci.IWeaveCrypto);
var salt = cryptoSvc.generateRandomBytes(16);
do_check_eq(salt.length, 24);
var iv = cryptoSvc.generateRandomIV();
do_check_eq(iv.length, 24);
var symKey = cryptoSvc.generateRandomKey();
do_check_eq(symKey.length, 44);
// Tests with a 2048 bit key (the default)
do_check_eq(cryptoSvc.keypairBits, 2048)
var pubOut = {};
var privOut = {};
cryptoSvc.generateKeypair("my passphrase", salt, iv, pubOut, privOut);
var pubKey = pubOut.value;
var privKey = privOut.value;
do_check_true(!!pubKey);
do_check_true(!!privKey);
do_check_eq(pubKey.length, 392);
do_check_eq(privKey.length, 1644);
// do some key wrapping
var wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
do_check_eq(wrappedKey.length, 344);
var unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
"my passphrase", salt, iv);
do_check_eq(unwrappedKey.length, 44);
// The acid test... Is our unwrapped key the same thing we started with?
do_check_eq(unwrappedKey, symKey);
// Tests with a 1024 bit key
cryptoSvc.keypairBits = 1024;
do_check_eq(cryptoSvc.keypairBits, 1024)
cryptoSvc.generateKeypair("my passphrase", salt, iv, pubOut, privOut);
var pubKey = pubOut.value;
var privKey = privOut.value;
do_check_true(!!pubKey);
do_check_true(!!privKey);
do_check_eq(pubKey.length, 216);
do_check_eq(privKey.length, 856);
// do some key wrapping
wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
do_check_eq(wrappedKey.length, 172);
unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
"my passphrase", salt, iv);
do_check_eq(unwrappedKey.length, 44);
// The acid test... Is our unwrapped key the same thing we started with?
do_check_eq(unwrappedKey, symKey);
}

View File

@ -0,0 +1,65 @@
function run_test() {
var cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"].
getService(Ci.IWeaveCrypto);
// 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(16);
do_check_eq(salt.length, 24);
salt = cryptoSvc.generateRandomBytes(1024);
do_check_eq(salt.length, 1368);
// 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 = Ci.IWeaveCrypto.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);
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
keydata = cryptoSvc.generateRandomKey();
do_check_eq(keydata.length, 32);
keydata2 = cryptoSvc.generateRandomKey();
do_check_neq(keydata, keydata2); // sanity check for randomness
iv = cryptoSvc.generateRandomIV();
do_check_eq(iv.length, 24);
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
keydata = cryptoSvc.generateRandomKey();
do_check_eq(keydata.length, 24);
keydata2 = cryptoSvc.generateRandomKey();
do_check_neq(keydata, keydata2); // sanity check for randomness
iv = cryptoSvc.generateRandomIV();
do_check_eq(iv.length, 24);
}

View File

@ -35,7 +35,7 @@ let __fakeLogins = [
// ----------------------------------------
function run_test() {
ID.set('Engine:PBE:default',
ID.set('WeaveID',
new Identity('Mozilla Services Encryption Passphrase', 'foo'));
// The JS module we're testing, with all members exposed.

View File

@ -1,24 +0,0 @@
function run_test() {
// initialize nss
let ch = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
let pbe = Cc["@labs.mozilla.com/Weave/Crypto;1"].getService(Ci.IWeaveCrypto);
pbe.algorithm = pbe.DES_EDE3_CBC;
let cipherTxt = pbe.encrypt("passphrase", "my very secret message!");
do_check_true(cipherTxt != "my very secret message!");
let clearTxt = pbe.decrypt("passphrase", cipherTxt);
do_check_true(clearTxt == "my very secret message!");
// The following check with wrong password must cause decryption to fail
// because of used padding-schema cipher, RFC 3852 Section 6.3
let failure = false;
try {
pbe.decrypt("wrongpassphrase", cipherTxt);
} catch (e) {
failure = true;
}
do_check_true(failure);
}

View File

@ -21,18 +21,6 @@ let __fakePasswords = {
'Mozilla Services Encryption Passphrase': {foo: "passphrase"}
};
Crypto.__proto__ = {
RSAkeydecrypt: function fake_RSAkeydecrypt(identity) {
let self = yield;
if (identity.password == "passphrase" &&
identity.privkey == "fake private key")
self.done("fake public key");
else
throw new Error("Unexpected identity information.");
}
};
let Service = loadInSandbox("resource://weave/service.js");
function TestService() {