describe('download', function() {
        var base64Content = 'AYzsvV+hGMMT4BIl/XFjbl60BaM5DpDYVNyKPnoZ4ZyW1qy1udkQR7VUeNKJw5v2gWOqc3y6KHkZIqybOVro6e8tzhK1Fvpz+rgmME0tbrrh/Dd6QMBXb9c6ZAzgbLdq0sxftqXO9GoxINAVcfGN/MkcOIhonEjIsLSaYY2WLuGOLp8ZNdgO0tPxfcdd/f1hVXH2JRYmkOwStH3y2uYDmUhEWWeLfP2vF57F4NgtK2Ln4Ypn4VDx1SWtI6E1IMpwchpwXssBwzY2uWKUPNbWEwEYDU6pleWCKphc2YBp0ohJg1HfE+Et9/8wsZtQAjTiigZuovRd5ABd6LkCCuPNenmzKvR5os8fbe9HDsAiDYl5OrA1iGTWVcAKec1OWxRWKn3Ktt/v+W39gxvmA6OOSuPkA3PF+1rY2lU05busVlNVmNmv6vY3LTJz4J/jVPP7Bn6+Wl/BwdGC7OagZCORmDUujk4AaIz5y+x/hgS6g9yY8oaY5EGdFCxRpS7aptqiBNIXIpuxGtKZpP3bmjI4pIcVb4xTA57SFTE7czfvlvTjvBSCQP7MGYCNC+SbDRgt1beyM8uUrKiuLTWK+YJ6rvcIvOIEqvUBDR7ak+9S6+fyxw033vNHfQSAagIUC1eq+c8yoUzvtSRISOMEbu7MnjI5i4AQrD5yfJDJdp5NTpZ0Dz3fW3RVmMhghTGN3ch+6vVwkzO2ik11EGTqwaLfOgZuwunEonXLT4v4fJjIFvsl+hMab0keksuW1G8AQCdkNcgDfxMTIz6S/k51yVIGE2DZo1e1LTc7pu8gOCNHtuNMuwzDTZuutWdd0P93ZL7W6j1eq33DShX2zeuxk5S28crn6DdlK5QBYMSpECU1JDKRu1QMBNtiEgGlJaVOi1AQ+cDdZKthMYfJ0MPHCeRyQFMpEYkYUBfhBMnGaiDTmDFMvEy0WGhabKChhtdBF/rlyug8Kx9M60lx1t9dYVbxSmWkqWgUZ36vRwQXPVEWWmRROHtG/V9+CSPCCa9heDDqqj8nKzL0vK9kBG8nh2XlPAVg7ICicTLw0u93pz4US3pRKwfMys1mQNV0z0k0uXB1zJZqDrIsCihcUMC2vFOXg+dNlanPXeP8AMp3ojuMPAClIt+bxTyrjZ7MV0mkDuaWUaeEq2xuaU5cKlG1Aam6vSb3jmURgEzOk1onlkGrCfVUTne18W8V6KL7iG+lX+331baiZVGoMUXT++0T05KYBdTRYL0OZ0P3OPRqilPpxaZCY0NG5rJxC5ij0vnu9ECAvN3xSdiRF7SobVSVFIdc32aY24nLKv8/gSnROgmQbAqeCMOz0bULRyVTe0lzSXBcCgu5gK+KEo8p38trTSJ/S95sKQnyNbrnz2QOIXvzxLrL6/nnC/4pwxXKZ6XqB/2zLVfiJRjUQ1NUC0xDXA==';
        var root = [{
            type: 'attachment',
            content: util.binStr2Uint8Arr(util.base642Str(base64Content))
        }];

        beforeEach(function() {
            sinon.stub(privkeyDao, '_getFolder');
            sinon.stub(privkeyDao, '_fetchMessage');
            sinon.stub(privkeyDao, '_parse');
        });
        afterEach(function() {
            privkeyDao._getFolder.restore();
            privkeyDao._fetchMessage.restore();
            privkeyDao._parse.restore();
        });

        it('should fail if key not synced', function(done) {
            privkeyDao._getFolder.returns(resolves('foo'));
            privkeyDao._fetchMessage.returns(resolves());

            privkeyDao.download({
                userId: emailAddress,
                keyId: keyId
            }).catch(function(err) {
                expect(err.message).to.match(/not synced/);
                done();
            });
        });

        it('should work', function(done) {
            privkeyDao._getFolder.returns(resolves('foo'));
            privkeyDao._fetchMessage.returns(resolves({}));
            imapClientStub.getBodyParts.returns(resolves());
            privkeyDao._parse.returns(resolves(root));

            privkeyDao.download({
                userId: emailAddress,
                keyId: keyId
            }).then(function(privkey) {
                expect(privkey._id).to.equal(keyId);
                expect(privkey.userId).to.equal(emailAddress);
                expect(privkey.encryptedPrivateKey).to.exist;
                done();
            });
        });
    });
Example #2
0
Account.prototype.init = function(options) {
    var self = this;

    // account information for the email dao
    var account = {
        realname: options.realname,
        emailAddress: options.emailAddress,
        asymKeySize: this._appConfig.config.asymKeySize
    };

    // Pre-Flight check: don't even start to initialize stuff if the email address is not valid
    if (!util.validateEmailAddress(options.emailAddress)) {
        return new Promise(function() {
            throw new Error('The user email address is invalid!');
        });
    }

    // Pre-Flight check: initialize and prepare user's local database
    return self._accountStore.init(options.emailAddress).then(function() {
        // Migrate the databases if necessary
        return self._updateHandler.update().catch(function(err) {
            throw new Error('Updating the internal database failed. Please reinstall the app! Reason: ' + err.message);
        });

    }).then(function() {
        // retrieve keypair fom devicestorage/cloud, refresh public key if signup was incomplete before
        return self._keychain.getUserKeyPair(options.emailAddress);

    }).then(function(keys) {
        // this is either a first start on a new device, OR a subsequent start without completing the signup,
        // since we can't differenciate those cases here, do a public key refresh because it might be outdated
        if (keys && keys.publicKey && !keys.privateKey) {
            return self._keychain.refreshKeyForUserId({
                userId: options.emailAddress,
                overridePermission: true
            }).then(function(publicKey) {
                return {
                    publicKey: publicKey
                };
            });
        }
        // either signup was complete or no pubkey is available, so we're good here.
        return keys;

    }).then(function(keys) {
        // init the email data access object
        return self._emailDao.init({
            account: account
        }).then(function() {
            // Handle offline and online gracefully ... arm dom event
            window.addEventListener('online', self.onConnect.bind(self));
            window.addEventListener('offline', self.onDisconnect.bind(self));

            // add account object to the accounts array for the ng controllers
            self._accounts.push(account);

            return keys;
        });
    });
};
Example #3
0
        self.getUserKeyPair(options.userId, function(err, keypair) {
            if (err) {
                callback(err);
                return;
            }

            var privkeyId = keypair.privateKey._id,
                pgpBlock = keypair.privateKey.encryptedKey;

            // encrypt the private key with the derived key
            var iv = util.random(config.symIvSize);
            self._crypto.encrypt(pgpBlock, encryptionKey, iv, function(err, ct) {
                if (err) {
                    callback(err);
                    return;
                }

                var payload = {
                    _id: privkeyId,
                    userId: options.userId,
                    encryptedPrivateKey: ct,
                    salt: salt,
                    iv: iv
                };

                uploadPrivateKey(payload);
            });
        });
Example #4
0
        self.getDeviceSecret(function(err, deviceSecret) {
            if (err) {
                callback(err);
                return;
            }

            var iv = util.random(config.symIvSize);
            // encrypt the challenge
            self._crypto.encrypt(challenge, sessionKey, iv, function(err, encryptedChallenge) {
                if (err) {
                    callback(err);
                    return;
                }

                // encrypt the device secret
                self._crypto.encrypt(deviceSecret, sessionKey, iv, function(err, encryptedDeviceSecret) {
                    if (err) {
                        callback(err);
                        return;
                    }

                    replyChallenge({
                        encryptedChallenge: encryptedChallenge,
                        encryptedDeviceSecret: encryptedDeviceSecret,
                        iv: iv
                    }, sessionKey);
                });
            });
        });
Example #5
0
 function deriveKey(code) {
     // generate random salt
     salt = util.random(keySize);
     // derive key from the code using PBKDF2
     return self._crypto.deriveKey(code, salt, keySize).then(function(key) {
         return encryptPrivateKey(key);
     });
 }
Example #6
0
        it('should work', function(done) {
            var plaintext = 'Hello, World!';
            var key = util.random(keySize);
            var iv = util.random(ivSize);

            crypto.encrypt(plaintext, key, iv, function(err, ciphertext) {
                expect(err).to.not.exist;
                expect(ciphertext).to.exist;

                crypto.decrypt(ciphertext, key, iv, function(err, decrypted) {
                    expect(err).to.not.exist;
                    expect(decrypted).to.equal(plaintext);

                    done();
                });
            });
        });
Example #7
0
        it('should work', function(done) {
            var salt = util.random(keySize);

            crypto.deriveKey(password, salt, keySize, function(err, key) {
                expect(err).to.not.exist;
                expect(util.base642Str(key).length * 8).to.equal(keySize);

                done();
            });
        });
Example #8
0
    $scope.displayUploadUi = function() {
        // go to step 1
        $scope.step = 1;
        // generate new code for the user
        $scope.code = util.randomString(24);
        $scope.displayedCode = $scope.code.slice(0, 4) + '-' + $scope.code.slice(4, 8) + '-' + $scope.code.slice(8, 12) + '-' + $scope.code.slice(12, 16) + '-' + $scope.code.slice(16, 20) + '-' + $scope.code.slice(20, 24);

        // clear input fields of any previous artifacts
        $scope.code0 = $scope.code1 = $scope.code2 = $scope.code3 = $scope.code4 = $scope.code5 = '';
    };
Example #9
0
 function check(recipient) {
     // validate address
     if (!util.validateEmailAddress(recipient.address)) {
         return;
     }
     numReceivers++;
     if (!recipient.secure) {
         allSecure = false;
     }
 }
Example #10
0
    $scope.verify = function(recipient) {
        if (!recipient) {
            return;
        }

        if (recipient.address) {
            // display only email address after autocomplete
            recipient.displayId = recipient.address;
        } else {
            // set address after manual input
            recipient.address = recipient.displayId;
        }

        // set display to insecure while fetching keys
        recipient.key = undefined;
        recipient.secure = false;
        $scope.checkSendStatus();

        // verify email address
        if (!util.validateEmailAddress(recipient.address)) {
            recipient.secure = undefined;
            $scope.checkSendStatus();
            return;
        }

        // check if to address is contained in known public keys
        // when we write an email, we always need to work with the latest keys available
        return $q(function(resolve) {
            resolve();

        }).then(function() {
            return keychain.refreshKeyForUserId({
                userId: recipient.address
            });

        }).then(function(key) {
            if (key) {
                // compare again since model could have changed during the roundtrip
                var userIds = pgp.getKeyParams(key.publicKey).userIds;
                var matchingUserId = _.findWhere(userIds, {
                    emailAddress: recipient.address
                });
                // compare either primary userId or (if available) multiple IDs
                if (matchingUserId) {
                    recipient.key = key;
                    recipient.secure = true;
                }
            } else {
                // show invite dialog if no key found
                $scope.showInvite = true;
            }
            $scope.checkSendStatus();

        }).catch(dialog.error);
    };
Example #11
0
Outbox.prototype.put = function(mail) {
    var self = this,
        allReaders = mail.from.concat(mail.to.concat(mail.cc.concat(mail.bcc))); // all the users that should be able to read the mail

    mail.publicKeysArmored = []; // gather the public keys
    mail.uid = mail.id = util.UUID(); // the mail needs a random id & uid for storage in the database

    // do not encrypt mails with a bcc recipient, due to a possible privacy leak
    if (mail.bcc.length > 0) {
        return storeAndForward(mail);
    }

    return checkRecipients(allReaders).then(checkEncrypt);

    // check if there are unregistered recipients
    function checkRecipients(recipients) {
        var pubkeyJobs = [];
        recipients.forEach(function(recipient) {
            var promise = self._keychain.getReceiverPublicKey(recipient.address).then(function(key) {
                // if a public key is available, add the recipient's key to the armored public keys,
                // otherwise remember the recipient as unregistered for later sending
                if (key) {
                    mail.publicKeysArmored.push(key.publicKey);
                }
            });
            pubkeyJobs.push(promise);
        });

        return Promise.all(pubkeyJobs);
    }

    function checkEncrypt() {
        // only encrypt if all recipients have public keys
        if (mail.publicKeysArmored.length < allReaders.length) {
            return storeAndForward(mail);
        }

        // encrypts the body and attachments and persists the mail object
        return self._emailDao.encrypt({
            mail: mail,
            publicKeysArmored: mail.publicKeysArmored
        }).then(function() {
            return storeAndForward(mail);
        });
    }

    function storeAndForward(mail) {
        // store in outbox
        return self._devicestorage.storeList([mail], outboxDb).then(function() {
            // don't wait for next round
            self._processOutbox(self._onUpdate);
        });
    }
};
Example #12
0
    function createMessage() {
        var encryptedKeyBuf = util.binStr2Uint8Arr(util.base642Str(options.encryptedPrivateKey));
        var saltBuf = util.binStr2Uint8Arr(util.base642Str(options.salt));
        var ivBuf = util.binStr2Uint8Arr(util.base642Str(options.iv));

        // allocate payload buffer for sync
        var payloadBuf = new Uint8Array(1 + saltBuf.length + ivBuf.length + encryptedKeyBuf.length);
        var offset = 0;
        // set version byte
        payloadBuf[offset] = 0x01; // version 1 of the key-sync protocol
        offset++;
        // copy salt bytes
        payloadBuf.set(saltBuf, offset);
        offset += saltBuf.length;
        // copy iv bytes
        payloadBuf.set(ivBuf, offset);
        offset += ivBuf.length;
        // copy encrypted key bytes
        payloadBuf.set(encryptedKeyBuf, offset);

        // create MIME tree
        var rootNode = options.rootNode || new self._Mailbuild();
        rootNode.setHeader({
            subject: options._id,
            from: options.userId,
            to: options.userId,
            'content-type': MIME_TYPE + '; charset=us-ascii',
            'content-transfer-encoding': 'base64'
        });
        rootNode.setContent(payloadBuf);

        return rootNode.build();
    }
Example #13
0
    }).then(function(root) {
        var payloadBuf = filterBodyParts(root, MSG_PART_TYPE_ATTACHMENT)[0].content;
        var offset = 0;
        var SALT_LEN = 32;
        var IV_LEN = 12;

        // check version
        var version = payloadBuf[offset];
        offset++;
        if (version !== 1) {
            throw new Error('Unsupported key sync protocol version!');
        }
        // salt
        var saltBuf = payloadBuf.subarray(offset, offset + SALT_LEN);
        offset += SALT_LEN;
        // iv
        var ivBuf = payloadBuf.subarray(offset, offset + IV_LEN);
        offset += IV_LEN;
        // encrypted private key
        var encryptedKeyBuf = payloadBuf.subarray(offset, payloadBuf.length);

        return {
            _id: options.keyId,
            userId: options.userId,
            encryptedPrivateKey: util.str2Base64(util.uint8Arr2BinStr(encryptedKeyBuf)),
            salt: util.str2Base64(util.uint8Arr2BinStr(saltBuf)),
            iv: util.str2Base64(util.uint8Arr2BinStr(ivBuf))
        };
    });
Example #14
0
    return self._lawnchairDAO.read(DB_DEVICE_SECRET).then(function(storedDevSecret) {
        if (storedDevSecret) {
            // a device key is already available locally
            return storedDevSecret;
        }

        // generate random deviceSecret
        var deviceSecret = util.random(config.symKeySize);
        // persist deviceSecret to local storage (in plaintext)
        return self._lawnchairDAO.persist(DB_DEVICE_SECRET, deviceSecret).then(function() {
            return deviceSecret;
        });
    });
Example #15
0
 function check(recipient) {
     // validate address
     if (!util.validateEmailAddress(recipient.address)) {
         return dialog.info({
             title: 'Warning',
             message: 'Invalid recipient address!'
         });
     }
     numReceivers++;
     if (!recipient.secure) {
         allSecure = false;
     }
 }
Example #16
0
    function deriveKey(code) {
        // generate random salt
        salt = util.random(keySize);
        // derive key from the code using PBKDF2
        self._crypto.deriveKey(code, salt, keySize, function(err, key) {
            if (err) {
                callback(err);
                return;
            }

            encryptPrivateKey(key);
        });
    }
Example #17
0
PrivateKey.prototype.encrypt = function(code) {
    var self = this,
        config = self._appConfig.config,
        keySize = config.symKeySize,
        encryptionKey, salt, iv, privkeyId;

    if (!code) {
        return new Promise(function() {
            throw new Error('Incomplete arguments!');
        });
    }

    // generate random salt and iv
    salt = util.random(keySize);
    iv = util.random(config.symIvSize);

    // derive key from the code using PBKDF2
    return self._crypto.deriveKey(code, salt, keySize).then(function(key) {
        encryptionKey = key;

        // get private key from local storage
        return self._pgp.exportKeys();
    }).then(function(keypair) {
        privkeyId = keypair.keyId;

        // encrypt the private key with the derived key
        return self._crypto.encrypt(keypair.privateKeyArmored, encryptionKey, iv);

    }).then(function(ct) {
        return {
            _id: privkeyId,
            encryptedPrivateKey: ct,
            salt: salt,
            iv: iv
        };
    });
};
Example #18
0
    function uploadDeviceSecret(regSessionKey) {
        // generate iv
        var iv = util.random(config.symIvSize);
        // read device secret from local storage
        return self.getDeviceSecret().then(function(deviceSecret) {
            // encrypt deviceSecret
            return self._crypto.encrypt(deviceSecret, regSessionKey, iv);

        }).then(function(encryptedDeviceSecret) {
            // upload encryptedDeviceSecret
            return self._privateKeyDao.uploadDeviceSecret({
                userId: options.userId,
                deviceName: devName,
                encryptedDeviceSecret: encryptedDeviceSecret,
                iv: iv
            });
        });
    }
Example #19
0
    $scope.verify = function(recipient) {
        if (!recipient) {
            return;
        }

        // set display to insecure while fetching keys
        recipient.key = undefined;
        recipient.secure = false;
        $scope.checkSendStatus();

        // verify email address
        if (!util.validateEmailAddress(recipient.address)) {
            recipient.secure = undefined;
            $scope.checkSendStatus();
            return;
        }

        // check if to address is contained in known public keys
        // when we write an email, we always need to work with the latest keys available
        return $q(function(resolve) {
            resolve();

        }).then(function() {
            return keychain.refreshKeyForUserId({
                userId: recipient.address
            });

        }).then(function(key) {
            if (key) {
                // compare again since model could have changed during the roundtrip
                var matchingUserId = _.findWhere(key.userIds, {
                    emailAddress: recipient.address
                });
                // compare either primary userId or (if available) multiple IDs
                if (key.userId === recipient.address || matchingUserId) {
                    recipient.key = key;
                    recipient.secure = true;
                }
            }
            $scope.checkSendStatus();

        }).catch(dialog.error);
    };
Example #20
0
    function encryptPrivateKey(encryptionKey) {
        var privkeyId, pgpBlock,
            iv = util.random(config.symIvSize);

        // get private key from local storage
        return self.getUserKeyPair(options.userId).then(function(keypair) {
            privkeyId = keypair.privateKey._id;
            pgpBlock = keypair.privateKey.encryptedKey;

            // encrypt the private key with the derived key
            return self._crypto.encrypt(pgpBlock, encryptionKey, iv);

        }).then(function(ct) {
            return uploadPrivateKey({
                _id: privkeyId,
                userId: options.userId,
                encryptedPrivateKey: ct,
                salt: salt,
                iv: iv
            });
        });
    }
Example #21
0
    function encryptChallenge(sessionKey, challenge) {
        var deviceSecret, encryptedChallenge;
        var iv = util.random(config.symIvSize);
        // get device secret
        return self.getDeviceSecret().then(function(secret) {
            deviceSecret = secret;
            // encrypt the challenge
            return self._crypto.encrypt(challenge, sessionKey, iv);

        }).then(function(ct) {
            encryptedChallenge = ct;
            // encrypt the device secret
            return self._crypto.encrypt(deviceSecret, sessionKey, iv);

        }).then(function(encryptedDeviceSecret) {
            return replyChallenge({
                encryptedChallenge: encryptedChallenge,
                encryptedDeviceSecret: encryptedDeviceSecret,
                iv: iv
            }, sessionKey);
        });
    }
Example #22
0
    self._localDbDao.read(DB_DEVICE_SECRET, function(err, storedDevSecret) {
        if (err) {
            callback(err);
            return;
        }

        if (storedDevSecret) {
            // a device key is already available locally
            callback(null, storedDevSecret);
            return;
        }

        // generate random deviceSecret
        var deviceSecret = util.random(config.symKeySize);
        // persist deviceSecret to local storage (in plaintext)
        self._localDbDao.persist(DB_DEVICE_SECRET, deviceSecret, function(err) {
            if (err) {
                callback(err);
                return;
            }

            callback(null, deviceSecret);
        });
    });
Example #23
0
        self.getDeviceSecret(function(err, deviceSecret) {
            if (err) {
                callback(err);
                return;
            }

            // generate iv
            var iv = util.random(config.symIvSize);
            // encrypt deviceSecret
            self._crypto.encrypt(deviceSecret, regSessionKey, iv, function(err, encryptedDeviceSecret) {
                if (err) {
                    callback(err);
                    return;
                }

                // upload encryptedDeviceSecret
                self._privateKeyDao.uploadDeviceSecret({
                    userId: options.userId,
                    deviceName: devName,
                    encryptedDeviceSecret: encryptedDeviceSecret,
                    iv: iv
                }, callback);
            });
        });
Example #24
0
            crypto.deriveKey(password, salt, keySize, function(err, key) {
                expect(err).to.not.exist;
                expect(util.base642Str(key).length * 8).to.equal(keySize);

                done();
            });
describe('Private Key DAO unit tests', function() {

    var privkeyDao, authStub, pgpStub, cryptoStub, imapClientStub,
        emailAddress = '*****@*****.**',
        keyId = '12345',
        salt = util.random(appConfig.config.symKeySize),
        iv = util.random(appConfig.config.symIvSize),
        encryptedPrivateKey = util.random(1024 * 8);

    beforeEach(function() {
        authStub = sinon.createStubInstance(Auth);
        authStub.emailAddress = emailAddress;
        pgpStub = sinon.createStubInstance(PGP);
        cryptoStub = sinon.createStubInstance(Crypto);
        privkeyDao = new PrivateKey(authStub, Mailbuild, mailreader, appConfig, pgpStub, cryptoStub, axe);
        imapClientStub = sinon.createStubInstance(ImapClient);
        privkeyDao._imap = imapClientStub;
    });

    afterEach(function() {});

    describe('destroy', function() {
        it('should work', function(done) {
            privkeyDao.destroy().then(function() {
                expect(imapClientStub.logout.calledOnce).to.be.true;
                done();
            });
        });
    });

    describe('encrypt', function() {
        it('should fail due to invalid args', function(done) {
            privkeyDao.encrypt().catch(function(err) {
                expect(err.message).to.match(/Incomplete/);
                done();
            });
        });

        it('should work', function(done) {
            cryptoStub.deriveKey.returns(resolves('derivedKey'));
            pgpStub.exportKeys.returns(resolves({
                keyId: keyId,
                privateKeyArmored: 'PGP BLOCK'
            }));
            cryptoStub.encrypt.returns(resolves(encryptedPrivateKey));

            privkeyDao.encrypt('asdf').then(function(encryptedKey) {
                expect(encryptedKey._id).to.equal(keyId);
                expect(encryptedKey.encryptedPrivateKey).to.equal(encryptedPrivateKey);
                expect(encryptedKey.salt).to.exist;
                expect(encryptedKey.iv).to.exist;
                done();
            });
        });
    });

    describe('upload', function() {
        it('should fail due to invalid args', function(done) {
            privkeyDao.upload({}).catch(function(err) {
                expect(err.message).to.match(/Incomplete/);
                done();
            });
        });

        it('should work', function(done) {
            var IMAP_KEYS_FOLDER = 'openpgp_keys';
            var fullPath = 'INBOX.' + IMAP_KEYS_FOLDER;

            imapClientStub.createFolder.withArgs({
                path: IMAP_KEYS_FOLDER
            }).returns(resolves(fullPath));
            imapClientStub.uploadMessage.withArgs(sinon.match(function(arg) {
                expect(arg.path).to.equal(fullPath);
                expect(arg.message).to.exist;
                return true;
            })).returns(resolves());

            privkeyDao.upload({
                _id: keyId,
                userId: emailAddress,
                encryptedPrivateKey: encryptedPrivateKey,
                salt: salt,
                iv: iv
            }).then(function() {
                expect(imapClientStub.createFolder.calledOnce).to.be.true;
                expect(imapClientStub.uploadMessage.calledOnce).to.be.true;
                done();
            });
        });
    });

    describe('isSynced', function() {
        beforeEach(function() {
            sinon.stub(privkeyDao, '_getFolder');
            sinon.stub(privkeyDao, '_fetchMessage');
        });
        afterEach(function() {
            privkeyDao._getFolder.restore();
            privkeyDao._fetchMessage.restore();
        });

        it('should be synced', function(done) {

            privkeyDao._getFolder.returns(resolves('foo'));
            privkeyDao._fetchMessage.returns(resolves({}));

            privkeyDao.isSynced().then(function(synced) {
                expect(synced).to.be.true;
                expect(privkeyDao._getFolder.calledOnce).to.be.true;
                expect(privkeyDao._fetchMessage.calledOnce).to.be.true;

                done();
            });
        });

        it('should not be synced', function(done) {
            privkeyDao._getFolder.returns(resolves());
            privkeyDao._fetchMessage.returns(resolves());

            privkeyDao.isSynced().then(function(synced) {
                expect(synced).to.be.false;
                expect(privkeyDao._getFolder.calledOnce).to.be.true;
                expect(privkeyDao._fetchMessage.calledOnce).to.be.true;

                done();
            });
        });

        it('should not be synced in case of error', function(done) {
            privkeyDao._getFolder.returns(rejects(new Error()));

            privkeyDao.isSynced().then(function(synced) {
                expect(synced).to.be.false;
                expect(privkeyDao._getFolder.calledOnce).to.be.true;

                done();
            });
        });

        it('should not be synced in case of error', function(done) {
            privkeyDao._getFolder.returns(resolves('foo'));
            privkeyDao._fetchMessage.returns(rejects(new Error()));

            privkeyDao.isSynced().then(function(synced) {
                expect(synced).to.be.false;
                expect(privkeyDao._getFolder.calledOnce).to.be.true;
                expect(privkeyDao._fetchMessage.calledOnce).to.be.true;

                done();
            });
        });
    });

    describe('download', function() {
        var base64Content = 'AYzsvV+hGMMT4BIl/XFjbl60BaM5DpDYVNyKPnoZ4ZyW1qy1udkQR7VUeNKJw5v2gWOqc3y6KHkZIqybOVro6e8tzhK1Fvpz+rgmME0tbrrh/Dd6QMBXb9c6ZAzgbLdq0sxftqXO9GoxINAVcfGN/MkcOIhonEjIsLSaYY2WLuGOLp8ZNdgO0tPxfcdd/f1hVXH2JRYmkOwStH3y2uYDmUhEWWeLfP2vF57F4NgtK2Ln4Ypn4VDx1SWtI6E1IMpwchpwXssBwzY2uWKUPNbWEwEYDU6pleWCKphc2YBp0ohJg1HfE+Et9/8wsZtQAjTiigZuovRd5ABd6LkCCuPNenmzKvR5os8fbe9HDsAiDYl5OrA1iGTWVcAKec1OWxRWKn3Ktt/v+W39gxvmA6OOSuPkA3PF+1rY2lU05busVlNVmNmv6vY3LTJz4J/jVPP7Bn6+Wl/BwdGC7OagZCORmDUujk4AaIz5y+x/hgS6g9yY8oaY5EGdFCxRpS7aptqiBNIXIpuxGtKZpP3bmjI4pIcVb4xTA57SFTE7czfvlvTjvBSCQP7MGYCNC+SbDRgt1beyM8uUrKiuLTWK+YJ6rvcIvOIEqvUBDR7ak+9S6+fyxw033vNHfQSAagIUC1eq+c8yoUzvtSRISOMEbu7MnjI5i4AQrD5yfJDJdp5NTpZ0Dz3fW3RVmMhghTGN3ch+6vVwkzO2ik11EGTqwaLfOgZuwunEonXLT4v4fJjIFvsl+hMab0keksuW1G8AQCdkNcgDfxMTIz6S/k51yVIGE2DZo1e1LTc7pu8gOCNHtuNMuwzDTZuutWdd0P93ZL7W6j1eq33DShX2zeuxk5S28crn6DdlK5QBYMSpECU1JDKRu1QMBNtiEgGlJaVOi1AQ+cDdZKthMYfJ0MPHCeRyQFMpEYkYUBfhBMnGaiDTmDFMvEy0WGhabKChhtdBF/rlyug8Kx9M60lx1t9dYVbxSmWkqWgUZ36vRwQXPVEWWmRROHtG/V9+CSPCCa9heDDqqj8nKzL0vK9kBG8nh2XlPAVg7ICicTLw0u93pz4US3pRKwfMys1mQNV0z0k0uXB1zJZqDrIsCihcUMC2vFOXg+dNlanPXeP8AMp3ojuMPAClIt+bxTyrjZ7MV0mkDuaWUaeEq2xuaU5cKlG1Aam6vSb3jmURgEzOk1onlkGrCfVUTne18W8V6KL7iG+lX+331baiZVGoMUXT++0T05KYBdTRYL0OZ0P3OPRqilPpxaZCY0NG5rJxC5ij0vnu9ECAvN3xSdiRF7SobVSVFIdc32aY24nLKv8/gSnROgmQbAqeCMOz0bULRyVTe0lzSXBcCgu5gK+KEo8p38trTSJ/S95sKQnyNbrnz2QOIXvzxLrL6/nnC/4pwxXKZ6XqB/2zLVfiJRjUQ1NUC0xDXA==';
        var root = [{
            type: 'attachment',
            content: util.binStr2Uint8Arr(util.base642Str(base64Content))
        }];

        beforeEach(function() {
            sinon.stub(privkeyDao, '_getFolder');
            sinon.stub(privkeyDao, '_fetchMessage');
            sinon.stub(privkeyDao, '_parse');
        });
        afterEach(function() {
            privkeyDao._getFolder.restore();
            privkeyDao._fetchMessage.restore();
            privkeyDao._parse.restore();
        });

        it('should fail if key not synced', function(done) {
            privkeyDao._getFolder.returns(resolves('foo'));
            privkeyDao._fetchMessage.returns(resolves());

            privkeyDao.download({
                userId: emailAddress,
                keyId: keyId
            }).catch(function(err) {
                expect(err.message).to.match(/not synced/);
                done();
            });
        });

        it('should work', function(done) {
            privkeyDao._getFolder.returns(resolves('foo'));
            privkeyDao._fetchMessage.returns(resolves({}));
            imapClientStub.getBodyParts.returns(resolves());
            privkeyDao._parse.returns(resolves(root));

            privkeyDao.download({
                userId: emailAddress,
                keyId: keyId
            }).then(function(privkey) {
                expect(privkey._id).to.equal(keyId);
                expect(privkey.userId).to.equal(emailAddress);
                expect(privkey.encryptedPrivateKey).to.exist;
                done();
            });
        });
    });

    describe('decrypt', function() {
        it('should fail due to invalid args', function(done) {
            privkeyDao.decrypt({}).catch(function(err) {
                expect(err.message).to.match(/Incomplete/);
                done();
            });
        });

        it('should fail for invalid code', function(done) {
            cryptoStub.deriveKey.returns(resolves('derivedKey'));
            cryptoStub.decrypt.returns(rejects(new Error()));

            privkeyDao.decrypt({
                _id: keyId,
                userId: emailAddress,
                code: 'asdf',
                encryptedPrivateKey: encryptedPrivateKey,
                salt: salt,
                iv: iv
            }).catch(function(err) {
                expect(err.message).to.match(/Invalid/);
                done();
            });
        });

        it('should  fail for invalid key params', function(done) {
            cryptoStub.deriveKey.returns(resolves('derivedKey'));
            cryptoStub.decrypt.returns(resolves('PGP BLOCK'));
            pgpStub.getKeyParams.returns({
                _id: '7890',
                userId: emailAddress
            });

            privkeyDao.decrypt({
                _id: keyId,
                userId: emailAddress,
                code: 'asdf',
                encryptedPrivateKey: encryptedPrivateKey,
                salt: salt,
                iv: iv
            }).catch(function(err) {
                expect(err.message).to.match(/key parameters/);
                done();
            });
        });

        it('should work', function(done) {
            cryptoStub.deriveKey.returns(resolves('derivedKey'));
            cryptoStub.decrypt.returns(resolves('PGP BLOCK'));
            pgpStub.getKeyParams.returns({
                _id: keyId,
                userId: emailAddress
            });

            privkeyDao.decrypt({
                _id: keyId,
                userId: emailAddress,
                code: 'asdf',
                encryptedPrivateKey: encryptedPrivateKey,
                salt: salt,
                iv: iv
            }).then(function(privkey) {
                expect(privkey._id).to.equal(keyId);
                expect(privkey.userId).to.equal(emailAddress);
                expect(privkey.encryptedKey).to.equal('PGP BLOCK');
                done();
            });
        });
    });

    describe('_getFolder', function() {
        it('should fail if imap folder does not exist', function(done) {
            imapClientStub.listWellKnownFolders.returns(resolves({
                Inbox: [{
                    path: 'INBOX'
                }],
                Other: [{
                    path: 'foo'
                }]
            }));

            privkeyDao._getFolder().catch(function(err) {
                expect(err.message).to.match(/Imap folder/);
                expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
                done();
            });
        });

        it('should work', function(done) {
            imapClientStub.listWellKnownFolders.returns(resolves({
                Inbox: [{
                    path: 'INBOX'
                }],
                Other: [{
                    path: 'openpgp_keys'
                }]
            }));

            privkeyDao._getFolder().then(function(path) {
                expect(path).to.equal('openpgp_keys');
                expect(imapClientStub.listWellKnownFolders.calledOnce).to.be.true;
                done();
            });
        });
    });

    describe('_fetchMessage', function() {
        it('should fail due to invalid args', function(done) {
            privkeyDao._fetchMessage({}).catch(function(err) {
                expect(err.message).to.match(/Incomplete/);
                done();
            });
        });


        it('should work', function(done) {
            imapClientStub.listMessages.returns(resolves([{
                subject: keyId
            }]));

            privkeyDao._fetchMessage({
                userId: emailAddress,
                keyId: keyId
            }).then(function(msg) {
                expect(msg.subject).to.equal(keyId);
                expect(imapClientStub.listMessages.calledOnce).to.be.true;
                done();
            });
        });

        it('should work with path prefix', function(done) {
            imapClientStub.listMessages.returns(resolves([{
                subject: keyId
            }]));

            privkeyDao._fetchMessage({
                userId: emailAddress,
                keyId: keyId
            }).then(function(msg) {
                expect(msg.subject).to.equal(keyId);
                expect(imapClientStub.listMessages.calledOnce).to.be.true;
                done();
            });
        });

        it('should work for not matching message', function(done) {
            imapClientStub.listMessages.returns(resolves([{
                subject: '7890'
            }]));

            privkeyDao._fetchMessage({
                userId: emailAddress,
                keyId: keyId
            }).then(function(msg) {
                expect(msg).to.not.exist;
                done();
            });
        });

        it('should work for no messages', function(done) {
            imapClientStub.listMessages.returns(resolves([]));

            privkeyDao._fetchMessage({
                userId: emailAddress,
                keyId: keyId
            }).then(function(msg) {
                expect(msg).to.not.exist;
                done();
            });
        });
    });

    describe('_parse', function() {
        var root = {
            foo: 'bar'
        };

        beforeEach(function() {
            sinon.stub(mailreader, 'parse');
        });
        afterEach(function() {
            mailreader.parse.restore();
        });

        it('should fail', function(done) {
            mailreader.parse.yields(new Error('asdf'));

            privkeyDao._parse().catch(function(err) {
                expect(err.message).to.match(/asdf/);
                done();
            });
        });

        it('should work', function(done) {
            mailreader.parse.yields(null, root);

            privkeyDao._parse().then(function(res) {
                expect(res).to.equal(root);
                done();
            });
        });
    });

});
Example #26
0
 function check(recipient) {
     if (util.validateEmailAddress(recipient.address) && !recipient.secure && $scope.invited.indexOf(recipient.address) === -1) {
         invitees.push(recipient.address);
     }
 }
Example #27
0
var LoginPrivateKeyUploadCtrl = function($scope, $location, $routeParams, $q, auth, privateKey) {
    !$routeParams.dev && !auth.isInitialized() && $location.path('/'); // init app

    //
    // scope state
    //

    // go to step 1
    $scope.step = 1;
    // generate new code for the user
    $scope.code = util.randomString(24);
    $scope.displayedCode = $scope.code.replace(/.{4}/g, "$&-").replace(/-$/, '');
    // clear input field of any previous artifacts
    $scope.inputCode = '';

    //
    // scope functions
    //

    $scope.encryptAndUploadKey = function() {
        return $q(function(resolve) {
            $scope.busy = true;
            $scope.errMsg = undefined;
            $scope.incorrect = false;
            resolve();

        }).then(function() {
            if ($scope.inputCode.toUpperCase() !== $scope.code) {
                throw new Error('The code does not match. Please go back and check the generated code.');
            }

        }).then(function() {
            // login to imap
            return privateKey.init();

        }).then(function() {
            // encrypt the private key
            return privateKey.encrypt($scope.code);

        }).then(function(encryptedPayload) {
            // set user id to encrypted payload
            encryptedPayload.userId = auth.emailAddress;

            // encrypt private PGP key using code and upload
            return privateKey.upload(encryptedPayload);

        }).then(function() {
            // logout of imap
            return privateKey.destroy();

        }).then(function() {
            // continue to public key verification
            $location.path('/login-verify-public-key');

        }).catch(displayError);
    };

    $scope.goForward = function() {
        $scope.step++;
    };

    $scope.goBack = function() {
        if ($scope.step > 1) {
            $scope.step--;
        }
    };

    //
    // helper functions
    //

    function displayError(err) {
        $scope.busy = false;
        $scope.incorrect = true;
        $scope.errMsg = err.errMsg || err.message;
    }
};