diff --git a/.gitignore b/.gitignore
index 7b35202b..90e9c209 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
resources/openpgpjs.pem
build/
.DS_Store
+node_modules
+test/integration/lib
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..f5632394
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+ - "0.10"
+before_install:
+ - npm install -g grunt-cli
\ No newline at end of file
diff --git a/Gruntfile.js b/Gruntfile.js
new file mode 100644
index 00000000..1909a371
--- /dev/null
+++ b/Gruntfile.js
@@ -0,0 +1,52 @@
+module.exports = function(grunt) {
+ 'use strict';
+
+ // Project configuration.
+ grunt.initConfig({
+ connect: {
+ dev: {
+ options: {
+ port: 8680,
+ base: '.',
+ keepalive: true
+ }
+ },
+ test: {
+ options: {
+ port: 8681,
+ base: '.'
+ }
+ }
+ },
+
+ mocha: {
+ all: {
+ options: {
+ urls: ['http://localhost:<%= connect.test.options.port %>/test/integration/index.html'],
+ run: false,
+ reporter: 'Spec'
+ }
+ }
+ },
+
+ copy: {
+ npm: {
+ expand: true,
+ flatten: true,
+ cwd: 'node_modules/',
+ src: ['requirejs/require.js', 'mocha/mocha.css', 'mocha/mocha.js', 'chai/chai.js', 'sinon/pkg/sinon.js'],
+ dest: 'test/integration/lib/'
+ }
+ }
+ });
+
+ // Load the plugin(s)
+ grunt.loadNpmTasks('grunt-contrib-copy');
+ grunt.loadNpmTasks('grunt-contrib-connect');
+ grunt.loadNpmTasks('grunt-mocha');
+
+ // Test/Dev tasks
+ grunt.registerTask('dev', ['connect:dev']);
+ grunt.registerTask('test', ['copy', 'connect:test', 'mocha']);
+
+};
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..38b4abf2
--- /dev/null
+++ b/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "openpgpjs",
+ "version": "0.1.0-dev",
+ "engines": {
+ "node": ">=0.8"
+ },
+ "scripts": {
+ "pretest": "make minify",
+ "test": "grunt test",
+ "start": "grunt dev"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "grunt": "0.4.1",
+ "mocha": "1.13.0",
+ "phantomjs": "1.9.1-9",
+ "requirejs": "2.1.8",
+ "chai": "1.7.2",
+ "sinon": "1.7.3",
+ "phantomjs": "1.9.1-9",
+ "grunt-contrib-connect": "0.5.0",
+ "grunt-contrib-copy": "0.4.1",
+ "grunt-mocha": "0.4.1"
+ }
+}
\ No newline at end of file
diff --git a/test/integration/index.html b/test/integration/index.html
new file mode 100644
index 00000000..ca87d20d
--- /dev/null
+++ b/test/integration/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+ JavaScript Unit Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/integration/main.js b/test/integration/main.js
new file mode 100644
index 00000000..f0703f35
--- /dev/null
+++ b/test/integration/main.js
@@ -0,0 +1,25 @@
+'use strict';
+
+// config require.js
+require.config({
+ baseUrl: './',
+ paths: {
+ openpgp: '../../resources/openpgp.min'
+ },
+ shim: {
+ openpgp: {
+ exports: 'window'
+ }
+ }
+});
+
+// start mocha tests
+mocha.setup('bdd');
+require(
+ [
+ 'pgp-test'
+ ], function() {
+ // require modules loaded -> run tests
+ mocha.run();
+ }
+);
\ No newline at end of file
diff --git a/test/integration/pgp-test.js b/test/integration/pgp-test.js
new file mode 100644
index 00000000..1b115f2e
--- /dev/null
+++ b/test/integration/pgp-test.js
@@ -0,0 +1,159 @@
+define(function(require) {
+ 'use strict';
+
+ var PGP = require('pgp'),
+ expect = chai.expect;
+
+ describe('PGP Crypto Api unit tests', function() {
+ var pgp,
+ user = 'test@t-online.de',
+ passphrase = 'asdf',
+ keySize = 512,
+ keyId = 'F6F60E9B42CDFF4C',
+ pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n' +
+ 'Version: OpenPGP.js v.1.20131011\n' +
+ 'Comment: http://openpgpjs.org\n' +
+ '\n' +
+ 'xk0EUlhMvAEB/2MZtCUOAYvyLFjDp3OBMGn3Ev8FwjzyPbIF0JUw+L7y2XR5\n' +
+ 'RVGvbK88unV3cU/1tOYdNsXI6pSp/Ztjyv7vbBUAEQEAAc0pV2hpdGVvdXQg\n' +
+ 'VXNlciA8d2hpdGVvdXQudGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhM\n' +
+ 'vQkQ9vYOm0LN/0wAAAW4Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXq\n' +
+ 'IiN602mWrkd8jcEzLsW5IUNzVPLhrFIuKyBDTpLnC07Loce1\n' +
+ '=6XMW\n' +
+ '-----END PGP PUBLIC KEY BLOCK-----',
+ privkey = '-----BEGIN PGP PRIVATE KEY BLOCK-----\n' +
+ 'Version: OpenPGP.js v.1.20131011\n' +
+ 'Comment: http://openpgpjs.org\n' +
+ '\n' +
+ 'xcBeBFJYTLwBAf9jGbQlDgGL8ixYw6dzgTBp9xL/BcI88j2yBdCVMPi+8tl0\n' +
+ 'eUVRr2yvPLp1d3FP9bTmHTbFyOqUqf2bY8r+72wVABEBAAH+AwMIhNB4ivtv\n' +
+ 'Y2xg6VeMcjjHxZayESHACV+nQx5Tx6ev6xzIF1Qh72fNPDppLhFSFOuTTMsU\n' +
+ 'kTN4c+BVYt29spH+cA1jcDAxQ2ULrNAXo+hheOqhpedTs8aCbcLFkJAS16hk\n' +
+ 'YSk4OnJgp/z24rVju1SHRSFbgundPzmNgXeX9e8IkviGhhQ11Wc5YwVkx03t\n' +
+ 'Z3MdDMF0jyhopbPIoBdyJB0dhvBh98w3JmwpYh9wjUA9MBHD1tvHpRmSZ3BM\n' +
+ 'UCmATn2ZLWBRWiYqFbgDnL1GM80pV2hpdGVvdXQgVXNlciA8d2hpdGVvdXQu\n' +
+ 'dGVzdEB0LW9ubGluZS5kZT7CXAQQAQgAEAUCUlhMvQkQ9vYOm0LN/0wAAAW4\n' +
+ 'Af9C+kYW1AvNWmivdtr0M0iYCUjM9DNOQH1fcvXqIiN602mWrkd8jcEzLsW5\n' +
+ 'IUNzVPLhrFIuKyBDTpLnC07Loce1\n' +
+ '=ULta\n' +
+ '-----END PGP PRIVATE KEY BLOCK-----';
+
+ beforeEach(function() {
+ pgp = new PGP();
+ });
+
+ afterEach(function() {});
+
+ describe('Generate key pair', function() {
+ it('should fail', function(done) {
+ pgp.generateKeys({
+ emailAddress: 'test@t-onlinede',
+ keySize: keySize,
+ passphrase: passphrase
+ }, function(err, keys) {
+ expect(err).to.exist;
+ expect(keys).to.not.exist;
+ done();
+ });
+ });
+ it('should fail', function(done) {
+ pgp.generateKeys({
+ emailAddress: 'testt-online.de',
+ keySize: keySize,
+ passphrase: passphrase
+ }, function(err, keys) {
+ expect(err).to.exist;
+ expect(keys).to.not.exist;
+ done();
+ });
+ });
+ it('should work', function(done) {
+ pgp.generateKeys({
+ emailAddress: user,
+ keySize: keySize,
+ passphrase: passphrase
+ }, function(err, keys) {
+ expect(err).to.not.exist;
+ expect(keys.keyId).to.exist;
+ expect(keys.privateKeyArmored).to.exist;
+ expect(keys.publicKeyArmored).to.exist;
+ done();
+ });
+ });
+ });
+
+ describe('Import/Export key pair', function() {
+ it('should fail', function(done) {
+ pgp.importKeys({
+ passphrase: 'asd',
+ privateKeyArmored: privkey,
+ publicKeyArmored: pubkey
+ }, function(err) {
+ expect(err).to.exist;
+
+ pgp.exportKeys(function(err, keys) {
+ expect(err).to.exist;
+ expect(keys).to.not.exist;
+ done();
+ });
+ });
+ });
+ it('should work', function(done) {
+ pgp.importKeys({
+ passphrase: passphrase,
+ privateKeyArmored: privkey,
+ publicKeyArmored: pubkey
+ }, function(err) {
+ expect(err).to.not.exist;
+
+ pgp.exportKeys(function(err, keys) {
+ expect(err).to.not.exist;
+ expect(keys.keyId).to.equal(keyId);
+ expect(keys.privateKeyArmored).to.equal(privkey);
+ expect(keys.publicKeyArmored).to.equal(pubkey);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('Encryption', function() {
+ var message = 'Hello, World!',
+ ciphertext;
+
+ beforeEach(function(done) {
+ pgp.importKeys({
+ passphrase: passphrase,
+ privateKeyArmored: privkey,
+ publicKeyArmored: pubkey
+ }, function(err) {
+ expect(err).to.not.exist;
+ done();
+ });
+ });
+
+ describe('Encrypt', function() {
+ it('should work', function(done) {
+ pgp.encrypt(message, [pubkey], function(err, ct) {
+ expect(err).to.not.exist;
+ expect(ct).to.exist;
+ ciphertext = ct;
+ done();
+ });
+ });
+ });
+
+ describe('Decrypt', function() {
+ it('should work', function(done) {
+ pgp.decrypt(ciphertext, pubkey, function(err, pt) {
+ expect(err).to.not.exist;
+ expect(pt).to.equal(message);
+ done();
+ });
+ });
+ });
+
+ });
+
+ });
+});
\ No newline at end of file
diff --git a/test/integration/pgp.js b/test/integration/pgp.js
new file mode 100644
index 00000000..8640e189
--- /dev/null
+++ b/test/integration/pgp.js
@@ -0,0 +1,173 @@
+/**
+ * High level crypto api that handles all calls to OpenPGP.js
+ */
+define(function(require) {
+ 'use strict';
+
+ var openpgp = require('openpgp').openpgp,
+ util = require('openpgp').util;
+
+ var PGP = function() {
+ openpgp.init();
+ };
+
+ /**
+ * Generate a key pair for the user
+ */
+ PGP.prototype.generateKeys = function(options, callback) {
+ var keys, userId;
+
+ if (!util.emailRegEx.test(options.emailAddress) || !options.keySize || typeof options.passphrase !== 'string') {
+ callback({
+ errMsg: 'Crypto init failed. Not all options set!'
+ });
+ return;
+ }
+
+ // generate keypair (keytype 1=RSA)
+ try {
+ userId = 'Whiteout User <' + options.emailAddress + '>';
+ keys = openpgp.generate_key_pair(1, options.keySize, userId, options.passphrase);
+ } catch (e) {
+ callback({
+ errMsg: 'Keygeneration failed!',
+ err: e
+ });
+ return;
+ }
+
+ callback(null, {
+ keyId: util.hexstrdump(keys.privateKey.getKeyId()).toUpperCase(),
+ privateKeyArmored: keys.privateKeyArmored,
+ publicKeyArmored: keys.publicKeyArmored
+ });
+ };
+
+ /**
+ * Import the user's key pair
+ */
+ PGP.prototype.importKeys = function(options, callback) {
+ var publicKey, privateKey;
+
+ // check passphrase
+ if (typeof options.passphrase !== 'string' || !options.privateKeyArmored || !options.publicKeyArmored) {
+ callback({
+ errMsg: 'Importing keys failed. Not all options set!'
+ });
+ return;
+ }
+
+ // clear any keypair already in the keychain
+ openpgp.keyring.init();
+ // unlock and import private key
+ if (!openpgp.keyring.importPrivateKey(options.privateKeyArmored, options.passphrase)) {
+ openpgp.keyring.init();
+ callback({
+ errMsg: 'Incorrect passphrase!'
+ });
+ return;
+ }
+ // import public key
+ openpgp.keyring.importPublicKey(options.publicKeyArmored);
+
+ // check if keys have the same id
+ privateKey = openpgp.keyring.exportPrivateKey(0);
+ publicKey = openpgp.keyring.getPublicKeysForKeyId(privateKey.keyId)[0];
+ if (!privateKey || !privateKey.armored || !publicKey || !publicKey.armored || privateKey.keyId !== publicKey.keyId) {
+ // reset keyring
+ openpgp.keyring.init();
+ callback({
+ errMsg: 'Key IDs dont match!'
+ });
+ return;
+ }
+
+ callback();
+ };
+
+ /**
+ * Export the user's key pair
+ */
+ PGP.prototype.exportKeys = function(callback) {
+ var publicKey, privateKey;
+
+ privateKey = openpgp.keyring.exportPrivateKey(0);
+ if (privateKey && privateKey.keyId) {
+ publicKey = openpgp.keyring.getPublicKeysForKeyId(privateKey.keyId)[0];
+ }
+
+ if (!privateKey || !privateKey.keyId || !privateKey.armored || !publicKey || !publicKey.armored) {
+ callback({
+ errMsg: 'Could not export keys!'
+ });
+ return;
+ }
+
+ callback(null, {
+ keyId: util.hexstrdump(privateKey.keyId).toUpperCase(),
+ privateKeyArmored: privateKey.armored,
+ publicKeyArmored: publicKey.armored
+ });
+ };
+
+ /**
+ * Encrypt and sign a pgp message for a list of receivers
+ */
+ PGP.prototype.encrypt = function(plaintext, receiverKeys, callback) {
+ var ct, i,
+ privateKey = openpgp.keyring.exportPrivateKey(0).obj;
+
+ for (i = 0; i < receiverKeys.length; i++) {
+ receiverKeys[i] = openpgp.read_publicKey(receiverKeys[i])[0];
+ }
+
+ ct = openpgp.write_signed_and_encrypted_message(privateKey, receiverKeys, plaintext);
+
+ callback(null, ct);
+ };
+
+ /**
+ * Decrypt and verify a pgp message for a single sender
+ */
+ PGP.prototype.decrypt = function(ciphertext, senderKey, callback) {
+ var privateKey = openpgp.keyring.exportPrivateKey(0).obj;
+ senderKey = openpgp.read_publicKey(senderKey)[0];
+
+ var msg = openpgp.read_message(ciphertext)[0];
+ var keymat = null;
+ var sesskey = null;
+
+ // Find the private (sub)key for the session key of the message
+ for (var i = 0; i < msg.sessionKeys.length; i++) {
+ if (privateKey.privateKeyPacket.publicKey.getKeyId() === msg.sessionKeys[i].keyId.bytes) {
+ keymat = {
+ key: privateKey,
+ keymaterial: privateKey.privateKeyPacket
+ };
+ sesskey = msg.sessionKeys[i];
+ break;
+ }
+ for (var j = 0; j < privateKey.subKeys.length; j++) {
+ if (privateKey.subKeys[j].publicKey.getKeyId() === msg.sessionKeys[i].keyId.bytes) {
+ keymat = {
+ key: privateKey,
+ keymaterial: privateKey.subKeys[j]
+ };
+ sesskey = msg.sessionKeys[i];
+ break;
+ }
+ }
+ }
+ if (keymat !== null) {
+ var decrypted = msg.decryptAndVerifySignature(keymat, sesskey, senderKey);
+ callback(null, decrypted.text);
+
+ } else {
+ callback({
+ errMsg: 'No private key found!'
+ });
+ }
+ };
+
+ return PGP;
+});
\ No newline at end of file