Skip to content

Commit 8320c1c

Browse files
committed
Refactor the interface to side-effect free
After attempting to use the interface for the first time, I found that it was not very suitable. Implementing the large Store interface was also painful. The new interface is much more functional. All methods are side-effect free, with the exception of calling the remaining store methods. I've moved the trust check out of Axolotl as this should be done at a higher level, in my opinion. I've also removed the JSON serialisation of the session, as some clients may want to serialise the session in other ways.
1 parent 99fca9c commit 8320c1c

20 files changed

+1002
-1614
lines changed

README.md

Lines changed: 1 addition & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -108,90 +108,13 @@ Name|Type|Description
108108
:---|:---|:----------
109109
`preKeyId`|Number|The identifier of the pre-key.
110110

111-
#### getRemotePreKeyBundle
112-
113-
```
114-
getRemotePreKeyBundle(remoteIdentity) → {PreKeyBundle}
115-
```
116-
117-
Retrieve a pre-key bundle for a remote identity. This will likely be retrieved from a remote server. A `PreKeyBundle` is
118-
a JavaScript object with the following properties:
119-
120-
Name|Type|Description
121-
:---|:---|:----------
122-
`identityKey`|ArrayBuffer|The remote identity's public key.
123-
`preKeyId`|Number|The identifier of the pre-key included in this bundle.
124-
`preKey`|ArrayBuffer|The public half of the pre-key.
125-
`signedPreKeyId`|Number|The identifier of the signed pre-key included in this bundle.
126-
`signedPreKey`|ArrayBuffer|The public half of the signed pre-key.
127-
`signedPreKeySignature`|ArrayBuffer|The signature associated with the `signedPreKey`.
128-
129111
##### Parameters
130112

131113
Name|Type|Description
132114
:---|:---|:----------
133115
`remoteIdentity`|Identity|The identity of the remote entity. The type is the same as the type you pass to into
134116
the methods on Axolotl.
135117

136-
#### isRemoteIdentityTrusted
137-
138-
```
139-
isRemoteIdentityTrusted(remoteIdentity, identityPublicKey) → {Boolean}
140-
```
141-
142-
Determine if the public key associated with the remote identity is trusted. It is up to the application to determine
143-
an appropriate algorithm for this.
144-
145-
##### Parameters
146-
147-
Name|Type|Description
148-
:---|:---|:----------
149-
`remoteIdentity`|Identity|The identity of the remote entity.
150-
`identityPublicKey`|ArrayBuffer|The purported remote identity's public key.
151-
152-
#### hasSession
153-
154-
```
155-
hasSession(identity) → {Boolean}
156-
```
157-
158-
Determine if there is a stored session for the identity.
159-
160-
##### Parameters
161-
162-
Name|Type|Description
163-
:---|:---|:----------
164-
`identity`|Identity|The identity.
165-
166-
#### getSession
167-
168-
```
169-
getSession(identity) → {String}
170-
```
171-
172-
Retrieve the session state associated with the identity.
173-
174-
##### Parameters
175-
176-
Name|Type|Description
177-
:---|:---|:----------
178-
`identity`|Identity|The identity.
179-
180-
#### putSession
181-
182-
```
183-
putSession(identity, sessionState) → {Void}
184-
```
185-
186-
Store the session state along with the identity.
187-
188-
##### Parameters
189-
190-
Name|Type|Description
191-
:---|:---|:----------
192-
`identity`|Identity|The identity.
193-
`sessionState`|String|The serialised session state.
194-
195118
### Using Axolotl
196119

197120
Start by instantiating Axolotl:
@@ -229,4 +152,4 @@ axol.decryptPreKeyWhisperMessage("alice", ciphertext).then(function(plaintext) {
229152
});
230153
```
231154

232-
and that's it!
155+
See [Axolotl.js](src/Axolotl.js) for more detailed API documentation.

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "axolotl",
33
"main": "dist/axolotl.js",
4-
"version": "0.9.0",
4+
"version": "1.0.0",
55
"authors": [
66
"Joe Bandenburg <joe@bandenburg.com>"
77
],

dist/axolotl.js

Lines changed: 287 additions & 598 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "axolotl",
3-
"version": "0.9.1",
3+
"version": "1.0.0",
44
"description": "A JavaScript port of libaxolotl. Axolotl is a ratcheting forward secrecy protocol.",
55
"main": "dist/axolotl.js",
66
"directories": {

src/Axolotl.js

Lines changed: 49 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
*/
1717

1818
import SessionFactory from "./SessionFactory";
19+
import SessionCipher from "./SessionCipher";
1920
import {InvalidMessageException} from "./Exceptions";
2021
import Store from "./Store";
2122
import Crypto from "./Crypto";
2223
import co from "co";
24+
import axolotlCrypto from "axolotl-crypto";
2325

2426
/**
2527
* A public/private key pair
@@ -90,6 +92,7 @@ function Axolotl(crypto, store) {
9092
var wrappedCrypto = new Crypto(crypto);
9193

9294
var sessionFactory = new SessionFactory(wrappedCrypto, wrappedStore);
95+
var sessionCipher = new SessionCipher(wrappedCrypto);
9396

9497
/**
9598
* Generate an identity key pair. Clients should only do this once, at install time.
@@ -176,67 +179,67 @@ function Axolotl(crypto, store) {
176179
});
177180

178181
/**
179-
* Encrypt a message using the session associated with toIdentity. If no such session exists, it will be created.
182+
* @typedef {Object} PreKeyBundle
183+
* @property {ArrayBuffer} identityKey - The remote identity's public key.
184+
* @property {Number} preKeyId - The identifier of the pre-key included in this bundle.
185+
* @property {ArrayBuffer} preKey - The public half of the pre-key.
186+
* @property {Number} signedPreKeyId - The identifier of the signed pre-key included in this bundle.
187+
* @property {ArrayBuffer} signedPreKey - The public half of the signed pre-key.
188+
* @property {ArrayBuffer} signedPreKeySignature - The signature associated with the `signedPreKey`
189+
*/
190+
191+
/**
192+
* Create a session from a pre-key bundle, probably retrieved from a server.
193+
* @method
194+
* @type {PreKeyBundle} a pre-key bundle
195+
* @returns {Promise.<Session, Error>}
196+
*/
197+
this.generateSignedPreKeyBundle = sessionFactory.createSessionFromPreKeyBundle;
198+
199+
/**
200+
* Create a session from a PreKeyWhisperMessage.
201+
* @method
202+
* @type {Session} session - a session, if one exists, or null otherwise.
203+
* @type {ArrayBuffer} preKeyWhisperMessageBytes - the bytes of a PreKeyWhisperMessage.
204+
* @returns {Promise.<Session, Error>}
205+
*/
206+
this.createSessionFromPreKeyWhisperMessage = sessionFactory.createSessionFromPreKeyWhisperMessage;
207+
208+
/**
209+
* Encrypt a message using the session.
180210
* <p>
181-
* It is safe to call this method multiple times without waiting for the returned promises to be resolved. In this
182-
* case, operations may be queued to ensure sessions are in the correct state for each encryption.
211+
* If this method succeeds, the passed in session should be destroyed. This method must never be called with
212+
* that session again.
183213
*
184214
* @method
185-
* @param {Identity} toIdentity - the identity of the intended recipient
186-
* @param {ArrayBuffer} message - the message bytes to be encrypted
187-
* @return {Promise.<ArrayBuffer, (InvalidKeyException|UntrustedIdentityException)>} encrypted message bytes if
188-
* fulfilled. May be rejected if a fresh session is required and the pre key bundle returned from the server is
189-
* invalid.
215+
* @param {Session} session
216+
* @param {ArrayBuffer} message - the message bytes to be encrypted (optionally padded)
217+
* @return {Promise.<Object, Error>} an object containing the encrypted message bytes as well as a new session
190218
*/
191-
this.encryptMessage = co.wrap(function*(toIdentity, message) {
192-
var session;
193-
var hasSession = yield sessionFactory.hasSessionForIdentity(toIdentity);
194-
if (hasSession) {
195-
session = yield sessionFactory.getSessionForIdentity(toIdentity);
196-
} else {
197-
var preKeyBundle = yield wrappedStore.getRemotePreKeyBundle(toIdentity);
198-
session = yield sessionFactory.createSessionFromPreKeyBundle(toIdentity, preKeyBundle);
199-
}
200-
return yield session.encryptMessage(message);
201-
});
219+
this.encryptMessage = sessionCipher.encryptMessage;
202220

203221
/**
204-
* Decrypt a WhisperMessage using the session associated with fromIdentity.
222+
* Decrypt a WhisperMessage using session.
205223
* <p>
206-
* It is safe to call this method multiple times without waiting for the returned promises to be resolved. In this
207-
* case, operations may be queued to ensure sessions are in the correct state for each decryption.
224+
* If this method succeeds, the passed in session should be destroyed. This method must never be called with
225+
* that session again.
208226
*
209227
* @method
210-
* @param {Identity} fromIdentity - the identity of the intended recipient
211-
* @param {ArrayBuffer} message - the encrypted message bytes
212-
* @return {Promise.<ArrayBuffer, (InvalidMessageException|DuplicateMessageException)>} decrypted message bytes if
213-
* fulfilled. May be rejected if a session does not exist, the message is invalid or has been decrypted before.
228+
* @param {Session} session
229+
* @param {ArrayBuffer} whisperMessageBytes - the encrypted message bytes
230+
* @returns {Promise.<Object, InvalidMessageException>} an object containing the decrypted message and a new session
214231
*/
215-
this.decryptWhisperMessage = co.wrap(function*(fromIdentity, message) {
216-
var hasSession = yield sessionFactory.hasSessionForIdentity(fromIdentity);
217-
if (!hasSession) {
218-
throw new InvalidMessageException("No session for message");
219-
}
220-
var session = yield sessionFactory.getSessionForIdentity(fromIdentity);
221-
return yield session.decryptWhisperMessage(message);
222-
});
232+
this.decryptWhisperMessage = sessionCipher.decryptWhisperMessage;
223233

224234
/**
225-
* Decrypt a PreKeyWhisperMessage using the session associated with fromIdentity.
226-
* <p>
227-
* It is safe to call this method multiple times without waiting for the returned promises to be resolved. In this
228-
* case, operations may be queued to ensure sessions are in the correct state for each decryption.
235+
* Unwrap the WhisperMessage from a PreKeyWhisperMessage and attempt to decrypt it using session.
229236
*
230237
* @method
231-
* @param {Identity} fromIdentity - the identity of the intended recipient
232-
* @param {ArrayBuffer} message - the encrypted message bytes
233-
* @return {Promise.<ArrayBuffer, (InvalidMessageException|DuplicateMessageException)>} decrypted message bytes if
234-
* fulfilled. May be rejected if the message is invalid or has been decrypted before.
238+
* @param {Session} session
239+
* @param {ArrayBuffer} preKeyWhisperMessageBytes - the encrypted message bytes
240+
* @returns {Promise.<Object, InvalidMessageException>} an object containing the decrypted message and a new session
235241
*/
236-
this.decryptPreKeyWhisperMessage = co.wrap(function*(fromIdentity, message) {
237-
var session = yield sessionFactory.createSessionFromPreKeyWhisperMessage(fromIdentity, message);
238-
return yield session.decryptPreKeyWhisperMessage(message);
239-
});
242+
this.decryptPreKeyWhisperMessage = sessionCipher.decryptPreKeyWhisperMessage;
240243

241244
Object.freeze(self);
242245
}

src/ProtocolConstants.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ export default {
3333
// TODO: Make these configurable?
3434
maximumRetainedReceivedChainKeys: 5,
3535
maximumMissedMessages: 2000,
36-
maximumSessionsPerIdentity: 40
36+
maximumSessionStatesPerIdentity: 40
3737
};

src/SequentialOperationQueue.js

Lines changed: 0 additions & 44 deletions
This file was deleted.

0 commit comments

Comments
 (0)