Compare commits

...

18 Commits

Author SHA1 Message Date
Tankred Hase 43729833a5 Merge pull request #412 from tanx/master
Minor fixes to master
2015-12-17 16:13:36 +07:00
Tankred Hase b05aeea342 Remove gitter link 2015-12-17 16:02:55 +07:00
Tankred Hase 137c8c7c24 Update links in readme 2015-12-17 16:00:49 +07:00
Tankred Hase 853be194d9 Fix dompurify build and upgrade to new version 2015-12-17 12:12:38 +07:00
Tankred Hase 8b36a719c3 Use faster container based travis builds, remove whiteout build email notification from travis 2015-12-14 10:30:25 +07:00
Tankred Hase 8165416c5d Rename repo to mail 2015-12-14 10:25:58 +07:00
Tankred Hase b375d81635 Update git repo name in readme 2015-12-11 19:10:10 +07:00
Felix Hammerl e56f8c2c28 Merge pull request #394 from whiteout-io/dev/WO-1026
[WO-1026] Fix broken key upload after mail server error
2015-08-19 12:36:59 +02:00
Felix Hammerl ad3691fae9 [WO-1026] Fix broken key upload after mail server error 2015-08-19 12:10:16 +02:00
Tankred Hase e0663ab8d8 Remove winstore-jscompat (not necessary in Windows 10 anymore) 2015-08-05 14:12:13 +02:00
Tankred Hase 263b5c13b0 Upgrade to grunt-mocha-phantomjs 0.7.0 2015-08-05 14:11:25 +02:00
Tankred Hase 8636ba201b Properly mock publickey requests 2015-08-05 14:10:29 +02:00
Tankred Hase aa24881efc Fix JS build by fixating transitive browserify dep 2015-07-10 12:39:47 +02:00
Tankred Hase b7b5c1bdf5 Update README.md 2015-07-08 17:55:05 +02:00
Tankred Hase 59d38f9b14 Bump mobile version code 2015-06-26 11:28:25 +02:00
Tankred Hase c44984b2f3 Bump mobile version code 2015-06-18 11:58:57 +02:00
Felix Hammerl c31c320e83 Merge pull request #381 from whiteout-io/WO-1000
Remove invite code from app
2015-06-18 11:52:27 +02:00
Tankred Hase 7f49e691db Remove invite code from app 2015-06-18 10:45:18 +02:00
12 changed files with 88 additions and 235 deletions

View File

@ -1,10 +1,10 @@
branch-defaults:
release/prod:
environment: mail-html5-prod
environment: mail-prod
release/test:
environment: mail-html5-test
environment: mail-test
global:
application_name: mail-html5
application_name: mail
default_ec2_keyname: null
default_platform: Node.js
default_region: eu-central-1

View File

@ -1,9 +1,7 @@
sudo: false
language: node_js
node_js:
- "0.12"
before_install:
- gem install sass
- npm install -g grunt-cli
notifications:
email:
- build@whiteout.io

View File

@ -282,7 +282,6 @@ module.exports = function(grunt) {
},
app: {
src: [
'src/lib/winstore-jscompat.js',
'src/lib/underscore/underscore.js',
'node_modules/jquery/dist/jquery.min.js',
'src/lib/angular/angular.js',
@ -308,7 +307,7 @@ module.exports = function(grunt) {
},
readSandbox: {
src: [
'node_modules/dompurify/purify.js',
'node_modules/dompurify/src/purify.js',
'node_modules/iframe-resizer/js/iframeResizer.contentWindow.min.js',
'src/js/controller/app/read-sandbox.js'
],

View File

@ -1,4 +1,4 @@
Whiteout Mail [![Build Status](https://travis-ci.org/whiteout-io/mail-html5.svg?branch=master)](https://travis-ci.org/whiteout-io/mail-html5) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/whiteout-io/mail-html5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Whiteout Mail [![Build Status](https://travis-ci.org/whiteout-io/mail.svg?branch=master)](https://travis-ci.org/whiteout-io/mail)
==========
Whiteout Mail is an easy to use email client with integrated OpenPGP encryption written in pure JavaScript. Download the official version under [whiteout.io](https://whiteout.io/#product).
@ -7,15 +7,17 @@ Whiteout Mail is an easy to use email client with integrated OpenPGP encryption
### Features
You can read about product features and our future roadmap in our [FAQ](https://github.com/whiteout-io/mail-html5/wiki/FAQ).
You can read about product features and our future roadmap in our [FAQ](https://github.com/whiteout-io/mail/wiki/FAQ).
### Privacy and Security
We take the privacy of your data very seriously. Here are some of the technical details:
* The code has undergone a [full security audit](https://blog.whiteout.io/2015/06/11/whiteout-mail-1-0-and-security-audit-by-cure53/) by [Cure53](https://cure53.de).
* Messages are [encrypted end-to-end ](http://en.wikipedia.org/wiki/End-to-end_encryption) using the [OpenPGP](http://en.wikipedia.org/wiki/Pretty_Good_Privacy) standard. This means that only you and the recipient can read your mail. Your messages and private PGP key are stored only on your computer (in IndexedDB).
* Users have the option to use [encrypted private key sync](https://github.com/whiteout-io/mail-html5/wiki/Secure-OpenPGP-Key-Pair-Synchronization-via-IMAP) if they want to use Whiteout on multiple devices.
* Users have the option to use [encrypted private key sync](https://github.com/whiteout-io/mail/wiki/Secure-OpenPGP-Key-Pair-Synchronization-via-IMAP) if they want to use Whiteout on multiple devices.
* [Content Security Policy (CSP)](http://www.html5rocks.com/en/tutorials/security/content-security-policy/) is enforced to prevent injection attacks.
@ -25,7 +27,7 @@ We take the privacy of your data very seriously. Here are some of the technical
* Like most native email clients, whiteout mail uses raw [TCP sockets](http://developer.chrome.com/apps/socket.html) to communicate directly with your mail server via IMAP/SMTP. TLS is used to protect your password and message data in transit.
* The app is deployed as a signed [Chrome Packaged App](https://developer.chrome.com/apps/about_apps.html) with [auditable static versions](https://github.com/whiteout-io/mail-html5/releases) in order to prevent [problems with host-based security](https://blog.whiteout.io/2014/04/13/heartbleed-and-javascript-crypto/).
* The app is deployed as a signed [Chrome Packaged App](https://developer.chrome.com/apps/about_apps.html) with [auditable static versions](https://github.com/whiteout-io/mail/releases) in order to prevent [problems with host-based security](https://blog.whiteout.io/2014/04/13/heartbleed-and-javascript-crypto/).
* The app can also be used from any modern web browser in environments where installing an app is not possible (e.g. a locked down corporate desktop). The IMAP/SMTP TLS sessions are still terminated in the user's browser using JS crypto ([Forge](https://github.com/digitalbazaar/forge)), but the encrypted TLS payload is proxied via [socket.io](http://socket.io/), due to the lack of raw sockets in the browser. **Please keep in mind that this mode of operation is not as secure as using the signed packaged app, since users must trust the webserver to deliver the correct code. This mode will still protect user against passive attacks like wiretapping (since PGP and TLS are still applied in the user's browser), but not against active attacks from the webserver. So it's best to decide which threat model applies to you.**
@ -37,11 +39,11 @@ We take the privacy of your data very seriously. Here are some of the technical
* We will launch a bug bounty program later on for independent security researchers. If you find any security vulnerabilities, don't hesitate to contact us [security@whiteout.io](mailto:security@whiteout.io).
* You can also just create an [issue](https://github.com/whiteout-io/mail-html5/issues) on GitHub if you're missing a feature or just want to give us feedback. It would be much appreciated!
* You can also just create an [issue](https://github.com/whiteout-io/mail/issues) on GitHub if you're missing a feature or just want to give us feedback. It would be much appreciated!
### Testing
You can download a prebuilt bundle under [releases](https://github.com/whiteout-io/mail-html5/releases) or build your own from source (requires [node.js](http://nodejs.org/download/), [grunt](http://gruntjs.com/getting-started#installing-the-cli) and [sass](http://sass-lang.com/install)):
You can download a prebuilt bundle under [releases](https://github.com/whiteout-io/mail/releases) or build your own from source (requires [node.js](http://nodejs.org/download/), [grunt](http://gruntjs.com/getting-started#installing-the-cli) and [sass](http://sass-lang.com/install)):
npm install && npm test
@ -69,7 +71,7 @@ The App can be used either as a Chrome Packaged App or just by hosting it on you
Clone the git repository
git clone https://github.com/whiteout-io/mail-html5.git
git clone https://github.com/whiteout-io/mail.git
Build and generate the `dist/` directory:

View File

@ -3,21 +3,6 @@
"description": "Mail App with integrated OpenPGP encryption.",
"author": "Whiteout Networks",
"homepage": "https://whiteout.io",
"repository": {
"type": "git",
"url": "https://github.com/whiteout-io/mail-html5.git"
},
"keywords": [
"email",
"mail",
"client",
"app",
"openpgp",
"pgp",
"gpg",
"imap",
"smtp"
],
"engines": {
"node": ">=0.10"
},
@ -40,11 +25,12 @@
"browsersmtp": "https://github.com/whiteout-io/browsersmtp/tarball/master",
"chai": "~1.9.2",
"crypto-lib": "~0.2.1",
"dompurify": "~0.6.3",
"dompurify": "~0.7.3",
"grunt": "~0.4.1",
"grunt-angular-templates": "~0.5.7",
"grunt-autoprefixer": "~0.7.2",
"grunt-browserify": "~3.7.0",
"grunt-browserify": "3.7.0",
"insert-module-globals": "6.5.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.5.2",
"grunt-contrib-concat": "^0.5.0",
@ -57,7 +43,7 @@
"grunt-csso": "~0.6.1",
"grunt-exorcise": "^0.2.0",
"grunt-manifest": "^0.4.0",
"grunt-mocha-phantomjs": "^0.6.0",
"grunt-mocha-phantomjs": "^0.7.0",
"grunt-shell": "~1.1.1",
"grunt-string-replace": "~1.0.0",
"grunt-svgmin": "~1.0.0",
@ -79,4 +65,4 @@
"time-grunt": "^1.0.0",
"wo-smtpclient": "~0.6.0"
}
}
}

View File

@ -52,8 +52,7 @@ var CreateAccountCtrl = function($scope, $location, $routeParams, $q, auth, admi
return admin.createUser({
emailAddress: emailAddress,
password: $scope.pass,
phone: phone.internationalNumber,
betaCode: $scope.betaCode.toUpperCase()
phone: phone.internationalNumber
});
}).then(function() {

View File

@ -102,24 +102,39 @@ PrivateKey.prototype.upload = function(options) {
return new Promise(function(resolve) {
if (!options._id || !options.userId || !options.encryptedPrivateKey || !options.salt || !options.iv) {
throw new Error('Incomplete arguments!');
throw new Error('Incomplete arguments for key upload!');
}
resolve();
}).then(function() {
// create imap folder
return self._imap.createFolder({
path: IMAP_KEYS_FOLDER
}).then(function(fullPath) {
// Some servers (Exchange, Cyrus) error when creating an existing IMAP mailbox instead of
// responding with ALREADYEXISTS. Hence we search for the folder before uploading.
self._axe.debug('Searching imap folder for key upload...');
return self._getFolder().then(function(fullPath) {
path = fullPath;
self._axe.debug('Successfully created imap folder ' + path);
}).catch(function(err) {
var prettyErr = new Error('Creating imap folder ' + IMAP_KEYS_FOLDER + ' failed: ' + err.message);
self._axe.error(prettyErr);
throw prettyErr;
}).catch(function() {
// create imap folder
self._axe.debug('Folder not found, creating imap folder.');
return self._imap.createFolder({
path: IMAP_KEYS_FOLDER
}).then(function(fullPath) {
path = fullPath;
self._axe.debug('Successfully created imap folder ' + path);
}).catch(function(err) {
var prettyErr = new Error('Creating imap folder ' + IMAP_KEYS_FOLDER + ' failed: ' + err.message);
self._axe.error(prettyErr);
throw prettyErr;
});
});
}).then(createMessage).then(function(message) {
// upload to imap folder
self._axe.debug('Uploading key...');
return self._imap.uploadMessage({
path: path,
message: message
@ -380,4 +395,4 @@ function filterBodyParts(bodyParts, type, result) {
}
});
return result;
}
}

View File

@ -1,174 +0,0 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// JavaScript Dynamic Content shim for Windows Store apps
(function () {
if (window.MSApp && MSApp.execUnsafeLocalFunction) {
// Some nodes will have an "attributes" property which shadows the Node.prototype.attributes property
// and means we don't actually see the attributes of the Node (interestingly the VS debug console
// appears to suffer from the same issue).
//
var Element_setAttribute = Object.getOwnPropertyDescriptor(Element.prototype, "setAttribute").value;
var Element_removeAttribute = Object.getOwnPropertyDescriptor(Element.prototype, "removeAttribute").value;
var HTMLElement_insertAdjacentHTMLPropertyDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "insertAdjacentHTML");
var Node_get_attributes = Object.getOwnPropertyDescriptor(Node.prototype, "attributes").get;
var Node_get_childNodes = Object.getOwnPropertyDescriptor(Node.prototype, "childNodes").get;
var detectionDiv = document.createElement("div");
function getAttributes(element) {
return Node_get_attributes.call(element);
}
function setAttribute(element, attribute, value) {
try {
Element_setAttribute.call(element, attribute, value);
} catch (e) {
// ignore
}
}
function removeAttribute(element, attribute) {
Element_removeAttribute.call(element, attribute);
}
function childNodes(element) {
return Node_get_childNodes.call(element);
}
function empty(element) {
while (element.childNodes.length) {
element.removeChild(element.lastChild);
}
}
function insertAdjacentHTML(element, position, html) {
HTMLElement_insertAdjacentHTMLPropertyDescriptor.value.call(element, position, html);
}
function inUnsafeMode() {
var isUnsafe = true;
try {
detectionDiv.innerHTML = "<test/>";
}
catch (ex) {
isUnsafe = false;
}
return isUnsafe;
}
function cleanse(html, targetElement) {
var cleaner = document.implementation.createHTMLDocument("cleaner");
empty(cleaner.documentElement);
MSApp.execUnsafeLocalFunction(function () {
insertAdjacentHTML(cleaner.documentElement, "afterbegin", html);
});
var scripts = cleaner.documentElement.querySelectorAll("script");
Array.prototype.forEach.call(scripts, function (script) {
switch (script.type.toLowerCase()) {
case "":
script.type = "text/inert";
break;
case "text/javascript":
case "text/ecmascript":
case "text/x-javascript":
case "text/jscript":
case "text/livescript":
case "text/javascript1.1":
case "text/javascript1.2":
case "text/javascript1.3":
script.type = "text/inert-" + script.type.slice("text/".length);
break;
case "application/javascript":
case "application/ecmascript":
case "application/x-javascript":
script.type = "application/inert-" + script.type.slice("application/".length);
break;
default:
break;
}
});
function cleanseAttributes(element) {
var attributes = getAttributes(element);
if (attributes && attributes.length) {
// because the attributes collection is live it is simpler to queue up the renames
var events;
for (var i = 0, len = attributes.length; i < len; i++) {
var attribute = attributes[i];
var name = attribute.name;
if ((name[0] === "o" || name[0] === "O") &&
(name[1] === "n" || name[1] === "N")) {
events = events || [];
events.push({ name: attribute.name, value: attribute.value });
}
}
if (events) {
for (var i = 0, len = events.length; i < len; i++) {
var attribute = events[i];
removeAttribute(element, attribute.name);
setAttribute(element, "x-" + attribute.name, attribute.value);
}
}
}
var children = childNodes(element);
for (var i = 0, len = children.length; i < len; i++) {
cleanseAttributes(children[i]);
}
}
cleanseAttributes(cleaner.documentElement);
var cleanedNodes = [];
if (targetElement.tagName === 'HTML') {
cleanedNodes = Array.prototype.slice.call(document.adoptNode(cleaner.documentElement).childNodes);
} else {
if (cleaner.head) {
cleanedNodes = cleanedNodes.concat(Array.prototype.slice.call(document.adoptNode(cleaner.head).childNodes));
}
if (cleaner.body) {
cleanedNodes = cleanedNodes.concat(Array.prototype.slice.call(document.adoptNode(cleaner.body).childNodes));
}
}
return cleanedNodes;
}
function cleansePropertySetter(property, setter) {
var propertyDescriptor = Object.getOwnPropertyDescriptor(HTMLElement.prototype, property);
var originalSetter = propertyDescriptor.set;
Object.defineProperty(HTMLElement.prototype, property, {
get: propertyDescriptor.get,
set: function (value) {
if(window.WinJS && window.WinJS._execUnsafe && inUnsafeMode()) {
originalSetter.call(this, value);
} else {
var that = this;
var nodes = cleanse(value, that);
MSApp.execUnsafeLocalFunction(function () {
setter(propertyDescriptor, that, nodes);
});
}
},
enumerable: propertyDescriptor.enumerable,
configurable: propertyDescriptor.configurable,
});
}
cleansePropertySetter("innerHTML", function (propertyDescriptor, target, elements) {
empty(target);
for (var i = 0, len = elements.length; i < len; i++) {
target.appendChild(elements[i]);
}
});
cleansePropertySetter("outerHTML", function (propertyDescriptor, target, elements) {
for (var i = 0, len = elements.length; i < len; i++) {
target.insertAdjacentElement("afterend", elements[i]);
}
target.parentNode.removeChild(target);
});
}
}());

View File

@ -1,6 +1,6 @@
{
"packageId": "io.whiteout.WhiteoutMail",
"versionCode": 26,
"versionCode": 28,
"CFBundleVersion": "1",
"ios": {

View File

@ -287,9 +287,6 @@
<input class="input-text" type="tel" ng-model="dial" required placeholder="Mobile phone (for SMS validation)" tabindex="5">
</div>
</div>
<div class="form__row">
<input class="input-text" type="text" ng-model="betaCode" required placeholder="Invitation code" tabindex="6">
</div>
<div class="spinner-block" ng-show="busy">
<span class="spinner spinner--big"></span>
</div>
@ -297,9 +294,6 @@
<button class="btn" type="submit" ng-click="showConfirm()">Create</button>
</div>
</form>
<p class="typo-paragraph">
<a href="http://eepurl.com/ba09in" target="_blank" title="Sign up for mailbox access.">Need an invitation code?</a>
</p>
</main>
<div ng-include="'tpl/page-footer.html'"></div>
</div>

View File

@ -249,6 +249,7 @@ describe('Email DAO integration tests', function() {
function onCleaned() {
userStorage = accountService._accountStore;
auth = accountService._auth;
emailDao = accountService._emailDao;
auth.setCredentials({
emailAddress: testAccount.user,
@ -257,19 +258,17 @@ describe('Email DAO integration tests', function() {
imap: {} // a preconfigured smtpclient with mocked tcp sockets
});
// stub rest request to key server
sinon.stub(emailDao._keychain._publicKeyDao, 'get').returns(resolves(mockKeyPair.publicKey));
sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').returns(resolves(mockKeyPair.publicKey));
auth.init().then(function() {
accountService.init({
emailAddress: testAccount.user
}).then(function() {
emailDao = accountService._emailDao;
// retrieve the pgpbuilder from the emaildao and initialize the pgpmailer with the existing pgpbuilder
pgpMailer = new PgpMailer({}, emailDao._pgpbuilder);
// stub rest request to key server
sinon.stub(emailDao._keychain._publicKeyDao, 'get').returns(resolves(mockKeyPair.publicKey));
sinon.stub(emailDao._keychain._publicKeyDao, 'getByUserId').returns(resolves(mockKeyPair.publicKey));
emailDao.unlock({
passphrase: testAccount.pass,
keypair: mockKeyPair

View File

@ -68,6 +68,14 @@ describe('Private Key DAO unit tests', function() {
});
describe('upload', function() {
beforeEach(function() {
sinon.stub(privkeyDao, '_getFolder');
});
afterEach(function() {
privkeyDao._getFolder.restore();
});
it('should fail due to invalid args', function(done) {
privkeyDao.upload({}).catch(function(err) {
expect(err.message).to.match(/Incomplete/);
@ -75,10 +83,11 @@ describe('Private Key DAO unit tests', function() {
});
});
it('should work', function(done) {
it('should work without existing folder', function(done) {
var IMAP_KEYS_FOLDER = 'openpgp_keys';
var fullPath = 'INBOX.' + IMAP_KEYS_FOLDER;
privkeyDao._getFolder.returns(rejects(new Error()));
imapClientStub.createFolder.withArgs({
path: IMAP_KEYS_FOLDER
}).returns(resolves(fullPath));
@ -95,11 +104,37 @@ describe('Private Key DAO unit tests', function() {
salt: salt,
iv: iv
}).then(function() {
expect(privkeyDao._getFolder.calledOnce).to.be.true;
expect(imapClientStub.createFolder.calledOnce).to.be.true;
expect(imapClientStub.uploadMessage.calledOnce).to.be.true;
done();
});
});
it('should work with existing folder', function(done) {
var IMAP_KEYS_FOLDER = 'openpgp_keys';
var fullPath = 'INBOX.' + IMAP_KEYS_FOLDER;
privkeyDao._getFolder.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(privkeyDao._getFolder.calledOnce).to.be.true;
expect(imapClientStub.createFolder.called).to.be.false;
expect(imapClientStub.uploadMessage.calledOnce).to.be.true;
done();
});
});
});
describe('isSynced', function() {