Skip to content

Commit d204927

Browse files
committed
Merge pull request #397 from uProxy/trevj-rc4
add an RC4-based transformer
2 parents 6582e45 + cafd030 commit d204927

5 files changed

Lines changed: 139 additions & 0 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"jasmine-core": "^2.3.4",
5959
"lodash": "^3.10.1",
6060
"request": "^2.53.0",
61+
"simple-rc4": "0.0.1-b",
6162
"socks5-http-client": "^1.0.2",
6263
"ssh2": "0.5.0",
6364
"tslint": "^3.3.0",

src/churn-pipe/churn-pipe.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import net = require('../net/net.types');
1212
import PassThrough = require('../transformers/passthrough');
1313
import promises = require('../promises/promises');
1414
import protean = require('../transformers/protean');
15+
import rc4 = require('../transformers/rc4');
1516
import sequence = require('../transformers/byteSequenceShaper');
1617
import transformer = require('../transformers/transformer');
1718

@@ -29,6 +30,7 @@ var transformers :{[name:string] : new() => transformer.Transformer} = {
2930
'fragmentationShaper': fragmentation.FragmentationShaper,
3031
'none': PassThrough,
3132
'protean': protean.Protean,
33+
'rc4': rc4.Rc4Transformer,
3234
'sequenceShaper': sequence.ByteSequenceShaper
3335
};
3436

src/transformers/rc4.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path='../../../third_party/typings/browser.d.ts' />
2+
3+
import freedomMocker = require('../freedom/mocks/mock-freedom-in-module-env');
4+
declare let freedom: freedom.FreedomInModuleEnv;
5+
freedom = freedomMocker.makeMockFreedomInModuleEnv();
6+
7+
import arraybuffers = require('../arraybuffers/arraybuffers');
8+
import rc4 = require('./rc4');
9+
10+
describe('rc4 transformer', function() {
11+
let transformer: rc4.Rc4Transformer;
12+
13+
beforeEach(function() {
14+
transformer = new rc4.Rc4Transformer();
15+
});
16+
17+
it('simple transform/restore', function() {
18+
const p = new Uint8Array([0, 1, 2]);
19+
20+
const transformedFragments = transformer.transform(p);
21+
const result = transformer.restore(transformedFragments[0])[0];
22+
23+
expect(arraybuffers.byteEquality(p, result)).toBeTruthy();
24+
});
25+
});

src/transformers/rc4.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/// <reference path='../../../third_party/simple-rc4/simple-rc4.d.ts' />
2+
/// <reference path='../../../third_party/typings/browser.d.ts' />
3+
4+
import crypto = require('crypto');
5+
import logging = require('../logging/logging');
6+
import rc4 = require('simple-rc4');
7+
import transformer = require('./transformer');
8+
9+
const log = new logging.Log('rc4 transformer');
10+
11+
// Accepted in serialised form by configure().
12+
export interface Config {
13+
key: string
14+
}
15+
16+
const KEY_LENGTH_BYTES = 16;
17+
const R_LENGTH_BYTES = 8;
18+
const TRUNCATED_IV_LENGTH_BYTES = 8;
19+
20+
// Creates a sample (non-random) config, suitable for testing.
21+
export function sampleConfig(): Config {
22+
return {
23+
key: new Buffer(KEY_LENGTH_BYTES).fill(0).toString('hex')
24+
};
25+
}
26+
27+
// Fast, uniformly random transformer with minimal length expansion, via RC4.
28+
// From a proposal by kpdyer.
29+
//
30+
// Terminology:
31+
// - K: 128-bit session key, negotiated through the signalling channel
32+
// - RANDOM(N): a cryptographically secure N-byte string
33+
// - X[n,...,m]: the (m - n + 1) bytes starting at byte-index n of X
34+
// - |X|: length, in bytes, of X
35+
//
36+
// Pseudo-code:
37+
// def Encrypt(K, P):
38+
// R = RANDOM(8)
39+
// IV = SHA1(K || R)
40+
// RET R || RC4(IV[0,...,7], P)
41+
//
42+
// def Decrypt(K, C):
43+
// R = C[0,...,7]
44+
// IV = SHA1(K || R)
45+
// RET RC4(IV[0,...,7], C[8,...,|C | -1])
46+
export class Rc4Transformer implements transformer.Transformer {
47+
private key_: Buffer;
48+
49+
public constructor() {
50+
this.configure(JSON.stringify(sampleConfig()));
51+
}
52+
53+
public configure = (json: string): void => {
54+
try {
55+
const config = <Config>JSON.parse(json);
56+
if (config.key === undefined) {
57+
throw new Error('must set key parameter');
58+
}
59+
const key = new Buffer(config.key, 'hex');
60+
if (key.byteLength !== KEY_LENGTH_BYTES) {
61+
throw new Error('keys must be ' + KEY_LENGTH_BYTES + ' bytes in length');
62+
}
63+
this.key_ = key;
64+
} catch (e) {
65+
throw new Error('could not parse config: ' + e.message);
66+
}
67+
}
68+
69+
// Applies RC4(IV[0,...,7] to bytes, as described in the pseudocode above.
70+
private update_ = (r: Buffer, bytes:Buffer): void => {
71+
const hasher = crypto.createHash('sha1');
72+
const iv = hasher.update(Buffer.concat([this.key_, r]));
73+
const truncatedIv = iv.digest().slice(0, TRUNCATED_IV_LENGTH_BYTES);
74+
new rc4(truncatedIv).update(bytes);
75+
}
76+
77+
public transform = (ab: ArrayBuffer): ArrayBuffer[] => {
78+
const p = new Buffer(ab);
79+
80+
const r = crypto.randomBytes(R_LENGTH_BYTES);
81+
this.update_(r, p);
82+
83+
return [Buffer.concat([r, p]).buffer];
84+
}
85+
86+
public restore = (ab: ArrayBuffer): ArrayBuffer[] => {
87+
const c = new Buffer(ab);
88+
89+
const r = c.slice(0, R_LENGTH_BYTES);
90+
const tail = c.slice(R_LENGTH_BYTES);
91+
this.update_(r, tail);
92+
93+
// Because tail is constructed via Buffer#slice, its buffer field
94+
// still references ab, which still includes r.
95+
const slicedResult = tail.buffer.slice(8);
96+
return [slicedResult];
97+
}
98+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// TypeScript typings for:
2+
// https://www.npmjs.com/package/simple-rc4
3+
4+
declare module 'simple-rc4' {
5+
class RC4 {
6+
constructor(key:Buffer);
7+
8+
// Returns the supplied Buffer, encoded.
9+
update(msg: Buffer): Buffer;
10+
}
11+
12+
export = RC4;
13+
}

0 commit comments

Comments
 (0)