diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..74b0fca --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,25 @@ +# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs + +name: Node.js CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: 8.x + cache: 'npm' + - run: npm install + - run: npm test diff --git a/package.json b/package.json index 0bce708..37ca0ee 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "", "main": "crypto.js", "scripts": { - "test": "node test.js", + "test": "node_modules/.bin/tape ./tests/test_*.js", "lint": "jshint . --config .jshintrc" }, "repository": { @@ -18,6 +18,7 @@ }, "homepage": "https://github.com/xwiki-labs/chainpad-crypto#readme", "dependencies": { + "tape": "^5.6.1", "tweetnacl": "~0.12.2" } } diff --git a/test.js b/test.js deleted file mode 100644 index 966d592..0000000 --- a/test.js +++ /dev/null @@ -1,212 +0,0 @@ -var Assert = require("assert"); -var Crypto = require("./crypto"); -var Nacl = require("tweetnacl"); - -// encrypt -// decrypt -// parseKey -// rand64 -// genKey -// b64RemoveSlashes -// b64AddSlashes -// createEncryptor -// createEditCryptor -// createViewCryptor -// createViewCryptor2 -// createEditCryptor2 -// createFileCryptor2 -// Curve - // encrypt - // decrypt - // signAndEncrypt - // openSigned - // deriveKeys - // createEncryptor -// [ ] Mailbox - // [ ] sealSecretLetter - // [ ] openSecretLetter - // [ ] createEncryptor - -var Alice = Nacl.box.keyPair(); -var Alice_public = Nacl.util.encodeBase64(Alice.publicKey); - -var Bob = Nacl.box.keyPair(); -var Bob_public = Nacl.util.encodeBase64(Bob.publicKey); - -(function () { -var Curve = Crypto.Curve; - -// Basic one to one curve encryption used in CryptPad's chat - -// Alice and Bob can use their own private keys and the other's public key -// to derive some shared values for their pairwise-encrypted channel -// that includes a pre-computed secret key for curve encryption, a signing key, and a validate key - -// Alice derives the keys -var Alice_set = Curve.deriveKeys(Bob_public, Nacl.util.encodeBase64(Alice.secretKey)); - -// Bob does the same -var Bob_set = Curve.deriveKeys(Alice_public, Nacl.util.encodeBase64(Bob.secretKey)); - -['cryptKey', 'signKey', 'validateKey'].forEach(function (k) { - // these should all be strings - Assert.equal('string', typeof(Alice_set[k])); - Assert.equal('string', typeof(Bob_set[k])); - - // and Alice and Bob should have exactly the same values - Assert.equal(Alice_set[k], Bob_set[k]); -}); - -var Alice_cryptor = Curve.createEncryptor(Alice_set); -var Bob_cryptor = Curve.createEncryptor(Bob_set); - -// Now Alice should be able to send Bob a message - -var message = 'pewpewpew'; - -var Alice_ciphertext = Alice_cryptor.encrypt(message); - -var Bob_plaintext = Bob_cryptor.decrypt(Alice_ciphertext); - -Assert.equal(message, Bob_plaintext); -}()); - -// Mailbox stuff - -(function () { - var message = "test"; - var envelope = Crypto.Mailbox.sealSecretLetter(message, { - their_public: Alice.publicKey, - - my_private: Bob.secretKey, - my_public: Bob.publicKey, - }); - var letter = Crypto.Mailbox.openSecretLetter(envelope, { - my_private: Alice.secretKey, - }); - Assert.equal(message, letter.content); - Assert.equal(Bob_public, letter.author); -}()); - -(function () { - // Bob wants to drop a letter in Alice' mailbox. - - // Bob creates an encryptor with his base64-encoded private key - var bob_cryptor = Crypto.Mailbox.createEncryptor({ - curvePrivate: Nacl.util.encodeBase64(Bob.secretKey), - curvePublic: Nacl.util.encodeBase64(Bob.publicKey), - }); - - // Bob writes his very important letter - var bob_plaintext = "pewpewpew bangbangbang"; - - var bob_ciphertext = bob_cryptor.encrypt( - bob_plaintext, - Alice_public // encrypt for Alice' base64-encoded public key - ); - - Assert(Boolean(bob_ciphertext)); - Assert.equal('string', typeof(bob_ciphertext)); - - // Alice checks her mailbox - var Alice_cryptor = Crypto.Mailbox.createEncryptor({ - curvePrivate: Nacl.util.encodeBase64(Alice.secretKey), - curvePublic: Nacl.util.encodeBase64(Alice.publicKey), - }); - - var alice_plaintext = Alice_cryptor.decrypt(bob_ciphertext); - - Assert(Boolean(alice_plaintext)); - Assert.equal('object', typeof(alice_plaintext)); - - Assert.equal(bob_plaintext, alice_plaintext.content); -}()); - -var encode64 = Nacl.util.encodeBase64; - -var makeCurveKeys = function () { - var pair = Nacl.box.keyPair(); - return { - public: encode64(pair.publicKey), - private: encode64(pair.secretKey), - }; -}; - -var makeEdKeys = function () { - var pair = Nacl.sign.keyPair(); - return { - public: encode64(pair.publicKey), - private: encode64(pair.secretKey), - }; -}; - -(function () { - var Team = Crypto.Team; - - var team = { - ed: makeEdKeys(), - curve: makeCurveKeys(), - }; - - // Alice has all the keys for a team - var alice = makeCurveKeys(); - alice.cryptor = Team.createEncryptor({ - teamCurvePublic: team.curve.public, - teamCurvePrivate: team.curve.private, - - teamEdPublic: team.ed.public, - teamEdPrivate: team.ed.private, - - myCurvePublic: alice.public, - myCurvePrivate: alice.private, - }); - - Assert(Boolean(alice.cryptor.encrypt)); - Assert(Boolean(alice.cryptor.decrypt)); - - var plain = 'PEWPEWPEW'; - - var alice_ciphertext = alice.cryptor.encrypt(plain); - Assert(alice_ciphertext); - - // Bob has the keys to read, but not write - var bob = makeCurveKeys(); - bob.cryptor = Team.createEncryptor({ - teamCurvePrivate: team.curve.private, - teamEdPublic: team.ed.public, - }); - - Assert(bob.cryptor.decrypt); // Bob can decrypt - Assert(!bob.cryptor.encrypt); // Bob can't encrypt - - var bob_decrypted = bob.cryptor.decrypt(alice_ciphertext); - - Assert.equal(bob_decrypted.content, plain); - Assert.equal(bob_decrypted.author, alice.public); - - // the same thing, but skipping validation - bob_decrypted = bob.cryptor.decrypt(alice_ciphertext, true); - - Assert.equal(bob_decrypted.content, plain); - Assert.equal(bob_decrypted.author, alice.public); - - // Chuck has the keys to write, but not read - var chuck = makeCurveKeys(); - chuck.cryptor = Team.createEncryptor({ - myCurvePrivate: chuck.private, - myCurvePublic: chuck.public, - teamCurvePublic: team.curve.public, - teamEdPrivate: team.ed.private, - }); - - Assert(chuck.cryptor.encrypt); - Assert(!chuck.cryptor.decrypt); - - var plain2 = 'PEZPEZ'; - var chuck_ciphertext = chuck.cryptor.encrypt(plain2); - - var alice_decrypted = alice.cryptor.decrypt(chuck_ciphertext); - - Assert.equal(alice_decrypted.content, plain2); - Assert.equal(alice_decrypted.author, chuck.public); -}()); diff --git a/tests/test_curve.js b/tests/test_curve.js new file mode 100644 index 0000000..3db91fa --- /dev/null +++ b/tests/test_curve.js @@ -0,0 +1,49 @@ +var Crypto = require("../crypto"); +var Nacl = require("tweetnacl"); +var test = require('tape'); + +test('Test basic one to one curve encryption', function (t) { + var Alice = Nacl.box.keyPair(); + var Alice_public = Nacl.util.encodeBase64(Alice.publicKey); + + var Bob = Nacl.box.keyPair(); + var Bob_public = Nacl.util.encodeBase64(Bob.publicKey); + + var Curve = Crypto.Curve; + + // Basic one to one curve encryption used in CryptPad's chat + + // Alice and Bob can use their own private keys and the other's public key + // to derive some shared values for their pairwise-encrypted channel + // that includes a pre-computed secret key for curve encryption, a signing key, and a validate key + + // Alice derives the keys + var Alice_set = Curve.deriveKeys(Bob_public, Nacl.util.encodeBase64(Alice.secretKey)); + + // Bob does the same + var Bob_set = Curve.deriveKeys(Alice_public, Nacl.util.encodeBase64(Bob.secretKey)); + + ['cryptKey', 'signKey', 'validateKey'].forEach(function (k) { + // these should all be strings + t.equal('string', typeof(Alice_set[k])); + t.equal('string', typeof(Bob_set[k])); + + // and Alice and Bob should have exactly the same values + t.equal(Alice_set[k], Bob_set[k]); + }); + + var Alice_cryptor = Curve.createEncryptor(Alice_set); + var Bob_cryptor = Curve.createEncryptor(Bob_set); + + // Now Alice should be able to send Bob a message + + var message = 'pewpewpew'; + + var Alice_ciphertext = Alice_cryptor.encrypt(message); + + var Bob_plaintext = Bob_cryptor.decrypt(Alice_ciphertext); + + t.equal(message, Bob_plaintext); + t.end(); +}); + diff --git a/tests/test_editcryptor2.js b/tests/test_editcryptor2.js new file mode 100644 index 0000000..270c495 --- /dev/null +++ b/tests/test_editcryptor2.js @@ -0,0 +1,37 @@ +var Crypto = require("../crypto"); +var Nacl = require("tweetnacl"); +var test = require('tape'); + +// EditCryptor2 +test('Test EditCryptor2 without passwords', function (t) { + t.pass("passed"); + var message = "EditCryptor2"; + var alice_cryptor = Crypto.createEditCryptor2(void 0, void 0, void 0); + var bob_cryptor = Crypto.createEditCryptor2(alice_cryptor.editKeyStr, void 0, void 0 ); + var charlie_cryptor = Crypto.createEditCryptor2("abcd", void 0, void 0); + + // Alice and Bob should generate the same keys + t.deepEqual(alice_cryptor, bob_cryptor); + ['cryptKey', "chanId"].forEach(function (k) { + t.notDeepEqual(alice_cryptor[k], charlie_cryptor[k], "Do NOT generate the same keys under wrong keystr"); + }); + t.end(); +}); + +test('Test EditCryptor2 with passwords', function (t) { + t.pass("passed"); + var message = "EditCryptor2"; + var password = "SuperSecretPassword"; + var alice_cryptor = Crypto.createEditCryptor2(void 0, void 0, password); + var bob_cryptor = Crypto.createEditCryptor2(alice_cryptor.editKeyStr, void 0, password ); + var charlie_cryptor = Crypto.createEditCryptor2(alice_cryptor.editKeyStr, void 0, "Wrong" ); + var diana_cryptor = Crypto.createEditCryptor2("abcd", void 0, password); + + // Alice and Bob should generate the same keys + t.deepEqual(alice_cryptor, bob_cryptor); + ['cryptKey', "chanId"].forEach(function (k) { + t.notEqual(alice_cryptor[k], diana_cryptor[k], "Do NOT generate the same keys under wrong keystr"); + }); + t.end(); +}); + diff --git a/tests/test_filecryptor2.js b/tests/test_filecryptor2.js new file mode 100644 index 0000000..8879808 --- /dev/null +++ b/tests/test_filecryptor2.js @@ -0,0 +1,36 @@ +var Crypto = require("../crypto"); +var Nacl = require("tweetnacl"); +var test = require('tape'); + +test('Test FileCryptor2 without passwords', function (t) { + var message = "FileCryptor2"; + var alice_cryptor = Crypto.createFileCryptor2(void 0, void 0); + var bob_cryptor = Crypto.createFileCryptor2(alice_cryptor.fileKeyStr, void 0); + var charlie_cryptor = Crypto.createFileCryptor2("abcd", void 0); + + t.deepEqual(alice_cryptor, bob_cryptor, "Generate the same keys"); + + t.deepEqual(alice_cryptor, bob_cryptor); + ["fileKeyStr", "cryptKey", "chanId"].forEach(function (k) { + t.notDeepEqual(alice_cryptor[k], charlie_cryptor[k], "Different keys under wrong keystr"); + }); + t.end(); +}); + + +test('Test FileCryptor2 with passwords', function (t) { + var message = "FileCryptor2"; + var password = "SuperSecretPassword"; + var alice_cryptor = Crypto.createFileCryptor2(void 0, password); + var bob_cryptor = Crypto.createFileCryptor2(alice_cryptor.fileKeyStr, password ); + var charlie_cryptor = Crypto.createFileCryptor2(alice_cryptor.fileKeyStr, "wrong" ); + var diana_cryptor = Crypto.createFileCryptor2("abcd", password); + t.deepEqual(alice_cryptor, bob_cryptor, "Generate the same keys"); + ['cryptKey', "chanId"].forEach(function (k) { + // Alice and Charlie should NOT generate the same keys (wrong password) + t.notDeepEqual(alice_cryptor[k], charlie_cryptor[k], "Different keys under wrong password"); + // Alice and Charlie should NOT generate the same keys (wrong password) + t.notDeepEqual(alice_cryptor[k], diana_cryptor[k], "Different keys under wrong keystr"); + }); + t.end(); +}); diff --git a/tests/test_mailbox.js b/tests/test_mailbox.js new file mode 100644 index 0000000..e3a0bc9 --- /dev/null +++ b/tests/test_mailbox.js @@ -0,0 +1,94 @@ +var Crypto = require("../crypto"); +var Nacl = require("tweetnacl"); +var test = require('tape'); + +var Alice = Nacl.box.keyPair(); +var Alice_public = Nacl.util.encodeBase64(Alice.publicKey); + +var Bob = Nacl.box.keyPair(); +var Bob_public = Nacl.util.encodeBase64(Bob.publicKey); + +var Curve = Crypto.Curve; + +test('Test basic Mailbox encryption with openOwnSecretLetter', function (t) { + // Alice wants to send a letter to Bob, but she wants to read it later. + var message = "It is a short test."; + + var keys = { + my_private: Alice.secretKey, + my_public: Alice.publicKey, + their_public : Bob.publicKey, + ephemeral_keypair : Nacl.box.keyPair(), + }; + keys.ephemeral_private = keys.ephemeral_keypair.secretKey; + keys.ephemeral_public = keys.ephemeral_keypair.publicKey; + + // Encrypt + var envelope = Crypto.Mailbox.sealSecretLetter(message, keys); + // Decrypt + var letter = Crypto.Mailbox.openOwnSecretLetter(envelope, keys); + + t.equal(message, letter.content, "Can decrypt own content"); + t.equal(Alice_public, letter.author, "Author of own letter is correct"); + + t.end(); +}); + + +test('Test basic Mailbox encryption', function (t) { + var message = "test"; + + // Encrypt + var envelope = Crypto.Mailbox.sealSecretLetter(message, { + their_public: Alice.publicKey, + + my_private: Bob.secretKey, + my_public: Bob.publicKey, + }); + // Decrypt + var letter = Crypto.Mailbox.openSecretLetter(envelope, { + my_private: Alice.secretKey, + }); + + t.equal(message, letter.content, "Can decrypt own content"); + t.equal(Bob_public, letter.author, "Author of own letter is correct"); + + t.end(); +}); + +test('Test basic Mailbox using createEncryptor', function (t) { + // Bob wants to drop a letter in Alice' mailbox. + + // Bob creates an encryptor with his base64-encoded private key + var bob_cryptor = Crypto.Mailbox.createEncryptor({ + curvePrivate: Nacl.util.encodeBase64(Bob.secretKey), + curvePublic: Nacl.util.encodeBase64(Bob.publicKey), + }); + + // Bob writes his very important letter + var bob_plaintext = "pewpewpew bangbangbang"; + + var bob_ciphertext = bob_cryptor.encrypt( + bob_plaintext, + Alice_public // encrypt for Alice' base64-encoded public key + ); + + t.assert(Boolean(bob_ciphertext)); + t.equal('string', typeof(bob_ciphertext)); + + // Alice checks her mailbox + var Alice_cryptor = Crypto.Mailbox.createEncryptor({ + curvePrivate: Nacl.util.encodeBase64(Alice.secretKey), + curvePublic: Nacl.util.encodeBase64(Alice.publicKey), + }); + + var alice_plaintext = Alice_cryptor.decrypt(bob_ciphertext); + + t.assert(Boolean(alice_plaintext)); + t.equal('object', typeof(alice_plaintext)); + + t.equal(bob_plaintext, alice_plaintext.content, "Alice and Bob decrypt to the same message"); + + t.end(); + +}); diff --git a/tests/test_team.js b/tests/test_team.js new file mode 100644 index 0000000..f0be69d --- /dev/null +++ b/tests/test_team.js @@ -0,0 +1,135 @@ +var Crypto = require("../crypto"); +var Nacl = require("tweetnacl"); +var test = require('tape'); + + +//Helper functions +var encode64 = Nacl.util.encodeBase64; + +var makeCurveKeys = function () { + var pair = Nacl.box.keyPair(); + return { + public: encode64(pair.publicKey), + private: encode64(pair.secretKey), + }; +}; + +var makeEdKeys = function () { + var pair = Nacl.sign.keyPair(); + return { + public: encode64(pair.publicKey), + private: encode64(pair.secretKey), + }; +}; + + +var Alice = Nacl.box.keyPair(); +var Alice_public = Nacl.util.encodeBase64(Alice.publicKey); + +var Bob = Nacl.box.keyPair(); +var Bob_public = Nacl.util.encodeBase64(Bob.publicKey); +var Team = Crypto.Team; + +var team = { + ed: makeEdKeys(), + curve: makeCurveKeys(), +}; + +// Alice has all the keys for a team +var alice = makeCurveKeys(); +alice.cryptor = Team.createEncryptor({ + teamCurvePublic: team.curve.public, + teamCurvePrivate: team.curve.private, + + teamEdPublic: team.ed.public, + teamEdPrivate: team.ed.private, + + myCurvePublic: alice.public, + myCurvePrivate: alice.private, +}); + +// Bob has the keys to read, but not write +var bob = makeCurveKeys(); +bob.cryptor = Team.createEncryptor({ + teamCurvePrivate: team.curve.private, + teamEdPublic: team.ed.public, +}); + + +var plaintext = 'PEWPEWPEW'; + +var alice_ciphertext = alice.cryptor.encrypt(plaintext); + +test('Test Mailbox with validation', function (t) { + t.assert(Boolean(alice.cryptor.encrypt)); + t.assert(Boolean(alice.cryptor.decrypt)); + + + t.assert(alice_ciphertext); + + t.assert(bob.cryptor.decrypt, "Bob can decrypt"); + t.assert(!bob.cryptor.encrypt, "Bob can NOT encrypt"); + + var bob_decrypted = bob.cryptor.decrypt(alice_ciphertext); + + t.equal(bob_decrypted.content, plaintext, "Bob correctly decrypts"); + t.equal(bob_decrypted.author, alice.public, "Bob sees author"); + + t.end(); +}); + +test('Test Mailbox without validation', function (t) { + // the same thing as above, but skipping validation + bob_decrypted = bob.cryptor.decrypt(alice_ciphertext, true); + + t.equal(bob_decrypted.content, plaintext, "Bob correctly decrypts w/o validation"); + t.equal(bob_decrypted.author, alice.public, "Bob correctly decrypts author w/o validation"); + + t.end(); +}); + +test('Test Mailbox write only', function (t) { + // Chuck has the keys to write, but not read + var chuck = makeCurveKeys(); + chuck.cryptor = Team.createEncryptor({ + myCurvePrivate: chuck.private, + myCurvePublic: chuck.public, + teamCurvePublic: team.curve.public, + teamEdPrivate: team.ed.private, + }); + t.assert(chuck.cryptor.encrypt, "Chuck can encrypt"); + t.assert(!chuck.cryptor.decrypt, "Chuck cannot decrypt"); + + var plaintext = 'PEZPEZ'; + var chuck_ciphertext = chuck.cryptor.encrypt(plaintext); + + var alice_decrypted = alice.cryptor.decrypt(chuck_ciphertext); + + t.equal(alice_decrypted.content, plaintext, "Alice can decrypt content"); + t.equal(alice_decrypted.author, chuck.public, "Alice can decrpyt author"); + + t.end(); +}); + +test('Test Mailbox read only', function (t) { + // Diana has the keys to read, but not write + var diana = makeCurveKeys(); + diana.cryptor = Team.createEncryptor({ + myCurvePrivate: diana.private, + myCurvePublic: diana.public, + teamCurvePrivate: team.curve.private, + teamEdPublic: team.ed.public, + }); + t.assert(!diana.cryptor.encrypt, "Diana cannot encrypt"); + t.assert(diana.cryptor.decrypt, "Diana can decrypt"); + + var plaintext = 'a boring plaintext'; + var alice_ciphertext = alice.cryptor.encrypt(plaintext); + + var diana_decrypted = diana.cryptor.decrypt(alice_ciphertext); + + t.equal(diana_decrypted.content, plaintext, "Diana can decrypt content"); + t.equal(diana_decrypted.author, alice.public, "Diana can decrypt author"); + + t.end(); +}); diff --git a/tests/test_viewcryptor2.js b/tests/test_viewcryptor2.js new file mode 100644 index 0000000..d560f6e --- /dev/null +++ b/tests/test_viewcryptor2.js @@ -0,0 +1,41 @@ + +var Crypto = require("../crypto"); +var Nacl = require("tweetnacl"); +var test = require('tape'); + +// We first need to generate a viewKeystr. +var editcryptor = Crypto.createEditCryptor2(void 0, void 0, void 0); +var other_editcryptor = Crypto.createEditCryptor2(void 0, void 0, void 0); + +test('Test ViewCryptor2 without passwords', function (t) { + var message = "Hello ViewCryptor2"; + var alice_cryptor = Crypto.createViewCryptor2(editcryptor.viewKeyStr, void 0); + var bob_cryptor = Crypto.createViewCryptor2(editcryptor.viewKeyStr, void 0); + var charlie_cryptor = Crypto.createViewCryptor2(other_editcryptor.viewKeyStr, void 0); + + t.deepEqual(alice_cryptor, bob_cryptor, "Generate the same keys"); + + t.deepEqual(alice_cryptor, bob_cryptor); + ["viewKeyStr", "cryptKey", "chanId"].forEach(function (k) { + t.notDeepEqual(alice_cryptor[k], charlie_cryptor[k], "Different keys under wrong keystr"); + }); + t.end(); +}); + + +test('Test ViewCryptor2 with passwords', function (t) { + var message = "FileCryptor2"; + var password = "SuperSecretPassword"; + var alice_cryptor = Crypto.createViewCryptor2(editcryptor.viewKeyStr, password); + var bob_cryptor = Crypto.createViewCryptor2(editcryptor.viewKeyStr, password); + var charlie_cryptor = Crypto.createViewCryptor2(editcryptor.viewKeyStr, "wrong password"); + var diana_cryptor = Crypto.createViewCryptor2(other_editcryptor.viewKeyStr, password); + t.deepEqual(alice_cryptor, bob_cryptor, "Generate the same keys"); + ['cryptKey', "chanId", "secondarySignKey", "secondaryValidateKey"].forEach(function (k) { + // Alice and Charlie should NOT generate the same keys (wrong password) + t.notDeepEqual(alice_cryptor[k], charlie_cryptor[k], "Different keys under wrong password"); + // Alice and Charlie should NOT generate the same keys (wrong password) + t.notDeepEqual(alice_cryptor[k], diana_cryptor[k], "Different keys under wrong keystr"); + }); + t.end(); +});