summaryrefslogtreecommitdiff
path: root/src/org/noreply/fancydress/type3/SingleLeg.java
blob: fe138b75ecd76e41cb4c854b3a17b46c49641707 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/* $Id$ */
package org.noreply.fancydress.type3;

import org.noreply.fancydress.misc.Util;
import org.noreply.fancydress.type3.routing.*;
import org.noreply.fancydress.crypto.*;
import org.noreply.fancydress.status.*;
import java.util.*;

public abstract class SingleLeg {
	public static final int MIN_SH = 42; /* length of the invariant part of a subheader */
	public static final int SINGLE_HEADER_LEN = 2048; /* length of the invariant part of a subheader */
	private byte[] thisLeg = null;
	private Routing thisRoute = null;

	public static final int MAJOR_VERSION = 0;
	public static final int MINOR_VERSION = 3;

	public SingleLeg() {
	}

	public byte[] asOctets() {
		if (thisLeg == null)
			throw new Error("Leg not yet initialized");
		return thisLeg;
	}

	protected void makeLeg(
		Hop[] hops,
		byte[][] sharedKeys,
		Routing finalRouting)
	throws Mix3BadArgumentsChainTooLongException
	{
		if (thisLeg != null)
			throw new Error("Leg already initialized");

		int n = hops.length;
		int[] size = new int[n];
		byte[][] junkKey = new byte[n][];
		byte[][] key = new byte[n][];
		byte[][] junk = new byte[n][];
		byte[][] subHeader = new byte[n+1][];

		// Calculate the sizes of the subheaders
		int paddingLength = SINGLE_HEADER_LEN;
		for (int i=0; i < n; i++) {
			Routing routing = i == n-1 ? finalRouting : hops[i+1].getRouting();

			size[i] = MIN_SH + RSAPublicKey.PK_OVERHEAD_LEN + routing.getRoutingInformationLength();
			junkKey[i] = CryptoPrimitives.subKey(sharedKeys[i], "RANDOM JUNK");
			key[i] = CryptoPrimitives.subKey(sharedKeys[i], "HEADER SECRET KEY");

			paddingLength -= size[i];
		}

		if (paddingLength < 0)
			throw new Mix3BadArgumentsChainTooLongException("Chain too long");

		// Calculate the Junk that will be appended during processing.
		// J_i is the junk that node i will append, and node i+1 will see.
		for (int i=0; i < n; i++) {
			byte[] tmp = CryptoPrimitives.prng(junkKey[i], size[i]);
			junk[i] = i == 0 ? tmp : Util.concat(junk[i-1], tmp);
			byte[] stream = CryptoPrimitives.prng(key[i], SINGLE_HEADER_LEN + size[i]);
			// Before we encrypt the junk, we encrypt all the data, and all
			// the initial padding, but not the RSA-encrypted part.
			int offset = SINGLE_HEADER_LEN - RSAPublicKey.PK_ENC_LEN - (i==0 ? 0 : junk[i-1].length);
			junk[i] = CryptoPrimitives.xor(junk[i], Util.slice(stream, offset, junk[i].length));
		}

		// Create the Header, starting with the padding
		subHeader[n] = CryptoPrimitives.rand(paddingLength);
		for (int i=n-1; i>=0; i--) {
			Routing routing = i == n-1 ? finalRouting : hops[i+1].getRouting();

			byte[] sh0 = makeSHS(sharedKeys[i], CryptoPrimitives.zero(CryptoPrimitives.HASH_LEN), routing);
			int shLength = sh0.length;
			byte[] h0 = Util.concat( sh0, subHeader[i+1] );

			byte[] rest = Util.slice( h0, RSAPublicKey.PK_MAX_DATA_LEN, h0.length - RSAPublicKey.PK_MAX_DATA_LEN);
			byte[] encryptedRest = CryptoPrimitives.encrypt(key[i], rest);

			byte[] digest = CryptoPrimitives.hash( i == 0 ? encryptedRest : Util.concat(encryptedRest, junk[i-1]));

			byte[] sh = makeSHS(sharedKeys[i], digest, routing);
			int underflow = Util.max(RSAPublicKey.PK_MAX_DATA_LEN - shLength, 0);
			byte[] rsaPart = Util.concat( sh, Util.slice(h0, RSAPublicKey.PK_MAX_DATA_LEN - underflow, underflow));

			RSAPublicKey pk = hops[i].getPubKey();
			byte[] esh = pk.encrypt(rsaPart);
			subHeader[i] = Util.concat(esh, encryptedRest);
		}
		byte[] result = subHeader[0];

		thisLeg = result;
		thisRoute = hops[0].getRouting();
	}


	/**
	 * create a sub header structure.
	 */
	static private byte[] makeSHS(
		byte[] sharedSecret,
		byte[] digest,
		Routing routing)
	{
		if (sharedSecret.length != CryptoPrimitives.KEY_LEN)
			throw new IllegalArgumentException("sharedSecret must be KEY_LEN bytes long.");
		if (digest.length != CryptoPrimitives.HASH_LEN)
			throw new IllegalArgumentException("digest must be HASH_LEN bytes long.");
		byte[] fixedPart = new byte[MIN_SH];
		byte[] dynamicPart = routing.getRoutingInformation();
		int routingSize = dynamicPart.length;
		int routingType = routing.getRoutingType();

		int pos = 0;
		fixedPart[pos] = MAJOR_VERSION;
		pos++;
		fixedPart[pos] = MINOR_VERSION;
		pos++;
		System.arraycopy(sharedSecret, 0, fixedPart, pos, CryptoPrimitives.KEY_LEN);
		pos +=CryptoPrimitives.KEY_LEN;
		System.arraycopy(digest, 0, fixedPart, pos, CryptoPrimitives.HASH_LEN);
		pos +=CryptoPrimitives.HASH_LEN;
		fixedPart[pos] = (byte) ( (routingSize >> 8) & 0xff);
		pos++;
		fixedPart[pos] = (byte) (  routingSize       & 0xff);
		pos++;
		fixedPart[pos] = (byte) ( (routingType >> 8) & 0xff);
		pos++;
		fixedPart[pos] = (byte) (  routingType       & 0xff);
		pos++;

		if (pos != MIN_SH)
			throw new Error("Did not fill in MIN_SH bytes!");

		return Util.concat(fixedPart, dynamicPart);
	}

	public Routing getRoute() {
		return thisRoute;
	}
}