Merged
authorjonathandicarlo@jonathan-dicarlos-macbook-pro.local
Tue, 24 Jun 2008 13:41:12 -0700
changeset 44719 e76f64ff9d2a557654230c876e5d29fefd8b5135
parent 44718 964e6ace258cb09a1f8db9dc385d37817cb23e57 (current diff)
parent 44712 144c3ff4a5d1eaf40cd4e2bf68a049f8809de090 (diff)
child 44720 bd095442e55a206a6f0b5209b68e4726b1fe3880
push id14033
push useredward.lee@engineering.uiuc.edu
push dateWed, 23 Jun 2010 22:21:35 +0000
treeherderautoland@227db4ad8cdf [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
Merged
services/sync/modules/engines.js
services/sync/modules/engines/bookmarks.js
services/sync/modules/service.js
services/sync/tests/unit/test_passwords.log.expected
services/sync/tests/unit/test_pbe.js
--- a/services/crypto/WeaveCrypto.cpp
+++ b/services/crypto/WeaveCrypto.cpp
@@ -38,16 +38,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "WeaveCrypto.h"
 
 #include "nsStringAPI.h"
 #include "nsAutoPtr.h"
 #include "plbase64.h"
+#include "prmem.h"
 #include "secerr.h"
 
 #include "pk11func.h"
 #include "keyhi.h"
 #include "nss.h"
 
 
 NS_IMPL_ISUPPORTS1(WeaveCrypto, IWeaveCrypto)
@@ -61,40 +62,60 @@ WeaveCrypto::WeaveCrypto() :
 WeaveCrypto::~WeaveCrypto()
 {
 }
 
 /*
  * Base 64 encoding and decoding...
  */
 
-void
+nsresult
 WeaveCrypto::EncodeBase64(const char *aData, PRUint32 aLength, nsACString& retval)
 {
+  // Empty input? Nothing to do.
+  if (!aLength) {
+    retval.Assign(EmptyCString());
+    return NS_OK;
+  }
+
   PRUint32 encodedLength = (aLength + 2) / 3 * 4;
-  char encoded[encodedLength];
+  char *encoded = (char *)PR_Malloc(encodedLength);
+  if (!encoded)
+    return NS_ERROR_OUT_OF_MEMORY;
 
   PL_Base64Encode(aData, aLength, encoded);
 
   retval.Assign(encoded, encodedLength);
+
+  PR_Free(encoded);
+  return NS_OK;
 }
 
-void
+nsresult
 WeaveCrypto::EncodeBase64(const nsACString& binary, nsACString& retval)
 {
   PromiseFlatCString fBinary(binary);
-  EncodeBase64(fBinary.get(), fBinary.Length(), retval);
+  return EncodeBase64(fBinary.get(), fBinary.Length(), retval);
 }
 
 nsresult 
 WeaveCrypto::DecodeBase64(const nsACString& base64,
                           char *decoded, PRUint32 *decodedSize)
 {
   PromiseFlatCString fBase64(base64);
 
+  if (fBase64.Length() == 0) {
+    *decodedSize = 0;
+    return NS_OK;
+  }
+
+  // We expect at least 4 bytes of input
+  if (fBase64.Length() < 4)
+    return NS_ERROR_FAILURE;
+
   PRUint32 size = (fBase64.Length() * 3) / 4;
   // Adjust for padding.
   if (*(fBase64.get() + fBase64.Length() - 1) == '=')
     size--;
   if (*(fBase64.get() + fBase64.Length() - 2) == '=')
     size--;
 
   // Just to be paranoid about buffer overflow.
@@ -106,23 +127,30 @@ WeaveCrypto::DecodeBase64(const nsACStri
     return NS_ERROR_ILLEGAL_VALUE;
 
   return NS_OK;
 }
 
 nsresult 
 WeaveCrypto::DecodeBase64(const nsACString& base64, nsACString& retval)
 {
-  char decoded[base64.Length()];
   PRUint32 decodedLength = base64.Length();
+  char *decoded = (char *)PR_Malloc(decodedLength);
+  if (!decoded)
+    return NS_ERROR_OUT_OF_MEMORY;
 
   nsresult rv = DecodeBase64(base64, decoded, &decodedLength);
-  NS_ENSURE_SUCCESS(rv, rv);  
+  if (NS_FAILED(rv)) {
+    PR_Free(decoded);
+    return rv;
+  }
 
   retval.Assign(decoded, decodedLength);
+
+  PR_Free(decoded);
   return NS_OK;
 }
 
 
 //////////////////////////////////////////////////////////////////////////////
 
 NS_IMETHODIMP
 WeaveCrypto::GetAlgorithm(PRUint32 *aAlgorithm)
@@ -157,64 +185,79 @@ WeaveCrypto::SetKeypairBits(PRUint32 aBi
  * Encrypt
  */
 NS_IMETHODIMP
 WeaveCrypto::Encrypt(const nsACString& aClearText,
                      const nsACString& aSymmetricKey,
                      const nsACString& aIV,
                      nsACString& aCipherText)
 {
-  nsresult rv;
+  nsresult rv = NS_OK;
 
   // When using CBC padding, the output is 1 block larger than the input.
   CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(mAlgorithm);
   PRUint32 blockSize = PK11_GetBlockSize(mech, nsnull);
 
-  char outputBuffer[aClearText.Length() + blockSize];
-  PRUint32 outputBufferSize = sizeof(outputBuffer);
+  PRUint32 outputBufferSize = aClearText.Length() + blockSize;
+  char *outputBuffer = (char *)PR_Malloc(outputBufferSize);
+  if (!outputBuffer)
+    return NS_ERROR_OUT_OF_MEMORY;
+
   PromiseFlatCString input(aClearText);
 
   rv = CommonCrypt(input.get(), input.Length(),
                    outputBuffer, &outputBufferSize,
                    aSymmetricKey, aIV, CKA_ENCRYPT);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_FAILED(rv))
+    goto encrypt_done;
 
-  EncodeBase64(outputBuffer, outputBufferSize, aCipherText);
+  rv = EncodeBase64(outputBuffer, outputBufferSize, aCipherText);
+  if (NS_FAILED(rv))
+    goto encrypt_done;
 
-  return NS_OK;
+encrypt_done:
+  PR_Free(outputBuffer);
+  return rv;
 }
 
 
 /*
  * Decrypt
  */
 NS_IMETHODIMP
 WeaveCrypto::Decrypt(const nsACString& aCipherText,
                      const nsACString& aSymmetricKey,
                      const nsACString& aIV,
                      nsACString& aClearText)
 {
-  nsresult rv;
+  nsresult rv = NS_OK;
 
-  char inputBuffer[aCipherText.Length()];
-  PRUint32 inputBufferSize = sizeof(inputBuffer);
-  char outputBuffer[aCipherText.Length()];
-  PRUint32 outputBufferSize = sizeof(outputBuffer);
+  PRUint32 inputBufferSize  = aCipherText.Length();
+  PRUint32 outputBufferSize = aCipherText.Length();
+  char *outputBuffer = (char *)PR_Malloc(outputBufferSize);
+  char *inputBuffer  = (char *)PR_Malloc(inputBufferSize);
+  if (!inputBuffer || !outputBuffer)
+    return NS_ERROR_OUT_OF_MEMORY;
 
   rv = DecodeBase64(aCipherText, inputBuffer, &inputBufferSize);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_FAILED(rv))
+    goto decrypt_done;
 
   rv = CommonCrypt(inputBuffer, inputBufferSize,
                    outputBuffer, &outputBufferSize,
                    aSymmetricKey, aIV, CKA_DECRYPT);
-  NS_ENSURE_SUCCESS(rv, rv);
+  if (NS_FAILED(rv))
+    goto decrypt_done;
 
   aClearText.Assign(outputBuffer, outputBufferSize);
 
-  return NS_OK;
+decrypt_done:
+  PR_Free(outputBuffer);
+  PR_Free(inputBuffer);
+  return rv;
 }
 
 
 /*
  * CommonCrypt
  */
 nsresult
 WeaveCrypto::CommonCrypt(const char *input, PRUint32 inputSize,
@@ -522,17 +565,18 @@ WeaveCrypto::WrapPrivateKey(SECKEYPrivat
   SECITEM_FreeItem(ivParam, PR_TRUE);
   PK11_FreeSymKey(pbeKey);
 
   if (s != SECSuccess) {
     NS_WARNING("PK11_WrapPrivKey failed");
     return(NS_ERROR_FAILURE);
   }
     
-  EncodeBase64((char *)wrappedKey.data, wrappedKey.len, aWrappedPrivateKey);
+  rv = EncodeBase64((char *)wrappedKey.data, wrappedKey.len, aWrappedPrivateKey);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 
 /*
  * EncodePublicKey
  *
@@ -542,17 +586,18 @@ nsresult
 WeaveCrypto::EncodePublicKey(SECKEYPublicKey *aPublicKey,
                              nsACString& aEncodedPublicKey)
 {
   //SECItem *derKey = PK11_DEREncodePublicKey(aPublicKey);
   SECItem *derKey = SECKEY_EncodeDERSubjectPublicKeyInfo(aPublicKey);
   if (!derKey)
     return NS_ERROR_FAILURE;
     
-  EncodeBase64((char *)derKey->data, derKey->len, aEncodedPublicKey);
+  nsresult rv = EncodeBase64((char *)derKey->data, derKey->len, aEncodedPublicKey);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // XXX destroy derKey?
 
   return NS_OK;
 }
 
 
 /*
@@ -563,17 +608,18 @@ WeaveCrypto::GenerateRandomBytes(PRUint3
                                  nsACString& aEncodedBytes)
 {
   nsresult rv;
   char random[aByteCount];
 
   rv = PK11_GenerateRandom((unsigned char *)random, aByteCount);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  EncodeBase64(random, aByteCount, aEncodedBytes);
+  rv = EncodeBase64(random, aByteCount, aEncodedBytes);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 
 /*
  * GenerateRandomIV
  */
@@ -585,17 +631,18 @@ WeaveCrypto::GenerateRandomIV(nsACString
   CK_MECHANISM_TYPE mech = PK11_AlgtagToMechanism(mAlgorithm);
   PRUint32 size = PK11_GetIVLength(mech);
 
   char random[size];
  
   rv = PK11_GenerateRandom((unsigned char *)random, size);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  EncodeBase64(random, size, aEncodedBytes);
+  rv = EncodeBase64(random, size, aEncodedBytes);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 
 /*
  * GenerateRandomKey
  *
@@ -655,17 +702,21 @@ WeaveCrypto::GenerateRandomKey(nsACStrin
 
   keydata = PK11_GetKeyData(randKey);
   if (!keydata) {
     NS_WARNING("PK11_GetKeyData failed");
     rv = NS_ERROR_FAILURE;
     goto keygen_done;
   }
 
-  EncodeBase64((char *)keydata->data, keydata->len, aEncodedKey);
+  rv = EncodeBase64((char *)keydata->data, keydata->len, aEncodedKey);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("EncodeBase64 failed");
+    goto keygen_done;
+  }
 
 keygen_done:
   // XXX does keydata need freed?
   if (randKey)
     PK11_FreeSymKey(randKey);
   if (slot)
     PK11_FreeSlot(slot);
 
@@ -767,17 +818,21 @@ WeaveCrypto::WrapSymmetricKey(const nsAC
     NS_WARNING("PK11_PubWrapSymKey failed");
     rv = NS_ERROR_FAILURE;
     goto wrap_done;
   }
 
 
   // Step 5. Base64 encode the wrapped key, cleanup, and return to caller.
 
-  EncodeBase64((char *)wrappedKey.data, wrappedKey.len, aWrappedKey);
+  rv = EncodeBase64((char *)wrappedKey.data, wrappedKey.len, aWrappedKey);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("EncodeBase64 failed");
+    goto wrap_done;
+  }
 
 wrap_done:
   if (pubKey)
     SECKEY_DestroyPublicKey(pubKey);
   if (pubKeyInfo)
     SECKEY_DestroySubjectPublicKeyInfo(pubKeyInfo);
   if (symKey)
     PK11_FreeSymKey(symKey);
@@ -911,17 +966,21 @@ WeaveCrypto::UnwrapSymmetricKey(const ns
   // XXX need to free this?
   symKeyData = PK11_GetKeyData(symKey);
   if (!symKeyData) {
     NS_WARNING("PK11_GetKeyData failed");
     rv = NS_ERROR_FAILURE;
     goto unwrap_done;
   }
 
-  EncodeBase64((char *)symKeyData->data, symKeyData->len, aSymmetricKey);
+  rv = EncodeBase64((char *)symKeyData->data, symKeyData->len, aSymmetricKey);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("EncodeBase64 failed");
+    goto unwrap_done;
+  }
 
 unwrap_done:
   if (privKey)
     SECKEY_DestroyPrivateKey(privKey);
   if (symKey)
     PK11_FreeSymKey(symKey);
   if (pbeKey)
     PK11_FreeSymKey(pbeKey);
--- a/services/crypto/WeaveCrypto.h
+++ b/services/crypto/WeaveCrypto.h
@@ -59,18 +59,18 @@ public:
 private:
   ~WeaveCrypto();
 
   SECOidTag mAlgorithm;
   PRUint32  mKeypairBits;
 
   nsresult DecodeBase64(const nsACString& base64, nsACString& retval);
   nsresult DecodeBase64(const nsACString& base64, char *aData, PRUint32 *aLength);
-  void EncodeBase64(const nsACString& binary, nsACString& retval);
-  void EncodeBase64(const char *aData, PRUint32 aLength, nsACString& retval);
+  nsresult EncodeBase64(const nsACString& binary, nsACString& retval);
+  nsresult EncodeBase64(const char *aData, PRUint32 aLength, nsACString& retval);
 
   nsresult CommonCrypt(const char *input, PRUint32 inputSize,
                        char *output, PRUint32 *outputSize,
                        const nsACString& aSymmetricKey,
                        const nsACString& aIV,
                        CK_ATTRIBUTE_TYPE aOperation);
 
 
new file mode 100644
--- /dev/null
+++ b/services/sync/locales/en-US/status.dtd
@@ -0,0 +1,2 @@
+<!ENTITY dialog.title         "Sync Status - Mozilla Weave">
+<!ENTITY status.idle          "Weave sync on quit">
new file mode 100644
--- /dev/null
+++ b/services/sync/locales/en-US/status.properties
@@ -0,0 +1,3 @@
+status.active   = Syncing with Weave...
+status.success  = Sync succeeded.
+status.error    = Sync failed.
--- a/services/sync/locales/en-US/wizard.dtd
+++ b/services/sync/locales/en-US/wizard.dtd
@@ -1,61 +1,88 @@
+<!ENTITY serverError1.description         "Server Error:  ">
+<!ENTITY serverError2.description         ",  ">
+
+
 <!ENTITY wizard.title                     "Weave Setup">
 
-<!ENTITY welcome.title                    "Welcome">
-<!ENTITY welcome.description              "Explain what Weave is...">
-<!ENTITY curUser-title.label	          "Sign in">
-<!ENTITY curUser.description       	  "Synchronize data here with your existing Weave data, or recover data from an existing account.">
-<!ENTITY newUser-title.label	          "Create a new account.">
-<!ENTITY newUser.description       	  "Create a new Weave account and choose the data you would like Weave to store.">
+<!ENTITY intro.title                      "Welcome to Weave">
+<!ENTITY intro.description                "[description of Weave]">
+<!ENTITY intro-more.label                 "Learn more">
+<!ENTITY intro-more.link                  "http://labs.mozilla.com">
 
-<!ENTITY verify.title                     "Account Verification">
-<!ENTITY msg.label	                  "Please enter all fields.">
+<!ENTITY welcome.title			  "Installation Type">
+<!ENTITY curUser-title.label              "Sign In">
+<!ENTITY curUser.description              "Sign into your existing account to set up Weave on this computer.">
+<!ENTITY newUser-title.label              "Get Started with Weave">
+<!ENTITY newUser.description              "Create a new account.">
+
+<!ENTITY verify.title                     "Account Verification (Step 1 of 3)">
 <!ENTITY username.label                   "User name:">
-<!ENTITY password.label            	  "Password:">
-<!ENTITY passphrase.label		  "Passphrase:">
-<!ENTITY reminder.label		          "Forget your password? ">
-<!ENTITY verify.label			  "Verifying username and password...">
-
+<!ENTITY password.label                   "Password:">
+<!ENTITY passphrase.label                 "Passphrase:">
+<!ENTITY reminder.label                   "Forgot your password? ">
+<!ENTITY unverified.label                 "Unverified">
 <!ENTITY recovery.link                    "https://sm-labs01.mozilla.org:81/client/forgot.php">
 
-<!ENTITY create.title			  "Create Account">
-<!ENTITY username.description		  "Enter a unique username."> 
-<!ENTITY reenterPassword.label		  "Re-enter password:">
-<!ENTITY passphrase.description		  "Explain what a passphrase is..."> 
-<!ENTITY reenterPassphrase.label	  "Re-enter passphrase:">
-<!ENTITY moreInfo.label			  "More information on choosing a phrase.">
-<!ENTITY email.description		  "Explain email address...">
-<!ENTITY email.label			  "Email address:">
-<!ENTITY captcha.label		  	  "So we can verify you are a real person...">
-<!ENTITY captchaDirections.label          "Type the two words, or ">
-<!ENTITY reloadCaptcha.text               "try another image">
-<!ENTITY terms.label			  "Terms, license (?)">
+<!ENTITY create.title                     "Create Account (Step 1 of 5)">
+<!ENTITY createUsername.label             "Desired login name:">
+<!ENTITY createUsernameHint.label         "Examples: Maria, Maria.Emerson">
+<!ENTITY createPassword.label             "Choose a password:">
+<!ENTITY createPasswordHint.label         "Minimum of 5 characters in length.">
+<!ENTITY createReenterPassword.label      "Re-enter password:">
+<!ENTITY createEmail.label                "Current email address:">
+<!ENTITY createEmailHint.label            "e.g. yourname@domain.com">
+
+<!ENTITY create2.title                    "Create Account (Step 2 of 5)">
+
+<!ENTITY passphrase.description           "You must also now choose an encryption passphrase that is different from your password. This will be used to protect your data on the server."> 
+<!ENTITY passphrase-more.label            "Learn More">
+<!ENTITY passphrase-more.link             "http://labs.mozilla.com">
 
-<!ENTITY data.title			  "Data">
-<!ENTITY data.description		  "Choose the data that you would like Weave to store for you.">
-<!ENTITY bookmarks.label	          "Bookmarks">
-<!ENTITY history.label	          	  "Browsing History">
-<!ENTITY cookies.label	          	  "Cookies">
-<!ENTITY passwords.label	          "Saved Passwords">
-<!ENTITY tabs.label	          	  "Tabs">
-<!ENTITY formdata.label	          	  "Saved Form Data">
-<!ENTITY data.note		  	  "Weave will synchronize this data every 30 seconds.">
+<!ENTITY reenterPassphrase.label          "Re-enter passphrase:">
+<!ENTITY moreInfo.label                   "More information on choosing a phrase.">
+
+<!ENTITY create3.title                    "Create Account (Step 3 of 5)">
+
+<!ENTITY instanceName.description         "Name this device... explain why... ">
+<!ENTITY instanceName.label               "Device name:">
+<!ENTITY examples.label                   "Examples: &quot;Home computer&quot;, &quot;Mobile phone&quot;, &quot;Work laptop&quot;"> 
+
+<!ENTITY data.description                 "Choose the data that you would like Weave to store for you.">
+<!ENTITY bookmarks.label                  "Bookmarks">
+<!ENTITY history.label                    "Browsing History">
+<!ENTITY cookies.label                    "Cookies">
+<!ENTITY passwords.label                  "Saved Passwords">
+<!ENTITY tabs.label                       "Tabs">
+<!ENTITY formdata.label                   "Saved Form Data">
+<!ENTITY data.note                        "Weave will synchronize this data every 30 seconds.">
 <!ENTITY removeBookmarks.label            "Remove default bookmarks on this device before synchronizing with Weave.">
 
-<!ENTITY final.title			  "Finish">
-<!ENTITY final.description		  "Installing Weave...">
+<!ENTITY captcha.label                    "Type the characters you see in the image below:">
+<!ENTITY captchaDirections.label          "">
+<!ENTITY reloadCaptcha.text               "Reload">
+<!ENTITY captchaHint.label                "Letters are not case-sensitive.">
+
+<!ENTITY terms.label                      "Terms of Service">
+<!ENTITY acceptTerms.label                "I have read and accept the Terms of Service.">
 
-<!ENTITY initialLogin.label               "Signing you in...">
-<!ENTITY initialPrefs.label               "Setting your preferences...">
-<!ENTITY initialReset.label               "Clearing your default bookmarks...">
-<!ENTITY initialSync.label		  "Synchronizing your data...">
+<!ENTITY final.description                "Installing Weave...">
+
+<!ENTITY initialLogin.label               "Signing you in.">
+<!ENTITY initialPrefs.label               "Setting your preferences.">
+<!ENTITY initialReset.label               "Clearing your default bookmarks.">
+<!ENTITY initialSync.label                "Synchronizing your data.">
 <!ENTITY finalStep1Finished.label         "Signed in.">
-<!ENTITY finalStep2.label		  "Synchronizing data...">
+<!ENTITY finalStep2.label                 "Synchronizing data...">
 <!ENTITY finalStep3Finished.label         "Data synchronized.">
 
+<!ENTITY userCheckFailed1.description     "Our server is having problems and we couldn't check that username. ">
+<!ENTITY userCheckFailed2.description     " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later.">
+
 <!ENTITY finished.description             "Good job. You installed Weave. You can change preferences in the Weave tab...">
-<!ENTITY initialLoginFailed1.description "Our server is having problems and we couldn't log you in. ">
-<!ENTITY initialLoginFailed2.description  " to try again, or click Done to exit the setup wizard and try again later.">
+<!ENTITY initialLoginFailed1.description  "Our server is having problems and we couldn't log you in. ">
+<!ENTITY initialLoginFailed2.description  " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later.">
 <!ENTITY initialSyncFailed1.description   "Our server is having problems and we couldn't synchronize your data. ">
-<!ENTITY initialSyncFailed2.description    " to try again, or click Done to exit the setup wizard and try again later."> 
+<!ENTITY initialSyncFailed2.description   " to try again, or click &quot;Done&quot; to exit the setup wizard and try again later."> 
 
 <!ENTITY clickHere.text                   "Click here">
+<!ENTITY tryAgain.text                    "Try again">
--- a/services/sync/locales/en-US/wizard.properties
+++ b/services/sync/locales/en-US/wizard.properties
@@ -1,58 +1,52 @@
-verifyStatusUnverified.label    = Status: Unverified
-verifyStatusVerifying.label     = Status: Verifying account...
-verifyStatusLoginVerified.label = Status: Account Verified
-verifyStatusLoginFailed.label   = Status: Authentication Failed
+serverError.label               = Server Error
+serverTimeoutError.label        = Server Error
+
 
-initStatusReadyToSync.label     = Status: Ready to Transfer Data
-initStatusSyncing.label         = Status: Transferring Data...
-initStatusSyncComplete.label    = Status: Transfer Complete
-initStatusSyncFailed.label      = Status: Transfer Failed
+createUsername-progress.label   = 
+createUsername-error.label      = %S is already taken. Please choose another.
+createUsername-success.label    = %S is available.
 
-invalidCredentials.alert        = You must provide a valid Weave user name and password to continue.
+email-progress.label            = 
+email-unavailable.label         = %S is already taken. Please choose another.
+email-invalid.label             = Please re-enter a valid email address.
+email-success.label             = %S is available.
 
-usernameTaken.label		= That username is already taken. Please choose another.
-usernameAvailable.label		= Available.
-
-loginFailed.label		= Please enter a valid username and password.
 
-requiredFields.label		= Please enter all required fields.
+passwordsUnmatched.label        = Passwords do not match
+passphrasesUnmatched.label      = Passphrases do not match.
+samePasswordAndPassphrase.label = Your password and passphrase must be different
 
-passwordsUnmatched.label	= Passwords don't match. Please re-enter.
-passphrasesUnmatched.label 	= Passphrases don't match. Please re-enter.
 
-noPassphrase.alert = You must enter a passphrase
-samePasswordAndPassphrase.label = Your password and passphrase must be different
-samePasswordAndPassphrase.alert = Your password and passphrase must be different
 
-invalidEmail.label		= Invalid email address.
-emailAlreadyExists.label	= Email address already exists. 
+missingCaptchaResponse.label    = Please enter the words in the Captcha box.
+incorrectCaptcha.label          = Incorrect Captcha response. Try again.
+
 
-emailTaken.label 		= Email address already exists.
-emailInvalid.label 		= Invalid email address.
-emailOk.label			= Email address is valid.
+verify-progress.label           = Verifying username and password
+verify-error.label              = Invalid username and password
+verify-success.label            = Username and password verified
 
-missingCaptchaResponse.label	= Please enter the words in the Captcha box.
-incorrectCaptcha.label		= Incorrect Captcha response. Try again.
-internalError.label		= Sorry! We had a problem. Please try again.
+
 
 initialLogin-progress.label     = Signing you in...
 initialLogin-done.label         = Sign-in successful. 
 initialLogin-error.label        = Problem signing in.
 
 initialPrefs-progress.label     = Setting your preferences...
 initialPrefs-done.label         = Preferences set. 
 
 initialReset-progress.label     = Clearing your default bookmarks...
 initialReset-done.label         = Default bookmarks cleared. 
 
 initialSync-progress.label      = Synchronizing your data...
 initialSync-done.label          = Sync successful.
 initialSync-error.label         = Problem syncing data. 
 
-initialLoginFailed.description1 = Our server is having problems and we couldn't log you in. 
-initialLoginFailed.description2 = to try again, or click "Done" to exit the setup wizard and try again later. 
+
+tryAgain.text                   = Try again now.
+clickHere.text                  = Click here
 
-initialSyncFailed.description1  = Our server is having problems and we couldn't synchronize your data. 
-initialSyncFailed.description2  = to try again, or click "Done" to exit the setup wizard and try again later. 
-
-tryAgain.text			= Click here
+data-verify.title               = Data (Step 2 of 3)
+data-create.title               = Data (Step 4 of 5)
+final-verify.title              = Finish (Step 3 of 3)
+final-create.title              = Finish (Step 5 of 5)
--- a/services/sync/modules/crypto.js
+++ b/services/sync/modules/crypto.js
@@ -14,16 +14,17 @@
  * The Original Code is Bookmarks Sync.
  *
  * The Initial Developer of the Original Code is Mozilla.
  * Portions created by the Initial Developer are Copyright (C) 2007
  * the Initial Developer. All Rights Reserved.
  *
  * 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
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -60,16 +61,25 @@ CryptoSvc.prototype = {
   __os: null,
   get _os() {
     if (!this.__os)
       this.__os = Cc["@mozilla.org/observer-service;1"]
         .getService(Ci.nsIObserverService);
     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");
   },
   set defaultAlgorithm(value) {
     if (value != Utils.prefs.getCharPref("encryption"))
       Utils.prefs.setCharPref("encryption", value);
   },
 
@@ -77,281 +87,16 @@ CryptoSvc.prototype = {
     this._log = Log4Moz.Service.getLogger("Service." + this._logName);
     this._log.level =
       Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.crypto")];
     let branch = Cc["@mozilla.org/preferences-service;1"]
       .getService(Ci.nsIPrefBranch2);
     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
 
   observe: function Sync_observe(subject, topic, data) {
     switch (topic) {
     case "extensions.weave.encryption": {
       let branch = Cc["@mozilla.org/preferences-service;1"]
@@ -376,104 +121,112 @@ CryptoSvc.prototype = {
     } break;
     default:
       this._log.warn("Unknown encryption preference changed - ignoring");
     }
   },
 
   // Crypto
 
-  PBEencrypt: function Crypto_PBEencrypt(data, identity, algorithm) {
+  encryptData: function Crypto_encryptData(data, identity) {
     let self = yield;
     let ret;
 
-    if (!algorithm)
-      algorithm = this.defaultAlgorithm;
-
-    if (algorithm != "none")
-      this._log.debug("Encrypting data");
-
-    switch (algorithm) {
-    case "none":
-      ret = data;
-      break;
+    this._log.trace("encrypt called. [id=" + identity.realm + "]");
 
-    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;
+    if ("none" == this.defaultAlgorithm) {
+      this._log.debug("NOT encrypting data");
+      ret = data;
+    } 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);
+  },
+
+  decryptData: function Crypto_decryptData(data, identity) {
+    let self = yield;
+    let ret;
+
+    this._log.trace("decrypt called. [id=" + identity.realm + "]");
+
+    if ("none" == this.defaultAlgorithm) {
+      this._log.debug("NOT decrypting data");
+      ret = data;
+    } else {
+      let symkey = identity.bulkKey;
+      let iv     = identity.bulkIV;
+      ret = this._crypto.decrypt(data, symkey, iv);
+    }
 
     self.done(ret);
   },
 
-  PBEdecrypt: function Crypto_PBEdecrypt(data, identity, algorithm) {
+  /*
+   * 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;
-
-    if (!algorithm)
-      algorithm = this.defaultAlgorithm;
 
-    if (algorithm != "none")
-      this._log.debug("Decrypting data");
+    this._log.trace("randomKeyGen called. [id=" + identity.realm + "]");
 
-    switch (algorithm) {
-    case "none":
-      ret = data;
-      break;
+    let symkey = this._crypto.generateRandomKey();
+    let iv     = this._crypto.generateRandomIV();
+
+    identity.bulkKey = symkey;
+    identity.bulkIV  = iv;
+  },
 
-    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;
+  /*
+   * 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;
+
+    this._log.trace("RSAkeygen called. [id=" + identity.realm + "]");
+    let privOut = {};
+    let pubOut  = {};
 
-    default:
-      throw "Unknown encryption algorithm: " + algorithm;
-    }
+    // 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);
 
-    if (algorithm != "none")
-      this._log.debug("Done decrypting data");
+    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);
   },
 
-  PBEkeygen: function Crypto_PBEkeygen() {
+  unwrapKey: function Crypto_unwrapKey(data, identity) {
     let self = yield;
-    let ret = this._opensslRand();
-    self.done(ret);
-  },
 
-  RSAkeygen: function Crypto_RSAkeygen(identity) {
-    let self = yield;
-    let ret = this._opensslRSAKeyGen(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);
   },
-
-  RSAencrypt: function Crypto_RSAencrypt(data, identity) {
-    let self = yield;
-    let ret = this._opensslRSAencrypt(data, identity);
-    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);
-  }
 };
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -156,34 +156,23 @@ Engine.prototype = {
     if (!this.__snapshot)
       this.__snapshot = new SnapshotStore(this.name);
     return this.__snapshot;
   },
   set _snapshot(value) {
     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;
   },
 
   _init: function Engine__init() {
     this._log = Log4Moz.Service.getLogger("Service." + this.logName);
     this._log.level =
@@ -266,20 +255,18 @@ Engine.prototype = {
       this._snapshot.data = {};
       this._snapshot.version = -1;
       this._snapshot.GUID = this._remote.status.data.GUID;
     }
 
     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 = {};
     let serverSnap = yield this._remote.wrap(self.cb, this._snapshot);
     server.snapshot = serverSnap.data;
     this._core.detectUpdates(self.cb, this._snapshot.data, server.snapshot);
     server.updates = yield;
 
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -364,38 +364,50 @@ BookmarksEngine.prototype = {
        bookmark folder, so we can easily get back to it later. */
     this._annoSvc.setItemAnnotation(folder.node.itemId,
                                     SERVER_PATH_ANNO,
                                     serverPath,
                                     0,
                                     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:
     let sharingApi = new Sharing.Api( DAV );
     sharingApi.shareWithUsers( serverPath, [username], self.cb );
     let result = yield;
 
     // return the server path:
@@ -415,26 +427,37 @@ BookmarksEngine.prototype = {
     let serverPath = this._annoSvc.getItemAnnotation(folderNode,
                                                      SERVER_PATH_ANNO);
     // TODO the above can throw an exception if the expected anotation isn't
     // there.
     // From that directory, get the keyring file, and from it, the symmetric
     // 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
        in my own or that of the person I share with? */
 
     // 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;
     self.done();
   },
 
   _stopOutgoingShare: function BmkEngine__stopOutgoingShare(folderNode) {
     /* Stops sharing the specified folder.  Deletes its data from the
@@ -558,24 +581,28 @@ BookmarksEngine.prototype = {
     // The folder has an annotation specifying the server path to the
     // directory:
     let serverPath = this._annoSvc.getItemAnnotation(mountData.node,
                                                      SERVER_PATH_ANNO);
     // From that directory, get the keyring file, and from it, the symmetric
     // 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...)
 
     // prune tree / get what we want
     for (let guid in json) {
       if (json[guid].type != "bookmark")
         delete json[guid];
       else
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -82,37 +82,35 @@ IDManager.prototype = {
  * Identity
  * These objects hold a realm, username, and password
  * They can hold a password in memory, but will try to fetch it from
  * the password manager if it's not set.
  * FIXME: need to rethink this stuff as part of a bigger identity mgmt framework
  */
 
 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; },
-
-  get username() { return this._username; },
-  set username(value) { this._username = value; },
-
-  get userHash() { return Utils.sha1(this.username); },
+  realm   : null,
 
-  _privkey: null,
-  get privkey() { return this._privkey; },
-  set privkey(value) { this._privkey = value; },
+  // Only the "WeaveCryptoID" realm uses these:
+  privkey        : null,
+  pubkey         : null,
+  passphraseSalt : null,
+  privkeyWrapIV  : null,
 
-  // FIXME: get this from the privkey using crypto.js?
-  _pubkey: null,
-  get pubkey() { return this._pubkey; },
-  set pubkey(value) { this._pubkey = value; },
+  // Only the per-engine identity uses these:
+  bulkKey : null,
+  bulkIV  : null,
+
+  username : null,
+  get userHash() { return Utils.sha1(this.username); },
 
   _password: null,
   get password() {
     if (!this._password)
       return Utils.findPassword(this.realm, this.username);
     return this._password;
   },
   set password(value) {
--- a/services/sync/modules/remote.js
+++ b/services/sync/modules/remote.js
@@ -277,28 +277,27 @@ function CryptoFilter(remoteStore, algPr
   this._log = Log4Moz.Service.getLogger("Service.CryptoFilter");
 }
 CryptoFilter.prototype = {
   __proto__: new ResourceFilter(),
 
   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);
   },
 
   afterGET: function CryptoFilter_afterGET(data) {
     let self = yield;
     this._log.debug("Decrypting data");
     if (!this._remote.status.data)
       throw "Remote status must be initialized before crypto filter can be used"
-    let alg = this._remote.status.data[this._algProp];
-    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);
   }
 };
 
 function Status(remoteStore) {
   this._init(remoteStore);
 }
@@ -316,43 +315,46 @@ function Keychain(remoteStore) {
 }
 Keychain.prototype = {
   __proto__: new Resource(),
   _init: function Keychain__init(remoteStore) {
     this._remote = remoteStore;
     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()
 };
 
 function RemoteStore(engine) {
   this._engine = engine;
   this._log = Log4Moz.Service.getLogger("Service.RemoteStore");
 }
 RemoteStore.prototype = {
   get serverPrefix() this._engine.serverPrefix,
   get engineId() this._engine.engineId,
-  get pbeId() this._engine.pbeId,
 
   get status() {
     let status = new Status(this);
     this.__defineGetter__("status", function() status);
     return status;
   },
 
   get keys() {
@@ -416,33 +418,30 @@ RemoteStore.prototype = {
     this.keys.data = null;
     this._snapshot.data = null;
     this._deltas.data = null;
   },
 
   // 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);
     yield this._deltas.put(self.cb, []);
 
     let c = 0;
     for (GUID in snapshot.data)
       c++;
@@ -501,16 +500,17 @@ RemoteStore.prototype = {
   // Gets the latest server snapshot by downloading only the necessary
   // deltas from the given snapshot (but may fall back to a full download)
   _getLatestFromSnap: function RStore__getLatestFromSnap(lastSyncSnap) {
     let self = yield;
     let deltas, snap = new SnapshotStore();
     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;
 
     } else if (lastSyncSnap.version >= this.status.data.snapVersion &&
                lastSyncSnap.version < this.status.data.maxVersion) {
       this._log.debug("Using last sync snapshot as starting point for server snapshot");
       snap.data = Utils.deepCopy(lastSyncSnap.data);
       this._log.info("Downloading server deltas");
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -127,16 +127,17 @@ function WeaveSvc(engines) {
     ];
 
   // Register engines
   for (let i = 0; i < engines.length; i++)
     Engines.register(engines[i]);
 
   // Other misc startup
   Utils.prefs.addObserver("", this, false);
+  this._os.addObserver(this, "quit-application", true);
 
   if (!this.enabled) {
     this._log.info("Weave Sync disabled");
     return;
   }
 }
 WeaveSvc.prototype = {
 
@@ -157,16 +158,24 @@ WeaveSvc.prototype = {
   __dirSvc: null,
   get _dirSvc() {
     if (!this.__dirSvc)
       this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
         getService(Ci.nsIProperties);
     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,
 
   get username() {
     return Utils.prefs.getCharPref("username");
   },
   set username(value) {
     if (value)
@@ -248,17 +257,17 @@ WeaveSvc.prototype = {
       this._scheduleTimer = null;
     }
     this._log.info("Weave scheduler disabled");
   },
 
   _onSchedule: function WeaveSync__onSchedule() {
     if (this.enabled) {
       this._log.info("Running scheduled sync");
-      this._lock(this._notify("sync", this._syncAsNeeded)).async(this);
+      this._notify("sync", this._lock(this._syncAsNeeded)).async(this);
     }
   },
 
   _initLogs: function WeaveSync__initLogs() {
     this._log = Log4Moz.Service.getLogger("Service.Main");
     this._log.level =
       Log4Moz.Level[Utils.prefs.getCharPref("log.logger.service.main")];
 
@@ -351,82 +360,146 @@ WeaveSvc.prototype = {
       let ret = yield;
       if (!ret)
         throw "Could not create user directory";
     }
     catch (e) { throw e; }
     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;
+
+    this._log.trace("Retrieving keypair from server");
 
-      if (keyResp.status != 404) {
-        let id = ID.get('WeaveCryptoID');
-        id.privkey = keyResp.responseText;
-        Crypto.RSAkeydecrypt.async(Crypto, self.cb, id);
-        id.pubkey = yield;
-      } else {
+    // 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() {
     let self = yield;
 
     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;
     if (!ret)
       throw "Could not create private key directory";
 
     DAV.MKCOL("public/", self.cb);
     ret = yield;
     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");
   },
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupports]),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsISupportsWeakReference]),
 
   // nsIObserver
 
   observe: function WeaveSync__observe(subject, topic, data) {
-    if (topic != "nsPref:changed")
+    switch (topic) {
+      case "nsPref:changed":
+        switch (data) {
+          case "enabled": // this works because this.schedule is 0 when disabled
+          case "schedule":
+            this._setSchedule(this.schedule);
+            break;
+        }
+        break;
+      case "quit-application":
+        this._onQuitApplication();
+        break;
+    }
+  },
+
+  _onQuitApplication: function WeaveSync__onQuitApplication() {
+    if (!this.enabled ||
+        !Utils.prefs.getBoolPref("syncOnQuit.enabled") ||
+        !this._loggedIn)
       return;
 
-    switch (data) {
-    case "enabled": // this works because this.schedule is 0 when disabled
-    case "schedule":
-      this._setSchedule(this.schedule);
-      break;
-    }
+    let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+             getService(Ci.nsIWindowWatcher);
+
+    // This window has to be modal to prevent the application from quitting
+    // until the sync finishes and the window closes.
+    let window = ww.openWindow(null,
+                               "chrome://weave/content/status.xul",
+                               "Weave:status",
+                               "chrome,centerscreen,modal",
+                               null);
   },
 
   // These are global (for all engines)
 
   login: function WeaveSync_login(onComplete, password, passphrase) {
     this._localLock(this._notify("login", this._login,
                                  password, passphrase)).async(this, onComplete);
   },
@@ -462,17 +535,17 @@ WeaveSvc.prototype = {
       if (!success)
         throw "Login failed";
     }
 
     this._log.info("Using server URL: " + DAV.baseURL + DAV.defaultPrefix);
 
     this._versionCheck.async(this, self.cb);
     yield;
-    this._keyCheck.async(this, self.cb);
+    this._getKeypair.async(this, self.cb);
     yield;
 
     this._loggedIn = true;
 
     this._setSchedule(this.schedule);
 
     self.done(true);
   },
@@ -498,17 +571,17 @@ WeaveSvc.prototype = {
   serverWipe: function WeaveSvc_serverWipe(onComplete) {
     let cb = function WeaveSvc_serverWipeCb() {
       let self = yield;
       this._serverWipe.async(this, self.cb);
       yield;
       this.logout();
       self.done();
     };
-    this._lock(this._notify("server-wipe", cb)).async(this, onComplete);
+    this._notify("server-wipe", this._lock(cb)).async(this, onComplete);
   },
   _serverWipe: function WeaveSvc__serverWipe() {
     let self = yield;
 
     DAV.listFiles.async(DAV, self.cb);
     let names = yield;
 
     for (let i = 0; i < names.length; i++) {
@@ -518,29 +591,29 @@ WeaveSvc.prototype = {
       let resp = yield;
       this._log.debug(resp.status);
     }
   },
 
   // These are per-engine
 
   sync: function WeaveSync_sync(onComplete) {
-    this._lock(this._notify("sync", this._sync)).async(this, onComplete);
+    this._notify("sync", this._lock(this._sync)).async(this, onComplete);
   },
 
   _sync: function WeaveSync__sync() {
     let self = yield;
 
     if (!this._loggedIn)
       throw "Can't sync: Not logged in";
 
     this._versionCheck.async(this, self.cb);
     yield;
 
-    this._keyCheck.async(this, self.cb);
+    this._getKeypair.async(this, self.cb);
     yield;
 
     let engines = Engines.getAll();
     for (let i = 0; i < engines.length; i++) {
       if (!engines[i].enabled)
         continue;
       this._notify(engines[i].name + "-engine:sync",
                    this._syncEngine, engines[i]).async(this, self.cb);
@@ -557,17 +630,17 @@ WeaveSvc.prototype = {
     let self = yield;
 
     if (!this._loggedIn)
       throw "Can't sync: Not logged in";
 
     this._versionCheck.async(this, self.cb);
     yield;
 
-    this._keyCheck.async(this, self.cb);
+    this._getKeypair.async(this, self.cb);
     yield;
 
     let engines = Engines.getAll();
     for each (let engine in engines) {
       if (!engine.enabled)
         continue;
 
       if (!(engine.name in this._syncThresholds))
@@ -616,18 +689,18 @@ WeaveSvc.prototype = {
     } catch(e) {
       this._log.error(Utils.exceptionStr(e));
       if (e.trace)
         this._log.trace(Utils.stackTrace(e.trace));
     }
   },
 
   resetServer: function WeaveSync_resetServer(onComplete) {
-    this._lock(this._notify("reset-server",
-                            this._resetServer)).async(this, onComplete);
+    this._notify("reset-server",
+                 this._lock(this._resetServer)).async(this, onComplete);
   },
   _resetServer: function WeaveSync__resetServer() {
     let self = yield;
 
     if (!this._loggedIn)
       throw "Can't reset server: Not logged in";
 
     let engines = Engines.getAll();
@@ -667,21 +740,20 @@ WeaveSvc.prototype = {
        Implementation, as well as the interpretation of what 'guid' means,
        is left up to the engine for the specific dataType. */
 
     let messageName = "share-" + dataType;
     /* so for instance, if dataType is "bookmarks" then a message
      "share-bookmarks" will be sent out to any observers who are listening
      for it.  As far as I know, there aren't currently any listeners for
      "share-bookmarks" but we'll send it out just in case. */
-    this._lock(this._notify(messageName,
-                            this._shareData,
-                            dataType,
-                            guid,
-                            username)).async(this, onComplete);
+    this._notify(messageName, this._lock(this._shareData,
+                                         dataType,
+                                         guid,
+                                         username)).async(this, onComplete);
   },
 
   _shareData: function WeaveSync__shareData(dataType,
 					    guid,
 					    username) {
     let self = yield;
     let ret;
     if (Engines.get(dataType).enabled) {
--- a/services/sync/modules/xmpp/transportLayer.js
+++ b/services/sync/modules/xmpp/transportLayer.js
@@ -251,16 +251,17 @@ HTTPPollingTransport.prototype = {
     if ( this._callbackObject != null ) {
       this._callbackObject.onTransportError( errorText );
     }
     this.disconnect();
   },
 
   _doPost: function( requestXml ) {
     var request = this._request;
+    request.mozBackgroundRequest = true;
     var callbackObj = this._callbackObject;
     var self = this;
     var contents = "";
 
     if ( this._useKey ) {
       this._advanceKey();
       contents = this._connectionId + ";" + this._key + "," + requestXml;
     } else {
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -8,16 +8,18 @@ pref("extensions.weave.lastsync", "0");
 
 pref("extensions.weave.ui.syncnow", true);
 pref("extensions.weave.ui.sharebookmarks", true);
 pref("extensions.weave.rememberpassword", true);
 pref("extensions.weave.autoconnect", true);
 pref("extensions.weave.enabled", true);
 pref("extensions.weave.schedule", 1);
 
+pref("extensions.weave.syncOnQuit.enabled", true);
+
 pref("extensions.weave.engine.bookmarks", true);
 pref("extensions.weave.engine.history", true);
 pref("extensions.weave.engine.cookies", true );
 pref("extensions.weave.engine.passwords", true );
 pref("extensions.weave.engine.forms", true );
 pref("extensions.weave.engine.tabs", true);
 
 pref("extensions.weave.log.appender.console", "Warn");
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/fake_login_manager.js
@@ -0,0 +1,38 @@
+Cu.import("resource://weave/util.js");
+
+// ----------------------------------------
+// Fake Sample Data
+// ----------------------------------------
+
+let fakeSampleLogins = [
+  // Fake nsILoginInfo object.
+  {hostname: "www.boogle.com",
+   formSubmitURL: "http://www.boogle.com/search",
+   httpRealm: "",
+   username: "",
+   password: "",
+   usernameField: "test_person",
+   passwordField: "test_password"}
+];
+
+// ----------------------------------------
+// Fake Login Manager
+// ----------------------------------------
+
+function FakeLoginManager(fakeLogins) {
+  this.fakeLogins = fakeLogins;
+
+  let self = this;
+
+  Utils.getLoginManager = function fake_getLoginManager() {
+    // Return a fake nsILoginManager object.
+    return {
+      getAllLogins: function() { return self.fakeLogins; },
+      addLogin: function(login) {
+        getTestLogger().info("nsILoginManager.addLogin() called " +
+                             "with hostname '" + login.hostname + "'.");
+        self.fakeLogins.push(login);
+      }
+    };
+  };
+}
--- a/services/sync/tests/unit/head_first.js
+++ b/services/sync/tests/unit/head_first.js
@@ -1,24 +1,29 @@
 version(180);
 
 const Cc = Components.classes;
 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) {
     if (iid.equals(Ci.nsIDirectoryServiceProvider) ||
         iid.equals(Ci.nsISupports)) {
       return this;
     }
     throw Cr.NS_ERROR_NO_INTERFACE;
@@ -255,8 +260,67 @@ function FakeFilesystemService(contents)
       close: function() {
         self.fakeContents[file._fakeFilename] = contents;
       },
       get _fakeData() { return contents; }
     };
     return [fakeStream];
   };
 };
+
+function FakeGUIDService() {
+  let latestGUID = 0;
+
+  Utils.makeGUID = function fake_makeGUID() {
+    return "fake-guid-" + latestGUID++;
+  };
+}
+
+function SyncTestingInfrastructure() {
+  let __fakePasswords = {
+    'Mozilla Services Password': {foo: "bar"},
+    'Mozilla Services Encryption Passphrase': {foo: "passphrase"}
+  };
+
+  let __fakePrefs = {
+    "encryption" : "none",
+    "log.logger.service.crypto" : "Debug",
+    "log.logger.service.engine" : "Debug",
+    "log.logger.async" : "Debug"
+  };
+
+  Cu.import("resource://weave/identity.js");
+
+  ID.set('WeaveID',
+         new Identity('Mozilla Services Encryption Passphrase', 'foo'));
+
+  this.fakePasswordService = new FakePasswordService(__fakePasswords);
+  this.fakePrefService = new FakePrefService(__fakePrefs);
+  this.fakeDAVService = new FakeDAVService({});
+  this.fakeTimerService = new FakeTimerService();
+  this.logStats = initTestLogging();
+  this.fakeFilesystem = new FakeFilesystemService({});
+  this.fakeGUIDService = new FakeGUIDService();
+
+  this.__makeCallback = function __makeCallback() {
+    this.__callbackCalled = false;
+    let self = this;
+    return function callback() {
+      self.__callbackCalled = true;
+    };
+  };
+
+  this.runAsyncFunc = function runAsyncFunc(name, func) {
+    let logger = getTestLogger();
+
+    logger.info("-----------------------------------------");
+    logger.info("Step '" + name + "' starting.");
+    logger.info("-----------------------------------------");
+    func(this.__makeCallback());
+    while (this.fakeTimerService.processCallback()) {}
+    do_check_true(this.__callbackCalled);
+    for (name in Async.outstandingGenerators)
+      logger.warn("Outstanding generator exists: " + name);
+    do_check_eq(this.logStats.errorsLogged, 0);
+    do_check_eq(Async.outstandingGenerators.length, 0);
+    logger.info("Step '" + name + "' succeeded.");
+  };
+}
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_crypto_crypt.js
@@ -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);
+
+}
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_crypto_keypair.js
@@ -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);
+
+
+}
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_crypto_random.js
@@ -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);
+}
new file mode 100644
--- /dev/null
+++ b/services/sync/tests/unit/test_password_syncing.js
@@ -0,0 +1,42 @@
+Cu.import("resource://weave/engines/passwords.js");
+
+load("fake_login_manager.js");
+
+// ----------------------------------------
+// Test Logic
+// ----------------------------------------
+
+function run_test() {
+  var syncTesting = new SyncTestingInfrastructure();
+  var fakeLoginManager = new FakeLoginManager(fakeSampleLogins);
+
+  function freshEngineSync(cb) {
+    let engine = new PasswordEngine();
+    engine.sync(cb);
+  };
+
+  syncTesting.runAsyncFunc("initial sync", freshEngineSync);
+
+  syncTesting.runAsyncFunc("trivial re-sync", freshEngineSync);
+
+  fakeLoginManager.fakeLogins.push(
+    {hostname: "www.yoogle.com",
+     formSubmitURL: "http://www.yoogle.com/search",
+     httpRealm: "",
+     username: "",
+     password: "",
+     usernameField: "test_person2",
+     passwordField: "test_password2"}
+  );
+
+  syncTesting.runAsyncFunc("add user and re-sync", freshEngineSync);
+
+  fakeLoginManager.fakeLogins.pop();
+
+  syncTesting.runAsyncFunc("remove user and re-sync", freshEngineSync);
+
+  syncTesting.fakeFilesystem.fakeContents = {};
+  fakeLoginManager.fakeLogins = [];
+
+  syncTesting.runAsyncFunc("resync on second computer", freshEngineSync);
+}
rename from services/sync/tests/unit/test_passwords.log.expected
rename to services/sync/tests/unit/test_password_syncing.log.expected
--- a/services/sync/tests/unit/test_passwords.log.expected
+++ b/services/sync/tests/unit/test_password_syncing.log.expected
@@ -1,52 +1,60 @@
 *** test pending
+Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'initial sync' starting.
+Testing	INFO	-----------------------------------------
 Service.PasswordEngine	INFO	Beginning sync
 Testing	INFO	HTTP MKCOL on user-data/passwords/
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 404
 Service.PasswordEngine	INFO	Initial upload to server
 Service.JsonFilter	DEBUG	Encoding data as JSON
-Testing	INFO	HTTP PUT to user-data/passwords/keys.json with data: {"ring":{}}
+Testing	INFO	HTTP PUT to user-data/passwords/keys.json with data: {"ring":{},"bulkIV":null}
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
+Service.Crypto	DEBUG	NOT encrypting data
 Testing	INFO	HTTP PUT to user-data/passwords/snapshot.json with data: {"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
+Service.Crypto	DEBUG	NOT encrypting data
 Testing	INFO	HTTP PUT to user-data/passwords/deltas.json with data: []
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":0,"snapEncryption":"none","deltasEncryption":"none","itemCount":1}
 Service.Resource	DEBUG	PUT request successful
 Service.RemoteStore	INFO	Full upload to server successful
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":0,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}}
 Testing	INFO	Step 'initial sync' succeeded.
+Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'trivial re-sync' starting.
+Testing	INFO	-----------------------------------------
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for reading.
 Testing	INFO	Reading from stream.
 Service.SnapStore	INFO	Read saved snapshot from disk
 Service.PasswordEngine	INFO	Beginning sync
 Testing	INFO	HTTP MKCOL on user-data/passwords/
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.RemoteStore	DEBUG	Downloading status file... done
 Service.PasswordEngine	INFO	Local snapshot version: 0
 Service.PasswordEngine	INFO	Server maxVersion: 0
 Service.RemoteStore	DEBUG	Using last sync snapshot as server snapshot (snap version == max version)
 Service.RemoteStore	TRACE	Local snapshot version == server maxVersion
 Service.PasswordEngine	INFO	Sync complete: no changes needed on client or server
 Testing	INFO	Step 'trivial re-sync' succeeded.
+Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'add user and re-sync' starting.
+Testing	INFO	-----------------------------------------
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for reading.
 Testing	INFO	Reading from stream.
 Service.SnapStore	INFO	Read saved snapshot from disk
 Service.PasswordEngine	INFO	Beginning sync
 Testing	INFO	HTTP MKCOL on user-data/passwords/
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
@@ -63,31 +71,35 @@ Service.PasswordEngine	INFO	Predicted ch
 Service.PasswordEngine	INFO	Client conflicts: 0
 Service.PasswordEngine	INFO	Server conflicts: 0
 Service.PasswordEngine	INFO	Actual changes for server: 1
 Service.PasswordEngine	DEBUG	Actual changes for server: [{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}]
 Service.PasswordEngine	INFO	Uploading changes to server
 Testing	INFO	HTTP GET from user-data/passwords/deltas.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
+Service.Crypto	DEBUG	NOT encrypting data
 Testing	INFO	HTTP PUT to user-data/passwords/deltas.json with data: [[{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}]]
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":1,"snapEncryption":"none","deltasEncryption":"none","itemCount":2}
 Service.Resource	DEBUG	PUT request successful
 Service.PasswordEngine	INFO	Successfully updated deltas and status on server
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":1,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"},"1b3869fc36234b39cd354f661ed1d7d148394ca3":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}}
 Service.PasswordEngine	INFO	Sync complete
 Testing	INFO	Step 'add user and re-sync' succeeded.
+Testing	INFO	-----------------------------------------
 Testing	INFO	Step 'remove user and re-sync' starting.
+Testing	INFO	-----------------------------------------
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for reading.
 Testing	INFO	Reading from stream.
 Service.SnapStore	INFO	Read saved snapshot from disk
 Service.PasswordEngine	INFO	Beginning sync
 Testing	INFO	HTTP MKCOL on user-data/passwords/
 Service.RemoteStore	DEBUG	Downloading status file
 Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
 Service.Resource	DEBUG	GET request successful
@@ -104,25 +116,74 @@ Service.PasswordEngine	INFO	Predicted ch
 Service.PasswordEngine	INFO	Client conflicts: 0
 Service.PasswordEngine	INFO	Server conflicts: 0
 Service.PasswordEngine	INFO	Actual changes for server: 1
 Service.PasswordEngine	DEBUG	Actual changes for server: [{"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}]
 Service.PasswordEngine	INFO	Uploading changes to server
 Testing	INFO	HTTP GET from user-data/passwords/deltas.json, returning status 200
 Service.Resource	DEBUG	GET request successful
 Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
 Service.JsonFilter	DEBUG	Decoding JSON data
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Service.CryptoFilter	DEBUG	Encrypting data
+Service.Crypto	DEBUG	NOT encrypting data
 Testing	INFO	HTTP PUT to user-data/passwords/deltas.json with data: [[{"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}],[{"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}]]
 Service.Resource	DEBUG	PUT request successful
 Service.JsonFilter	DEBUG	Encoding data as JSON
 Testing	INFO	HTTP PUT to user-data/passwords/status.json with data: {"GUID":"fake-guid-0","formatVersion":2,"snapVersion":0,"maxVersion":2,"snapEncryption":"none","deltasEncryption":"none","itemCount":1}
 Service.Resource	DEBUG	PUT request successful
 Service.PasswordEngine	INFO	Successfully updated deltas and status on server
 Service.SnapStore	INFO	Saving snapshot to disk
 Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
 Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":2,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}}
 Service.PasswordEngine	INFO	Sync complete
 Testing	INFO	Step 'remove user and re-sync' succeeded.
+Testing	INFO	-----------------------------------------
+Testing	INFO	Step 'resync on second computer' starting.
+Testing	INFO	-----------------------------------------
+Service.PasswordEngine	INFO	Beginning sync
+Testing	INFO	HTTP MKCOL on user-data/passwords/
+Service.RemoteStore	DEBUG	Downloading status file
+Testing	INFO	HTTP GET from user-data/passwords/status.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.RemoteStore	DEBUG	Downloading status file... done
+Service.PasswordEngine	DEBUG	Remote/local sync GUIDs do not match.  Forcing initial sync.
+Service.PasswordEngine	INFO	Local snapshot version: -1
+Service.PasswordEngine	INFO	Server maxVersion: 2
+Service.RemoteStore	TRACE	Getting latest from snap --> scratch
+Service.RemoteStore	INFO	Downloading all server data from scratch
+Service.RemoteStore	DEBUG	Downloading server snapshot
+Testing	INFO	HTTP GET from user-data/passwords/snapshot.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.RemoteStore	DEBUG	Downloading server deltas
+Testing	INFO	HTTP GET from user-data/passwords/deltas.json, returning status 200
+Service.Resource	DEBUG	GET request successful
+Service.CryptoFilter	DEBUG	Decrypting data
+Service.Crypto	DEBUG	NOT decrypting data
+Service.JsonFilter	DEBUG	Decoding JSON data
+Service.SnapStore	TRACE	Processing command: {"action":"create","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[],"data":{"hostname":"www.yoogle.com","formSubmitURL":"http://www.yoogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person2","passwordField":"test_password2"}}
+Service.SnapStore	TRACE	Processing command: {"action":"remove","GUID":"1b3869fc36234b39cd354f661ed1d7d148394ca3","depth":0,"parents":[]}
+Service.PasswordEngine	INFO	Reconciling client/server updates
+Service.PasswordSync	DEBUG	Reconciling 0 against 1 commands
+Service.PasswordEngine	INFO	Changes for client: 1
+Service.PasswordEngine	INFO	Predicted changes for server: 0
+Service.PasswordEngine	INFO	Client conflicts: 0
+Service.PasswordEngine	INFO	Server conflicts: 0
+Service.PasswordEngine	INFO	Applying changes locally
+Service.SnapStore	TRACE	Processing command: {"action":"create","GUID":"805ec58eb8dcded602999967e139be21acd0f194","depth":0,"parents":[],"data":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}
+Service.PasswordStore	TRACE	Processing command: {"action":"create","GUID":"805ec58eb8dcded602999967e139be21acd0f194","depth":0,"parents":[],"data":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}
+Service.PasswordStore	INFO	PasswordStore got createCommand: [object Object]
+Testing	INFO	nsILoginManager.addLogin() called with hostname 'www.boogle.com'.
+Service.SnapStore	INFO	Saving snapshot to disk
+Testing	INFO	Opening 'weave/snapshots/passwords.json' for writing.
+Testing	INFO	Writing data to local file 'weave/snapshots/passwords.json': {"version":2,"GUID":"fake-guid-0","snapshot":{"805ec58eb8dcded602999967e139be21acd0f194":{"hostname":"www.boogle.com","formSubmitURL":"http://www.boogle.com/search","httpRealm":"","username":"","password":"","usernameField":"test_person","passwordField":"test_password"}}}
+Service.PasswordEngine	INFO	Actual changes for server: 0
+Service.PasswordEngine	DEBUG	Actual changes for server: []
+Service.PasswordEngine	INFO	Sync complete
+Testing	INFO	Step 'resync on second computer' succeeded.
 *** test finished
 *** exiting
 *** PASS ***
--- a/services/sync/tests/unit/test_passwords.js
+++ b/services/sync/tests/unit/test_passwords.js
@@ -1,136 +1,19 @@
-Cu.import("resource://weave/util.js");
-Cu.import("resource://weave/async.js");
-Cu.import("resource://weave/dav.js");
-Cu.import("resource://weave/identity.js");
-
-// ----------------------------------------
-// Fake Data
-// ----------------------------------------
+load("fake_login_manager.js");
 
-let __fakePasswords = {
-  'Mozilla Services Password': {foo: "bar"},
-  'Mozilla Services Encryption Passphrase': {foo: "passphrase"}
-};
-
-let __fakePrefs = {
-  "encryption" : "none",
-  "log.logger.service.crypto" : "Debug",
-  "log.logger.service.engine" : "Debug",
-  "log.logger.async" : "Debug"
-};
+var loginMgr = new FakeLoginManager(fakeSampleLogins);
 
-let __fakeLogins = [
-  // Fake nsILoginInfo object.
-  {hostname: "www.boogle.com",
-   formSubmitURL: "http://www.boogle.com/search",
-   httpRealm: "",
-   username: "",
-   password: "",
-   usernameField: "test_person",
-   passwordField: "test_password"}
-];
+// The JS module we're testing, with all members exposed.
+var passwords = loadInSandbox("resource://weave/engines/passwords.js");
 
-// ----------------------------------------
-// Test Logic
-// ----------------------------------------
-
-function run_test() {
-  ID.set('Engine:PBE:default',
-         new Identity('Mozilla Services Encryption Passphrase', 'foo'));
-
-  // The JS module we're testing, with all members exposed.
-  var passwords = loadInSandbox("resource://weave/engines/passwords.js");
-
-  // Ensure that _hashLoginInfo() works.
-  var fakeUserHash = passwords._hashLoginInfo(__fakeLogins[0]);
+function test_hashLoginInfo_works() {
+  var fakeUserHash = passwords._hashLoginInfo(fakeSampleLogins[0]);
   do_check_eq(typeof fakeUserHash, 'string');
   do_check_eq(fakeUserHash.length, 40);
+}
 
-  // Ensure that PasswordSyncCore._itemExists() works.
+function test_synccore_itemexists_works() {
+  var fakeUserHash = passwords._hashLoginInfo(fakeSampleLogins[0]);
   var psc = new passwords.PasswordSyncCore();
   do_check_false(psc._itemExists("invalid guid"));
   do_check_true(psc._itemExists(fakeUserHash));
-
-  // Make sure the engine can sync.
-  function freshEngineSync(cb) {
-    let engine = new passwords.PasswordEngine();
-    engine.sync(cb);
-  };
-
-  runAndEnsureSuccess("initial sync", freshEngineSync);
-
-  runAndEnsureSuccess("trivial re-sync", freshEngineSync);
-
-  fakeLoginManager.fakeLogins.push(
-    {hostname: "www.yoogle.com",
-     formSubmitURL: "http://www.yoogle.com/search",
-     httpRealm: "",
-     username: "",
-     password: "",
-     usernameField: "test_person2",
-     passwordField: "test_password2"}
-  );
-
-  runAndEnsureSuccess("add user and re-sync", freshEngineSync);
-
-  fakeLoginManager.fakeLogins.pop();
-
-  runAndEnsureSuccess("remove user and re-sync", freshEngineSync);
 }
-
-// ----------------------------------------
-// Helper Functions
-// ----------------------------------------
-
-var callbackCalled = false;
-
-function __makeCallback() {
-  callbackCalled = false;
-  return function callback() {
-    callbackCalled = true;
-  };
-}
-
-function runAndEnsureSuccess(name, func) {
-  getTestLogger().info("Step '" + name + "' starting.");
-  func(__makeCallback());
-  while (fts.processCallback()) {}
-  do_check_true(callbackCalled);
-  for (name in Async.outstandingGenerators)
-    getTestLogger().warn("Outstanding generator exists: " + name);
-  do_check_eq(logStats.errorsLogged, 0);
-  do_check_eq(Async.outstandingGenerators.length, 0);
-  getTestLogger().info("Step '" + name + "' succeeded.");
-}
-
-// ----------------------------------------
-// Fake Infrastructure
-// ----------------------------------------
-
-var fpasses = new FakePasswordService(__fakePasswords);
-var fprefs = new FakePrefService(__fakePrefs);
-var fds = new FakeDAVService({});
-var fts = new FakeTimerService();
-var logStats = initTestLogging();
-var ffs = new FakeFilesystemService({});
-var fgs = new FakeGUIDService();
-var fakeLoginManager = new FakeLoginManager(__fakeLogins);
-
-function FakeGUIDService() {
-  let latestGUID = 0;
-
-  Utils.makeGUID = function fake_makeGUID() {
-    return "fake-guid-" + latestGUID++;
-  };
-}
-
-function FakeLoginManager(fakeLogins) {
-  this.fakeLogins = fakeLogins;
-
-  let self = this;
-
-  Utils.getLoginManager = function fake_getLoginManager() {
-    // Return a fake nsILoginManager object.
-    return {getAllLogins: function() { return self.fakeLogins; }};
-  };
-}
deleted file mode 100644
--- a/services/sync/tests/unit/test_pbe.js
+++ /dev/null
@@ -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);
-}
--- a/services/sync/tests/unit/test_service.js
+++ b/services/sync/tests/unit/test_service.js
@@ -16,28 +16,16 @@ let __fakeDAVContents = {
   "private/privkey" : "fake private key"
 };
 
 let __fakePasswords = {
   'Mozilla Services Password': {foo: "bar"},
   '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() {
   this.__superclassConstructor = Service.WeaveSvc;
   this.__superclassConstructor([]);
 }
 
 TestService.prototype = {