Skip to content

Commit 5483765

Browse files
committed
feat: Add int "aes128gcm" content-type handling to python.
* adds new content-type verison handling * fixes flake8 issues * adds documentation and type descriptions for parameters * moves tests to unit testing * adds ability to do cross library validation (note: node.js does not use valid ec256p curve points) * prep work for travis integration
1 parent bbbee50 commit 5483765

13 files changed

Lines changed: 831 additions & 281 deletions

File tree

nodejs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"email": "martin.thomson@gmail.com"
1010
},
1111
"contributors": [{
12-
"name": Marco Castelluccio",
12+
"name": "Marco Castelluccio",
1313
"email": "mcastelluccio@mozilla.com"
1414
}],
1515
"repository": {

nodejs/test.js

Lines changed: 85 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@ var crypto = require('crypto');
44
var ece = require('./ece.js');
55
var base64 = require('urlsafe-base64');
66
var assert = require('assert');
7+
var fs = require('fs');
78

8-
// Usage: node <this> <iterations> <maxsize>
9+
var DUMP_FILE = '../encrypt_data.json';
10+
var dump_data = {};
11+
12+
// Usage: node <this> <iterations> <maxsize> <dump>
913
var count = parseInt(process.argv[2], 10) || 20;
1014
var maxLen = 100;
1115
var plaintext = null;
16+
var dump = false; // flag to dump encrypt/decrypted values to JSON file for cross library checks.
17+
1218
if (process.argv.length >= 4) {
1319
if (!isNaN(parseInt(process.argv[3], 10))) {
1420
maxLen = parseInt(process.argv[3], 10);
1521
} else {
1622
plaintext = new Buffer(process.argv[3], 'ascii');
1723
}
24+
dump = ( process.argv.indexOf('dump') != -1)
1825
}
1926
var log;
2027
if (count === 1) {
@@ -32,16 +39,26 @@ function logbuf(msg, buf) {
3239
}
3340
}
3441

42+
// Validate that the encryption function only accepts Buffers
3543
function validate() {
3644
['hello', null, 1, NaN, [], {}].forEach(function(v) {
3745
try {
38-
encrypt('hello', {});
39-
throw new Error('should insist on a buffer');
40-
} catch (e) {}
46+
ece.encrypt(v, {});
47+
} catch (e) {
48+
if (e.toString() != "Error: buffer argument must be a Buffer") {
49+
throw new Error("encrypt failed to reject " + JSON.stringify(v));
50+
}
51+
}
4152
});
4253
}
4354

44-
function encryptDecrypt(length, encryptParams, decryptParams) {
55+
function dumpData(data){
56+
var version = data.version;
57+
delete(data.version);
58+
dump_data[version] = data;
59+
}
60+
61+
function encryptDecrypt(length, encryptParams, decryptParams, version, keyData) {
4562
decryptParams = decryptParams || encryptParams;
4663
logbuf('Salt', encryptParams.salt);
4764
var input = plaintext || crypto.randomBytes(Math.min(length, maxLen));
@@ -51,22 +68,37 @@ function encryptDecrypt(length, encryptParams, decryptParams) {
5168
logbuf('Encrypted', encrypted);
5269
var decrypted = ece.decrypt(encrypted, decryptParams);
5370
logbuf('Decrypted', decrypted);
71+
if (dump) {
72+
var data = {
73+
version: version,
74+
input: base64.encode(input),
75+
encrypted: base64.encode(encrypted),
76+
params: {
77+
encrypted: encryptParams,
78+
decrypt: decryptParams,
79+
}
80+
};
81+
if (keyData) {
82+
data.keys = keyData;
83+
}
84+
dumpData(data);
85+
}
5486
assert.equal(Buffer.compare(input, decrypted), 0);
5587
log('----- OK');
5688
}
5789

58-
function useExplicitKey() {
90+
function useExplicitKey(version) {
5991
var length = crypto.randomBytes(4);
6092
var params = {
6193
key: base64.encode(crypto.randomBytes(16)),
6294
salt: base64.encode(crypto.randomBytes(16)),
6395
rs: length.readUInt16BE(0) + 1
6496
};
6597
logbuf('Key', params.key);
66-
encryptDecrypt(length.readUInt16BE(2), params);
98+
encryptDecrypt(length.readUInt16BE(2), params, params, version);
6799
}
68100

69-
function authenticationSecret() {
101+
function authenticationSecret(version) {
70102
var length = crypto.randomBytes(4);
71103
var params = {
72104
key: base64.encode(crypto.randomBytes(16)),
@@ -76,20 +108,20 @@ function authenticationSecret() {
76108
};
77109
logbuf('Key', params.key);
78110
logbuf('Context', params.authSecret);
79-
encryptDecrypt(length.readUInt16BE(2), params);
111+
encryptDecrypt(length.readUInt16BE(2), params, params, version);
80112
}
81113

82-
function exactlyOneRecord() {
114+
function exactlyOneRecord(version) {
83115
var length = Math.min(crypto.randomBytes(2).readUInt16BE(0), maxLen);
84116
var params = {
85117
key: base64.encode(crypto.randomBytes(16)),
86118
salt: base64.encode(crypto.randomBytes(16)),
87119
rs: length + 1
88120
};
89-
encryptDecrypt(length, params);
121+
encryptDecrypt(length, params, params, version);
90122
}
91123

92-
function detectTruncation() {
124+
function detectTruncation(version) {
93125
var length = Math.min(crypto.randomBytes(2).readUInt16BE(0), maxLen);
94126
var params = {
95127
key: base64.encode(crypto.randomBytes(16)),
@@ -104,7 +136,7 @@ function detectTruncation() {
104136
logbuf('Encrypted', encrypted);
105137
var ok = false;
106138
try {
107-
ece.decrypt(encrypted, params);
139+
ece.decrypt(encrypted, params, params, version);
108140
} catch (e) {
109141
log('----- OK: ' + e);
110142
ok = true;
@@ -114,7 +146,7 @@ function detectTruncation() {
114146
}
115147
}
116148

117-
function useKeyId() {
149+
function useKeyId(version) {
118150
var length = crypto.randomBytes(4);
119151
var keyid = base64.encode(crypto.randomBytes(16));
120152
var key = crypto.randomBytes(16);
@@ -124,15 +156,15 @@ function useKeyId() {
124156
salt: base64.encode(crypto.randomBytes(16)),
125157
rs: length.readUInt16BE(0) + 1
126158
};
127-
encryptDecrypt(length.readUInt16BE(2), params);
159+
encryptDecrypt(length.readUInt16BE(2), params, params, version);
128160
}
129161

130-
function useDH() {
162+
function useDH(version) {
131163
// the static key is used by the receiver
132164
var staticKey = crypto.createECDH('prime256v1');
133165
staticKey.generateKeys();
134166
assert.equal(staticKey.getPublicKey()[0], 4, 'is an uncompressed point');
135-
var staticKeyId = staticKey.getPublicKey().toString('hex')
167+
var staticKeyId = staticKey.getPublicKey().toString('hex');
136168
ece.saveKey(staticKeyId, staticKey, 'P-256');
137169

138170
logbuf('Receiver private', staticKey.getPrivateKey());
@@ -161,22 +193,44 @@ function useDH() {
161193
salt: encryptParams.salt,
162194
rs: encryptParams.rs
163195
};
164-
encryptDecrypt(length.readUInt16BE(2), encryptParams, decryptParams);
196+
// keyData is used for cross library verification dumps
197+
var keyData = {
198+
sender: {
199+
private: base64.encode(ephemeralKey.getPrivateKey()),
200+
public: base64.encode(ephemeralKey.getPublicKey())
201+
},
202+
receiver: {
203+
private: base64.encode(staticKey.getPrivateKey()),
204+
public: base64.encode(staticKey.getPublicKey())
205+
}
206+
};
207+
encryptDecrypt(length.readUInt16BE(2), encryptParams, decryptParams, version, keyData);
165208
}
166209

167210
validate();
168-
var i;
169-
for (i = 0; i < count; ++i) {
170-
[ useExplicitKey,
171-
authenticationSecret,
172-
exactlyOneRecord,
173-
detectTruncation,
174-
useKeyId,
175-
useDH,
176-
].forEach(function(f) {
177-
log('Test: ' + f.name);
178-
f();
179-
});
180-
}
181211

212+
for (var version of ['aes128gcm', 'aesgcm', 'aesgcm128']) {
213+
for (var i = 0; i < count; ++i) {
214+
[useExplicitKey,
215+
authenticationSecret,
216+
exactlyOneRecord,
217+
detectTruncation,
218+
useKeyId,
219+
useDH,
220+
].forEach(function (f) {
221+
log('Test: ' + f.name);
222+
f(version);
223+
});
224+
}
225+
}
182226
console.log('All tests passed.');
227+
228+
if (dump) {
229+
fs.open(DUMP_FILE, 'w', function (err) {
230+
if (err) {
231+
fs.unlink(DUMP_FILE)
232+
}
233+
234+
fs.writeFile(DUMP_FILE, JSON.stringify(dump_data, undefined, ' '));
235+
})
236+
}

python/.coveragerc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[report]
2+
omit =
3+
*noseplugin*
4+
show_missing = true

python/.noserc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# this file's explicitly loaded from setup.cfg (.noserc isn't a
2+
# standard config path), separated out due to '%(...)s'
3+
[nosetests]
4+
verbose=True
5+
verbosity=1
6+
detailed-errors=True
7+
with-coverage=True
8+
cover-erase=True
9+
cover-package=http_ece
10+
cover-tests=True
11+
logging-format=%(asctime)s,%(msecs)03d %(name)s: %(levelname)s: %(message)s
12+
logging-datefmt=%H:%M:%S

python/.travis.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
language: python
2+
python:
3+
- "2.7"
4+
install:
5+
- pip install -r test-requirements.txt
6+
script:
7+
- nosetests
8+
- flake8 http-ece
9+
after_success:
10+
- codecov

python/VERSION_DIFFERENCES.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Major Differences Between the Various HTTP ECE Versions
2+
3+
## aes128gcm
4+
* Most current version as of 2016/11
5+
* `salt`, `rs`, and `key_id` now all contained as preamble for the encrypted content.
6+
* Sender's public DH key value is sent as the `dh` parameter of the `Crypto-Key` header
7+
* The `Encryption` header is no longer required.
8+
* The context string `WebPush: info\x00` + Receiver's raw public key + Sender's raw public key
9+
* `keyinfo` string set to `Content-Encoding: aes128gcm\x00`
10+
* `nonceinfo` string set to `Content-Encoding: nonce\x00`
11+
12+
## aesgcm
13+
* `salt` contained as 'salt' parameter of the `Encryption` header
14+
* `key_id` contained as `keyid` parameter of the `Crypto-Key` header
15+
* Sender's public DH key value is sent as the `dh` parameter of the `Crypto-Key` header
16+
* The context string is: `P-256\x00\x00\x41` + Receiver's raw public key + `\x00\x41` + Sender's raw public key
17+
* `keyinfo` string set to `Content-Encoding: aesgcm\x00` + context_string
18+
* `nonceinfo` string set to `Content-Encoding: nonce` + context_string
19+
20+
## aesgcm128
21+
* Most obsolete version
22+
* `salt` contained as 'salt' parameter of the `Encryption` header
23+
* `key_id` contained as `keyid` parameter of the `Encryption-Key` header
24+
* Sender's public DH key value is sent as the `dh` parameter of the `Encryption-Key` header
25+
* The context string is: `P-256\x00\x00\x41` + Receiver's raw public key + `\x00\x41` + Sender's raw public key
26+
* `keyinfo` string set to `Content-Encoding: aesgcm128`
27+
* `nonceinfo` string set to `Content-Encoding: nonce`
28+
* padding between chunks is only 1 octet.

0 commit comments

Comments
 (0)