Coverage Summary for Class: EncryptionHandshake (org.ethereum.net.rlpx)
Class |
Method, %
|
Line, %
|
EncryptionHandshake |
0%
(0/27)
|
0%
(0/173)
|
EncryptionHandshake$Secrets |
0%
(0/6)
|
0%
(0/6)
|
Total |
0%
(0/33)
|
0%
(0/179)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.net.rlpx;
21
22 import com.google.common.base.Preconditions;
23 import com.google.common.base.Throwables;
24 import org.ethereum.crypto.ECIESCoder;
25 import org.ethereum.crypto.ECKey;
26 import org.ethereum.crypto.Keccak256Helper;
27 import org.ethereum.crypto.signature.ECDSASignature;
28 import org.ethereum.crypto.signature.Secp256k1;
29 import org.ethereum.util.ByteUtil;
30 import org.bouncycastle.crypto.InvalidCipherTextException;
31 import org.bouncycastle.crypto.digests.KeccakDigest;
32 import org.bouncycastle.math.ec.ECPoint;
33
34 import javax.annotation.Nullable;
35 import java.io.IOException;
36 import java.math.BigInteger;
37 import java.security.SecureRandom;
38
39 import static org.ethereum.crypto.Keccak256Helper.keccak256;
40
41 /**
42 * Created by devrandom on 2015-04-08.
43 */
44 public class EncryptionHandshake {
45 public static final int NONCE_SIZE = 32;
46 public static final int MAC_SIZE = 256;
47 public static final int SECRET_SIZE = 32;
48 private SecureRandom random = new SecureRandom();
49 private boolean isInitiator;
50 private ECKey ephemeralKey;
51 private ECPoint remotePublicKey;
52 private ECPoint remoteEphemeralKey;
53 private byte[] initiatorNonce;
54 private byte[] responderNonce;
55 private Secrets secrets;
56
57 public EncryptionHandshake(ECPoint remotePublicKey) {
58 this.remotePublicKey = remotePublicKey;
59 ephemeralKey = new ECKey(random);
60 initiatorNonce = new byte[NONCE_SIZE];
61 random.nextBytes(initiatorNonce);
62 isInitiator = true;
63 }
64
65 EncryptionHandshake(ECPoint remotePublicKey, ECKey ephemeralKey, byte[] initiatorNonce, byte[] responderNonce, boolean isInitiator) {
66 this.remotePublicKey = remotePublicKey;
67 this.ephemeralKey = ephemeralKey;
68 this.initiatorNonce = initiatorNonce;
69 this.responderNonce = responderNonce;
70 this.isInitiator = isInitiator;
71 }
72
73 public EncryptionHandshake() {
74 ephemeralKey = new ECKey(random);
75 responderNonce = new byte[NONCE_SIZE];
76 random.nextBytes(responderNonce);
77 isInitiator = false;
78 }
79
80 /**
81 * Create a handshake auth message defined by EIP-8
82 *
83 * @param key our private key
84 */
85 public AuthInitiateMessageV4 createAuthInitiateV4(ECKey key) {
86 AuthInitiateMessageV4 message = new AuthInitiateMessageV4();
87 BigInteger secretScalar = remotePublicKey.multiply(key.getPrivKey()).normalize().getXCoord().toBigInteger();
88 byte[] token = ByteUtil.bigIntegerToBytes(secretScalar, NONCE_SIZE);
89
90 byte[] nonce = initiatorNonce;
91 byte[] signed = xor(token, nonce);
92 message.setSignature(ECDSASignature.fromSignature(ephemeralKey.sign(signed)));
93 message.publicKey = key.getPubKeyPoint();
94 message.nonce = initiatorNonce;
95 return message;
96 }
97
98 public byte[] encryptAuthInitiateV4(AuthInitiateMessageV4 message) {
99
100 byte[] msg = message.encode();
101 byte[] padded = padEip8(msg);
102
103 return encryptAuthEIP8(padded);
104 }
105
106 public AuthInitiateMessageV4 decryptAuthInitiateV4(byte[] in, ECKey myKey) throws InvalidCipherTextException {
107 try {
108
109 byte[] prefix = new byte[2];
110 System.arraycopy(in, 0, prefix, 0, 2);
111 short size = ByteUtil.bigEndianToShort(prefix, 0);
112 byte[] ciphertext = new byte[size];
113 System.arraycopy(in, 2, ciphertext, 0, size);
114
115 byte[] plaintext = ECIESCoder.decrypt(myKey.getPrivKey(), ciphertext, prefix);
116
117 return AuthInitiateMessageV4.decode(plaintext);
118 } catch (InvalidCipherTextException e) {
119 throw e;
120 } catch (IOException e) {
121 throw Throwables.propagate(e);
122 }
123 }
124
125 public byte[] encryptAuthResponseV4(AuthResponseMessageV4 message) {
126
127 byte[] msg = message.encode();
128 byte[] padded = padEip8(msg);
129
130 return encryptAuthEIP8(padded);
131 }
132
133 public AuthResponseMessageV4 decryptAuthResponseV4(byte[] in, ECKey myKey) {
134 try {
135
136 byte[] prefix = new byte[2];
137 System.arraycopy(in, 0, prefix, 0, 2);
138 short size = ByteUtil.bigEndianToShort(prefix, 0);
139 byte[] ciphertext = new byte[size];
140 System.arraycopy(in, 2, ciphertext, 0, size);
141
142 byte[] plaintext = ECIESCoder.decrypt(myKey.getPrivKey(), ciphertext, prefix);
143
144 return AuthResponseMessageV4.decode(plaintext);
145 } catch (IOException | InvalidCipherTextException e) {
146 throw Throwables.propagate(e);
147 }
148 }
149
150 AuthResponseMessageV4 makeAuthInitiateV4(AuthInitiateMessageV4 initiate, ECKey key) {
151 initiatorNonce = initiate.nonce;
152 remotePublicKey = initiate.publicKey;
153 BigInteger secretScalar = remotePublicKey.multiply(key.getPrivKey()).normalize().getXCoord().toBigInteger();
154 byte[] token = ByteUtil.bigIntegerToBytes(secretScalar, NONCE_SIZE);
155 byte[] signed = xor(token, initiatorNonce);
156
157 ECKey ephemeral = Secp256k1.getInstance().recoverFromSignature(recIdFromSignatureV(initiate.getSignature().getV()),
158 initiate.getSignature(), signed, false);
159 if (ephemeral == null) {
160 throw new RuntimeException("failed to recover signatue from message");
161 }
162 remoteEphemeralKey = ephemeral.getPubKeyPoint();
163 AuthResponseMessageV4 response = new AuthResponseMessageV4();
164 response.ephemeralPublicKey = ephemeralKey.getPubKeyPoint();
165 response.nonce = responderNonce;
166 return response;
167 }
168
169 public AuthResponseMessageV4 handleAuthResponseV4(ECKey myKey, byte[] initiatePacket, byte[] responsePacket) {
170 AuthResponseMessageV4 response = decryptAuthResponseV4(responsePacket, myKey);
171 remoteEphemeralKey = response.ephemeralPublicKey;
172 responderNonce = response.nonce;
173 agreeSecret(initiatePacket, responsePacket);
174 return response;
175 }
176
177 byte[] encryptAuthEIP8(byte[] msg) {
178
179 short size = (short) (msg.length + ECIESCoder.getOverhead());
180 byte[] prefix = ByteUtil.shortToBytes(size);
181 byte[] encrypted = ECIESCoder.encrypt(remotePublicKey, msg, prefix);
182
183 byte[] out = new byte[prefix.length + encrypted.length];
184 int offset = 0;
185 System.arraycopy(prefix, 0, out, offset, prefix.length);
186 offset += prefix.length;
187 System.arraycopy(encrypted, 0, out, offset, encrypted.length);
188
189 return out;
190 }
191
192 /**
193 * Create a handshake auth message
194 *
195 * @param token previous token if we had a previous session
196 * @param key our private key
197 */
198 public AuthInitiateMessage createAuthInitiate(@Nullable byte[] token, ECKey key) {
199 AuthInitiateMessage message = new AuthInitiateMessage();
200 boolean isToken;
201 if (token == null) {
202 isToken = false;
203 BigInteger secretScalar = remotePublicKey.multiply(key.getPrivKey()).normalize().getXCoord().toBigInteger();
204 token = ByteUtil.bigIntegerToBytes(secretScalar, NONCE_SIZE);
205 } else {
206 isToken = true;
207 }
208
209 byte[] nonce = initiatorNonce;
210 byte[] signed = xor(token, nonce);
211 message.setSignature(ECDSASignature.fromSignature(ephemeralKey.sign(signed)));
212 message.isTokenUsed = isToken;
213 message.ephemeralPublicHash = keccak256(ephemeralKey.getPubKeyPoint().getEncoded(false), 1, 64);
214 message.publicKey = key.getPubKeyPoint();
215 message.nonce = initiatorNonce;
216 return message;
217 }
218
219 private static byte[] xor(byte[] b1, byte[] b2) {
220 Preconditions.checkArgument(b1.length == b2.length);
221 byte[] out = new byte[b1.length];
222 for (int i = 0; i < b1.length; i++) {
223 out[i] = (byte) (b1[i] ^ b2[i]);
224 }
225 return out;
226 }
227
228 public byte[] encryptAuthMessage(AuthInitiateMessage message) {
229 return ECIESCoder.encrypt(remotePublicKey, message.encode());
230 }
231
232 public byte[] encryptAuthResponse(AuthResponseMessage message) {
233 return ECIESCoder.encrypt(remotePublicKey, message.encode());
234 }
235
236 public AuthResponseMessage decryptAuthResponse(byte[] ciphertext, ECKey myKey) {
237 try {
238 byte[] plaintext = ECIESCoder.decrypt(myKey.getPrivKey(), ciphertext);
239 return AuthResponseMessage.decode(plaintext);
240 } catch (IOException | InvalidCipherTextException e) {
241 throw Throwables.propagate(e);
242 }
243 }
244
245 public AuthInitiateMessage decryptAuthInitiate(byte[] ciphertext, ECKey myKey) throws InvalidCipherTextException {
246 try {
247 byte[] plaintext = ECIESCoder.decrypt(myKey.getPrivKey(), ciphertext);
248 return AuthInitiateMessage.decode(plaintext);
249 } catch (InvalidCipherTextException e) {
250 throw e;
251 } catch (IOException e) {
252 throw Throwables.propagate(e);
253 }
254 }
255
256 public AuthResponseMessage handleAuthResponse(ECKey myKey, byte[] initiatePacket, byte[] responsePacket) {
257 AuthResponseMessage response = decryptAuthResponse(responsePacket, myKey);
258 remoteEphemeralKey = response.ephemeralPublicKey;
259 responderNonce = response.nonce;
260 agreeSecret(initiatePacket, responsePacket);
261 return response;
262 }
263
264 void agreeSecret(byte[] initiatePacket, byte[] responsePacket) {
265 BigInteger secretScalar = remoteEphemeralKey.multiply(ephemeralKey.getPrivKey()).normalize().getXCoord().toBigInteger();
266 byte[] agreedSecret = ByteUtil.bigIntegerToBytes(secretScalar, SECRET_SIZE);
267 byte[] sharedSecret = Keccak256Helper.keccak256(agreedSecret, Keccak256Helper.keccak256(responderNonce, initiatorNonce));
268 byte[] aesSecret = Keccak256Helper.keccak256(agreedSecret, sharedSecret);
269 secrets = new Secrets();
270 secrets.aes = aesSecret;
271 secrets.mac = Keccak256Helper.keccak256(agreedSecret, aesSecret);
272 secrets.token = Keccak256Helper.keccak256(sharedSecret);
273
274 KeccakDigest mac1 = new KeccakDigest(MAC_SIZE);
275 mac1.update(xor(secrets.mac, responderNonce), 0, secrets.mac.length);
276 byte[] buf = new byte[32];
277 new KeccakDigest(mac1).doFinal(buf, 0);
278 mac1.update(initiatePacket, 0, initiatePacket.length);
279 new KeccakDigest(mac1).doFinal(buf, 0);
280 KeccakDigest mac2 = new KeccakDigest(MAC_SIZE);
281 mac2.update(xor(secrets.mac, initiatorNonce), 0, secrets.mac.length);
282 new KeccakDigest(mac2).doFinal(buf, 0);
283 mac2.update(responsePacket, 0, responsePacket.length);
284 new KeccakDigest(mac2).doFinal(buf, 0);
285 if (isInitiator) {
286 secrets.egressMac = mac1;
287 secrets.ingressMac = mac2;
288 } else {
289 secrets.egressMac = mac2;
290 secrets.ingressMac = mac1;
291 }
292 }
293
294 public byte[] handleAuthInitiate(byte[] initiatePacket, ECKey key) throws InvalidCipherTextException {
295 AuthResponseMessage response = makeAuthInitiate(initiatePacket, key);
296 byte[] responsePacket = encryptAuthResponse(response);
297 agreeSecret(initiatePacket, responsePacket);
298 return responsePacket;
299 }
300
301 AuthResponseMessage makeAuthInitiate(byte[] initiatePacket, ECKey key) throws InvalidCipherTextException {
302 AuthInitiateMessage initiate = decryptAuthInitiate(initiatePacket, key);
303 return makeAuthInitiate(initiate, key);
304 }
305
306 AuthResponseMessage makeAuthInitiate(AuthInitiateMessage initiate, ECKey key) {
307 initiatorNonce = initiate.nonce;
308 remotePublicKey = initiate.publicKey;
309 BigInteger secretScalar = remotePublicKey.multiply(key.getPrivKey()).normalize().getXCoord().toBigInteger();
310 byte[] token = ByteUtil.bigIntegerToBytes(secretScalar, NONCE_SIZE);
311 byte[] signed = xor(token, initiatorNonce);
312
313 ECKey ephemeral = Secp256k1.getInstance().recoverFromSignature(recIdFromSignatureV(initiate.getSignature().getV()),
314 initiate.getSignature(), signed, false);
315 if (ephemeral == null) {
316 throw new RuntimeException("failed to recover signatue from message");
317 }
318 remoteEphemeralKey = ephemeral.getPubKeyPoint();
319 AuthResponseMessage response = new AuthResponseMessage();
320 response.isTokenUsed = initiate.isTokenUsed;
321 response.ephemeralPublicKey = ephemeralKey.getPubKeyPoint();
322 response.nonce = responderNonce;
323 return response;
324 }
325
326 /**
327 * Pads messages with junk data,
328 * pad data length is random value satisfying 100 < len < 300.
329 * It's necessary to make messages described by EIP-8 distinguishable from pre-EIP-8 msgs
330 *
331 * @param msg message to pad
332 * @return padded message
333 */
334 private byte[] padEip8(byte[] msg) {
335
336 byte[] paddedMessage = new byte[msg.length + random.nextInt(200) + 100];
337 random.nextBytes(paddedMessage);
338 System.arraycopy(msg, 0, paddedMessage, 0, msg.length);
339
340 return paddedMessage;
341 }
342
343 public static byte recIdFromSignatureV(int v) {
344 if (v >= 31) {
345 // compressed
346 v -= 4;
347 }
348 return (byte)(v - 27);
349 }
350
351 public Secrets getSecrets() {
352 return secrets;
353 }
354
355 public ECPoint getRemotePublicKey() {
356 return remotePublicKey;
357 }
358
359 public static class Secrets {
360 byte[] aes;
361 byte[] mac;
362 byte[] token;
363 KeccakDigest egressMac;
364 KeccakDigest ingressMac;
365
366 public byte[] getAes() {
367 return aes;
368 }
369
370 public byte[] getMac() {
371 return mac;
372 }
373
374 public byte[] getToken() {
375 return token;
376 }
377
378 public KeccakDigest getIngressMac() {
379 return ingressMac;
380 }
381
382 public KeccakDigest getEgressMac() {
383 return egressMac;
384 }
385 }
386
387 public boolean isInitiator() {
388 return isInitiator;
389 }
390 }