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 }