A passphrase is like a password that protects your PGP key.
If your device is lost or stolen the passphrase protects the contents of your mailbox.
-You cannot change your passphrase at a later time.
diff --git a/src/js/app.js b/src/js/app.js index 89f6246..656bfdd 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -7,6 +7,7 @@ requirejs([ 'js/controller/popover', 'js/controller/add-account', 'js/controller/account', + 'js/controller/set-passphrase', 'js/controller/contacts', 'js/controller/login', 'js/controller/login-initial', @@ -26,6 +27,7 @@ requirejs([ PopoverCtrl, AddAccountCtrl, AccountCtrl, + SetPassphraseCtrl, ContactsCtrl, LoginCtrl, LoginInitialCtrl, @@ -92,6 +94,7 @@ requirejs([ app.controller('WriteCtrl', WriteCtrl); app.controller('MailListCtrl', MailListCtrl); app.controller('AccountCtrl', AccountCtrl); + app.controller('SetPassphraseCtrl', SetPassphraseCtrl); app.controller('ContactsCtrl', ContactsCtrl); app.controller('DialogCtrl', DialogCtrl); app.controller('PopoverCtrl', PopoverCtrl); diff --git a/src/js/controller/account.js b/src/js/controller/account.js index 5a9d13c..5e0a5c5 100644 --- a/src/js/controller/account.js +++ b/src/js/controller/account.js @@ -3,14 +3,16 @@ define(function(require) { var appController = require('js/app-controller'), dl = require('js/util/download'), - emailDao; + pgp, keychain, userId; // // Controller // var AccountCtrl = function($scope) { - emailDao = appController._emailDao; + userId = appController._emailDao._account.emailAddress; + keychain = appController._keychain; + pgp = appController._crypto; $scope.state.account = { open: false, @@ -23,32 +25,49 @@ define(function(require) { // scope variables // - var fpr = emailDao._crypto.getFingerprint(), - keyId = emailDao._crypto.getKeyId(); - $scope.eMail = emailDao._account.emailAddress; - $scope.keyId = keyId.slice(8); + var keyParams = pgp.getKeyParams(); + + $scope.eMail = userId; + $scope.keyId = keyParams._id.slice(8); + var fpr = keyParams.fingerprint; $scope.fingerprint = fpr.slice(0, 4) + ' ' + fpr.slice(4, 8) + ' ' + fpr.slice(8, 12) + ' ' + fpr.slice(12, 16) + ' ' + fpr.slice(16, 20) + ' ' + fpr.slice(20, 24) + ' ' + fpr.slice(24, 28) + ' ' + fpr.slice(28, 32) + ' ' + fpr.slice(32, 36) + ' ' + fpr.slice(36); - $scope.keysize = emailDao._account.asymKeySize; + $scope.keysize = keyParams.bitSize; // // scope functions // $scope.exportKeyFile = function() { - emailDao._crypto.exportKeys(function(err, keys) { + keychain.getUserKeyPair(userId, function(err, keys) { if (err) { $scope.onError(err); return; } - var id = 'whiteout_mail_' + emailDao._account.emailAddress + '_' + keys.keyId.substring(8, keys.keyId.length); + var keyId = keys.publicKey._id; + var file = 'whiteout_mail_' + userId + '_' + keyId.substring(8, keyId.length); + dl.createDownload({ - content: keys.publicKeyArmored + keys.privateKeyArmored, - filename: id + '.asc', + content: keys.publicKey.publicKey + keys.privateKey.encryptedKey, + filename: file + '.asc', contentType: 'text/plain' - }, $scope.onError); + }, onExport); }); }; + + function onExport(err) { + if (err) { + $scope.onError(err); + return; + } + + $scope.state.account.toggle(false); + $scope.$apply(); + $scope.onError({ + title: 'Success', + message: 'Exported keypair to file.' + }); + } }; return AccountCtrl; diff --git a/src/js/controller/set-passphrase.js b/src/js/controller/set-passphrase.js new file mode 100644 index 0000000..a280135 --- /dev/null +++ b/src/js/controller/set-passphrase.js @@ -0,0 +1,83 @@ +define(function(require) { + 'use strict'; + + var appController = require('js/app-controller'), + pgp, keychain; + + // + // Controller + // + + var SetPassphraseCtrl = function($scope) { + keychain = appController._keychain; + pgp = appController._crypto; + + $scope.state.setPassphrase = { + open: false, + toggle: function(to) { + this.open = to; + + $scope.newPassphrase = undefined; + $scope.oldPassphrase = undefined; + $scope.confirmation = undefined; + } + }; + + // + // scope variables + // + + // + // scope functions + // + + $scope.setPassphrase = function() { + var keyId = pgp.getKeyParams()._id; + keychain.lookupPrivateKey(keyId, function(err, savedKey) { + if (err) { + $scope.onError(err); + return; + } + + pgp.changePassphrase({ + privateKeyArmored: savedKey.encryptedKey, + oldPassphrase: $scope.oldPassphrase, + newPassphrase: $scope.newPassphrase + }, onPassphraseChanged); + }); + }; + + function onPassphraseChanged(err, newPrivateKeyArmored) { + if (err) { + $scope.onError(err); + return; + } + + // persist new armored key + var keyParams = pgp.getKeyParams(newPrivateKeyArmored); + var privateKey = { + _id: keyParams._id, + userId: keyParams.userId, + encryptedKey: newPrivateKeyArmored + }; + + keychain.saveLocalPrivateKey(privateKey, onKeyPersisted); + } + + function onKeyPersisted(err) { + if (err) { + $scope.onError(err); + return; + } + + $scope.state.setPassphrase.toggle(false); + $scope.$apply(); + $scope.onError({ + title: 'Success', + message: 'Passphrase change complete.' + }); + } + }; + + return SetPassphraseCtrl; +}); \ No newline at end of file diff --git a/src/js/crypto/pgp.js b/src/js/crypto/pgp.js index ab53d6f..649e0b3 100644 --- a/src/js/crypto/pgp.js +++ b/src/js/crypto/pgp.js @@ -17,7 +17,7 @@ define(function(require) { * Generate a key pair for the user */ PGP.prototype.generateKeys = function(options, callback) { - var userId; + var userId, passphrase; if (!util.emailRegEx.test(options.emailAddress) || !options.keySize) { callback({ @@ -28,7 +28,8 @@ define(function(require) { // generate keypair (keytype 1=RSA) userId = 'Whiteout User <' + options.emailAddress + '>'; - openpgp.generateKeyPair(1, options.keySize, userId, options.passphrase, onGenerated); + passphrase = (options.passphrase) ? options.passphrase : undefined; + openpgp.generateKeyPair(1, options.keySize, userId, passphrase, onGenerated); function onGenerated(err, keys) { if (err) { @@ -99,8 +100,18 @@ define(function(require) { * Read all relevant params of an armored key. */ PGP.prototype.getKeyParams = function(keyArmored) { - var key = openpgp.key.readArmored(keyArmored).keys[0], - packet = key.getKeyPacket(); + var key, packet; + + // process armored key input + if (keyArmored) { + key = openpgp.key.readArmored(keyArmored).keys[0]; + } else if (this._publicKey) { + key = this._publicKey; + } else { + throw new Error('Cannot read key params... keys not set!'); + } + + packet = key.getKeyPacket(); return { _id: packet.getKeyId().toHex().toUpperCase(), @@ -188,17 +199,24 @@ define(function(require) { * Change the passphrase of an ascii armored private key. */ PGP.prototype.changePassphrase = function(options, callback) { - var privKey, packets; + var privKey, packets, newPassphrase, newKeyArmored; - if (!options.privateKeyArmored || - typeof options.oldPassphrase !== 'string' || - typeof options.newPassphrase !== 'string') { + // set undefined instead of empty string as passphrase + newPassphrase = (options.newPassphrase) ? options.newPassphrase : undefined; + + if (!options.privateKeyArmored) { callback({ - errMsg: 'Could not export keys!' + errMsg: 'Private key must be specified to change passphrase!' }); return; } + if (options.oldPassphrase === newPassphrase || + (!options.oldPassphrase && !newPassphrase)) { + callback(new Error('New and old passphrase are the same!')); + return; + } + // read armored key try { privKey = openpgp.key.readArmored(options.privateKeyArmored).keys[0]; @@ -221,8 +239,9 @@ define(function(require) { try { packets = privKey.getAllKeyPackets(); for (var i = 0; i < packets.length; i++) { - packets[i].encrypt(options.newPassphrase); + packets[i].encrypt(newPassphrase); } + newKeyArmored = privKey.armor(); } catch (e) { callback({ errMsg: 'Setting new passphrase failed!' @@ -230,7 +249,15 @@ define(function(require) { return; } - callback(null, privKey.armor()); + // check if new passphrase really works + if (!privKey.decrypt(newPassphrase)) { + callback({ + errMsg: 'Decrypting key with new passphrase failed!' + }); + return; + } + + callback(null, newKeyArmored); }; /** diff --git a/src/js/util/error.js b/src/js/util/error.js index 025c4e3..05749a6 100644 --- a/src/js/util/error.js +++ b/src/js/util/error.js @@ -10,7 +10,11 @@ define(function() { return; } - console.error(options); + if (options.stack) { + console.error(options.stack); + } else { + console.error(options); + } scope.state.dialog = { open: true, diff --git a/src/sass/all.scss b/src/sass/all.scss index cb33f9d..5f94d17 100755 --- a/src/sass/all.scss +++ b/src/sass/all.scss @@ -15,6 +15,7 @@ @import "components/icons"; @import "components/lightbox"; @import "components/nav"; +@import "components/dialog"; @import "components/mail-list"; @import "components/layout"; @import "components/popover"; @@ -24,6 +25,7 @@ @import "views/shared"; @import "views/add-account"; @import "views/account"; +@import "views/set-passphrase"; @import "views/contacts"; @import "views/dialog"; @import "views/navigation"; diff --git a/src/sass/components/_dialog.scss b/src/sass/components/_dialog.scss new file mode 100644 index 0000000..2794a6d --- /dev/null +++ b/src/sass/components/_dialog.scss @@ -0,0 +1,17 @@ +.dialog { + padding: 0px; + color: $color-grey-dark; + + @include respond-to(mobile) { + top: 0; + max-width: 100%; + } + + .control { + float: right; + + button { + border: 0!important; + } + } +} \ No newline at end of file diff --git a/src/sass/views/_account.scss b/src/sass/views/_account.scss index 17b115f..5466502 100644 --- a/src/sass/views/_account.scss +++ b/src/sass/views/_account.scss @@ -1,13 +1,7 @@ .view-account { - padding: 0px; - color: $color-grey-dark; - - @include respond-to(mobile) { - height: 100%; - } table { - margin: 50px auto 100px auto; + margin: 50px auto 60px auto; td { padding-top: 15px; @@ -20,13 +14,4 @@ } } - button { - border: 0!important; - } - - .export-control { - position: absolute; - bottom: 15px; - right: 15px; - } } \ No newline at end of file diff --git a/src/sass/views/_dialog.scss b/src/sass/views/_dialog.scss index 2ec0daf..49a66aa 100644 --- a/src/sass/views/_dialog.scss +++ b/src/sass/views/_dialog.scss @@ -1,29 +1,17 @@ .view-dialog { - padding: 0px; - color: $color-grey-dark; max-width: 350px; height: auto; top: 30%; - @include respond-to(mobile) { - top: 0; - max-width: 100%; - } - p { text-align: center; max-width: 80%; - margin: 30px auto 70px auto; + margin: 30px auto; } .control { - position: absolute; - bottom: $lightbox-padding; - right: $lightbox-padding; - button { width: 100px; - border: 0!important; } } } \ No newline at end of file diff --git a/src/sass/views/_set-passphrase.scss b/src/sass/views/_set-passphrase.scss new file mode 100644 index 0000000..33d19a0 --- /dev/null +++ b/src/sass/views/_set-passphrase.scss @@ -0,0 +1,25 @@ +.view-set-passphrase { + + .inputs { + margin: 40px 60px 30px; + + div { + margin: 5px 0; + } + } + + table { + margin: 50px auto 60px auto; + + td { + padding-top: 15px; + + &:first-child { + text-align: right; + padding-right: 15px; + font-weight: bold; + } + } + } + +} \ No newline at end of file diff --git a/src/tpl/account.html b/src/tpl/account.html index 3c805ba..90c5491 100644 --- a/src/tpl/account.html +++ b/src/tpl/account.html @@ -5,7 +5,8 @@
A passphrase is like a password that protects your PGP key.
If your device is lost or stolen the passphrase protects the contents of your mailbox.
-You cannot change your passphrase at a later time.
The passphrase protects your encrypted mailbox.
+A passphrase is like a password that protects your PGP key.
There is no way to access your messages without your passphrase.
If you have forgotten your passphrase, please request an account reset by sending an email to support@whiteout.io. You will not be able to read previous messages after a reset.
+ | + |
+ | + |
+ | + |