Home | History | Annotate | Download | only in securegcm
      1 /* Copyright 2018 Google LLC
      2  *
      3  * Licensed under the Apache License, Version 2.0 (the "License");
      4  * you may not use this file except in compliance with the License.
      5  * You may obtain a copy of the License at
      6  *
      7  *     https://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software
     10  * distributed under the License is distributed on an "AS IS" BASIS,
     11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12  * See the License for the specific language governing permissions and
     13  * limitations under the License.
     14  */
     15 package com.google.security.cryptauth.lib.securegcm;
     16 
     17 import com.google.common.annotations.VisibleForTesting;
     18 import com.google.protobuf.ByteString;
     19 import com.google.protobuf.InvalidProtocolBufferException;
     20 import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.DeviceToDeviceMessage;
     21 import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.EcPoint;
     22 import com.google.security.cryptauth.lib.securegcm.DeviceToDeviceMessagesProto.SpakeHandshakeMessage;
     23 import com.google.security.cryptauth.lib.securegcm.Ed25519.Ed25519Exception;
     24 import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.Payload;
     25 import com.google.security.cryptauth.lib.securegcm.TransportCryptoOps.PayloadType;
     26 import java.math.BigInteger;
     27 import java.security.InvalidKeyException;
     28 import java.security.MessageDigest;
     29 import java.security.NoSuchAlgorithmException;
     30 import java.security.SecureRandom;
     31 import java.security.SignatureException;
     32 import javax.crypto.spec.SecretKeySpec;
     33 
     34 /**
     35  * Implements a {@link D2DHandshakeContext} by using SPAKE2 (Simple Password-Based Encrypted Key
     36  * Exchange Protocol) on top of the Ed25519 curve.
     37  * SPAKE2: http://www.di.ens.fr/~mabdalla/papers/AbPo05a-letter.pdf
     38  * Ed25519: http://ed25519.cr.yp.to/
     39  *
     40  * <p>Usage:
     41  * {@code
     42  *   // initiator:
     43  *   D2DHandshakeContext initiatorHandshakeContext =
     44  *       D2DSpakeEd25519Handshake.forInitiator(PASSWORD);
     45  *   byte[] initiatorMsg = initiatorHandshakeContext.getNextHandshakeMessage();
     46  *   // (send initiatorMsg to responder)
     47  *
     48  *   // responder:
     49  *   D2DHandshakeContext responderHandshakeContext =
     50  *       D2DSpakeEd25519Handshake.forResponder(PASSWORD);
     51  *   responderHandshakeContext.parseHandshakeMessage(initiatorMsg);
     52  *   byte[] responderMsg = responderHandshakeContext.getNextHandshakeMessage();
     53  *   // (send responderMsg to initiator)
     54  *
     55  *   // initiator:
     56  *   initiatorHandshakeContext.parseHandshakeMessage(responderMsg);
     57  *   initiatorMsg = initiatorHandshakeContext.getNextHandshakeMessage();
     58  *   // (send initiatorMsg to responder)
     59  *
     60  *   // responder:
     61  *   responderHandshakeContext.parseHandshakeMessage(initiatorMsg);
     62  *   responderMsg = responderHandshakeContext.getNextHandshakeMessage();
     63  *   D2DConnectionContext responderCtx = responderHandshakeContext.toConnectionContext();
     64  *   // (send responderMsg to initiator)
     65  *
     66  *   // initiator:
     67  *   initiatorHandshakeContext.parseHandshakeMessage(responderMsg);
     68  *   D2DConnectionContext initiatorCtx = initiatorHandshakeContext.toConnectionContext();
     69  * }
     70  *
     71  * <p>The initial computation is:
     72  *   Initiator                                              Responder
     73  *    has KM (pre-agreed point)                              has KM (pre-agreed point)
     74  *    has KN (pre-agreed point)                              has KN (pre-agreed point)
     75  *    has Password (pre-agreed)                              has Password (pre-agreed)
     76  *    picks random scalar Xi (private key)                   picks random scalar Xr (private key)
     77  *    computes the public key Pxi = G*Xi                     computes the public key Pxr = G*Xr
     78  *    computes commitment:                                   computes commitment:
     79  *      Ci = KM * password + Pxi                               Cr = KN * password + Pxr
     80  *
     81  * <p>The flow is:
     82  *   Initiator                                              Responder
     83  *              ----- Ci --------------------------------->
     84  *              <--------------------------------- Cr -----
     85  *   computes shared key K:                                 computes shared key K:
     86  *    (Cr - KN*password) * Xi                                (Ci - KM*password) * Xr
     87  *   computes hash:                                         computes hash:
     88  *    Hi = sha256(0|Cr|Ci|K)                                 Hr = sha256(1|Ci|Cr|K)
     89  *              ----- Hi --------------------------------->
     90  *                                                          Verify Hi
     91  *              <-------------- Hr (optional payload) -----
     92  *   Verify Hr
     93  */
     94 public class D2DSpakeEd25519Handshake implements D2DHandshakeContext {
     95   // Minimum length password that is acceptable for the handshake
     96   public static final int MIN_PASSWORD_LENGTH = 4;
     97   /**
     98    * Creates a new SPAKE handshake object for the initiator.
     99    *
    100    * @param password the password that should be used in the handshake.  Note that this should be
    101    *     at least {@value #MIN_PASSWORD_LENGTH} bytes long
    102    */
    103   public static D2DSpakeEd25519Handshake forInitiator(byte[] password) throws HandshakeException {
    104     return new D2DSpakeEd25519Handshake(State.INITIATOR_START, password);
    105   }
    106 
    107   /**
    108    * Creates a new SPAKE handshake object for the responder.
    109    *
    110    * @param password the password that should be used in the handshake.  Note that this should be
    111    *     at least {@value #MIN_PASSWORD_LENGTH} bytes long
    112    */
    113   public static D2DSpakeEd25519Handshake forResponder(byte[] password) throws HandshakeException {
    114     return new D2DSpakeEd25519Handshake(State.RESPONDER_START, password);
    115   }
    116 
    117   //
    118   // The protocol requires two verifiable, randomly generated group point. They were generated
    119   // using the python code below. The algorithm is to first pick a random y in the group and solve
    120   // the elliptic curve equation for a value of x, if possible. We then use (x, y) as the random
    121   // point.
    122   // Source of ed25519 is here: http://ed25519.cr.yp.to/python/ed25519.py
    123   // import ed25519
    124   // import hashlib
    125   //
    126   // # Seeds
    127   // seed1 = 'D2D Ed25519 point generation seed (M)'
    128   // seed2 = 'D2D Ed25519 point generation seed (N)'
    129   //
    130   // def find_seed(seed):
    131   //   # generate a random scalar for the y coordinate
    132   //   y = hashlib.sha256(seed).hexdigest()
    133   //
    134   //   P = ed25519.scalarmult(ed25519.B, int(y, 16) % ed25519.q)
    135   //   if (not ed25519.isoncurve(P)):
    136   //       print 'Wat? P should be on curve!'
    137   //
    138   //   print '  x: ' + hex(P[0])
    139   //   print '  y: ' + hex(P[1])
    140   //   print
    141   //
    142   // find_seed(seed1)
    143   // find_seed(seed2)
    144   //
    145   // Output is:
    146   //   x: 0x1981fb43f103290ecf9772022db8b19bfaf389057ed91e8486eb368763435925L
    147   //   y: 0xa714c34f3b588aac92fd2587884a20964fd351a1f147d5c4bbf5c2f37a77c36L
    148   //
    149   //   x: 0x201a184f47d9a7973891d148e3d1c864d8084547131c2c1cefb7eebd26c63567L
    150   //   y: 0x6da2d3b18ec4f9aa3b08e39c997cd8bf6e9948ffd4feffecaf8dd0b3d648b7e8L
    151   //
    152   // To get extended representation X, Y, Z, T, do: Z = 1, T = X*Y mod P
    153   @VisibleForTesting
    154   static final BigInteger[] KM = new BigInteger[] {
    155     new BigInteger(new byte[] {(byte) 0x19, (byte) 0x81, (byte) 0xFB, (byte) 0x43,
    156         (byte) 0xF1, (byte) 0x03, (byte) 0x29, (byte) 0x0E, (byte) 0xCF, (byte) 0x97,
    157         (byte) 0x72, (byte) 0x02, (byte) 0x2D, (byte) 0xB8, (byte) 0xB1, (byte) 0x9B,
    158         (byte) 0xFA, (byte) 0xF3, (byte) 0x89, (byte) 0x05, (byte) 0x7E, (byte) 0xD9,
    159         (byte) 0x1E, (byte) 0x84, (byte) 0x86, (byte) 0xEB, (byte) 0x36, (byte) 0x87,
    160         (byte) 0x63, (byte) 0x43, (byte) 0x59, (byte) 0x25}),
    161     new BigInteger(new byte[] {(byte) 0x0A, (byte) 0x71, (byte) 0x4C, (byte) 0x34,
    162         (byte) 0xF3, (byte) 0xB5, (byte) 0x88, (byte) 0xAA, (byte) 0xC9, (byte) 0x2F,
    163         (byte) 0xD2, (byte) 0x58, (byte) 0x78, (byte) 0x84, (byte) 0xA2, (byte) 0x09,
    164         (byte) 0x64, (byte) 0xFD, (byte) 0x35, (byte) 0x1A, (byte) 0x1F, (byte) 0x14,
    165         (byte) 0x7D, (byte) 0x5C, (byte) 0x4B, (byte) 0xBF, (byte) 0x5C, (byte) 0x2F,
    166         (byte) 0x37, (byte) 0xA7, (byte) 0x7C, (byte) 0x36}),
    167     BigInteger.ONE,
    168     new BigInteger(new byte[] {(byte) 0x04, (byte) 0x8F, (byte) 0xC1, (byte) 0xCE,
    169         (byte) 0xE5, (byte) 0x83, (byte) 0x99, (byte) 0x25, (byte) 0xE5, (byte) 0x9B,
    170         (byte) 0x80, (byte) 0xEA, (byte) 0xAD, (byte) 0x82, (byte) 0xAC, (byte) 0x0A,
    171         (byte) 0x3C, (byte) 0xFE, (byte) 0xC5, (byte) 0x60, (byte) 0x93, (byte) 0x59,
    172         (byte) 0x8B, (byte) 0x48, (byte) 0x44, (byte) 0xDD, (byte) 0x2A, (byte) 0x3E,
    173         (byte) 0x24, (byte) 0x5D, (byte) 0x88, (byte) 0x33})};
    174 
    175   @VisibleForTesting
    176   static final BigInteger[] KN = new BigInteger[] {
    177     new BigInteger(new byte[] {(byte) 0x20, (byte) 0x1A, (byte) 0x18, (byte) 0x4F,
    178         (byte) 0x47, (byte) 0xD9, (byte) 0xA7, (byte) 0x97, (byte) 0x38, (byte) 0x91,
    179         (byte) 0xD1, (byte) 0x48, (byte) 0xE3, (byte) 0xD1, (byte) 0xC8, (byte) 0x64,
    180         (byte) 0xD8, (byte) 0x08, (byte) 0x45, (byte) 0x47, (byte) 0x13, (byte) 0x1C,
    181         (byte) 0x2C, (byte) 0x1C, (byte) 0xEF, (byte) 0xB7, (byte) 0xEE, (byte) 0xBD,
    182         (byte) 0x26, (byte) 0xC6, (byte) 0x35, (byte) 0x67}),
    183     new BigInteger(new byte[] {(byte) 0x6D, (byte) 0xA2, (byte) 0xD3, (byte) 0xB1,
    184         (byte) 0x8E, (byte) 0xC4, (byte) 0xF9, (byte) 0xAA, (byte) 0x3B, (byte) 0x08,
    185         (byte) 0xE3, (byte) 0x9C, (byte) 0x99, (byte) 0x7C, (byte) 0xD8, (byte) 0xBF,
    186         (byte) 0x6E, (byte) 0x99, (byte) 0x48, (byte) 0xFF, (byte) 0xD4, (byte) 0xFE,
    187         (byte) 0xFF, (byte) 0xEC, (byte) 0xAF, (byte) 0x8D, (byte) 0xD0, (byte) 0xB3,
    188         (byte) 0xD6, (byte) 0x48, (byte) 0xB7, (byte) 0xE8}),
    189     BigInteger.ONE,
    190     new BigInteger(new byte[] {(byte) 0x16, (byte) 0x40, (byte) 0xED, (byte) 0x5A,
    191         (byte) 0x54, (byte) 0xFA, (byte) 0x0B, (byte) 0x07, (byte) 0x22, (byte) 0x86,
    192         (byte) 0xE9, (byte) 0xD2, (byte) 0x2F, (byte) 0x46, (byte) 0x47, (byte) 0x63,
    193         (byte) 0xFB, (byte) 0xF6, (byte) 0x0D, (byte) 0x79, (byte) 0x1D, (byte) 0x37,
    194         (byte) 0xB9, (byte) 0x09, (byte) 0x3B, (byte) 0x58, (byte) 0x4D, (byte) 0xF4,
    195         (byte) 0xC9, (byte) 0x95, (byte) 0xF7, (byte) 0x81})};
    196 
    197   // Base point B as per ed25519.cr.yp.to
    198   @VisibleForTesting
    199   /* package */ static final BigInteger[] B = new BigInteger[] {
    200     new BigInteger(new byte[] {(byte) 0x21, (byte) 0x69, (byte) 0x36, (byte) 0xD3,
    201         (byte) 0xCD, (byte) 0x6E, (byte) 0x53, (byte) 0xFE, (byte) 0xC0, (byte) 0xA4,
    202         (byte) 0xE2, (byte) 0x31, (byte) 0xFD, (byte) 0xD6, (byte) 0xDC, (byte) 0x5C,
    203         (byte) 0x69, (byte) 0x2C, (byte) 0xC7, (byte) 0x60, (byte) 0x95, (byte) 0x25,
    204         (byte) 0xA7, (byte) 0xB2, (byte) 0xC9, (byte) 0x56, (byte) 0x2D, (byte) 0x60,
    205         (byte) 0x8F, (byte) 0x25, (byte) 0xD5, (byte) 0x1A}),
    206     new BigInteger(new byte[] {(byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66,
    207         (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66,
    208         (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66,
    209         (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66,
    210         (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x66,
    211         (byte) 0x66, (byte) 0x66, (byte) 0x66, (byte) 0x58}),
    212     BigInteger.ONE,
    213     new BigInteger(new byte[] {(byte) 0x67, (byte) 0x87, (byte) 0x5F, (byte) 0x0F,
    214         (byte) 0xD7, (byte) 0x8B, (byte) 0x76, (byte) 0x65, (byte) 0x66, (byte) 0xEA,
    215         (byte) 0x4E, (byte) 0x8E, (byte) 0x64, (byte) 0xAB, (byte) 0xE3, (byte) 0x7D,
    216         (byte) 0x20, (byte) 0xF0, (byte) 0x9F, (byte) 0x80, (byte) 0x77, (byte) 0x51,
    217         (byte) 0x52, (byte) 0xF5, (byte) 0x6D, (byte) 0xDE, (byte) 0x8A, (byte) 0xB3,
    218         (byte) 0xA5, (byte) 0xB7, (byte) 0xDD, (byte) 0xA3})};
    219 
    220   // Number of bits needed to represent a point
    221   private static final int POINT_SIZE_BITS = 256;
    222 
    223   // Java Message Digest name for SHA 256
    224   private static final String SHA256 = "SHA-256";
    225 
    226   // Pre-shared password hash represented as an integer
    227   private BigInteger passwordHash;
    228 
    229   // Current state of the handshake
    230   private State handshakeState;
    231 
    232   // Derived shared key
    233   private byte[] sharedKey;
    234 
    235   // Private key (random scalar)
    236   private BigInteger valueX;
    237 
    238   // Public key (random point, in extended notation, based on valueX)
    239   private BigInteger[] pointX;
    240 
    241   // Commitment we've received from the other party (their password-authenticated public key)
    242   private BigInteger[] theirCommitmentPointAffine;
    243   private BigInteger[] theirCommitmentPointExtended;
    244 
    245   // Commitment we've sent to the other party (our password-authenticated public key)
    246   private BigInteger[] ourCommitmentPointAffine;
    247   private BigInteger[] ourCommitmentPointExtended;
    248 
    249   private enum State {
    250     // Initiator state
    251     INITIATOR_START,
    252     INITIATOR_WAITING_FOR_RESPONDER_COMMITMENT,
    253     INITIATOR_AFTER_RESPONDER_COMMITMENT,
    254     INITIATOR_WAITING_FOR_RESPONDER_HASH,
    255 
    256     // Responder state
    257     RESPONDER_START,
    258     RESPONDER_AFTER_INITIATOR_COMMITMENT,
    259     RESPONDER_WAITING_FOR_INITIATOR_HASH,
    260     RESPONDER_AFTER_INITIATOR_HASH,
    261 
    262     // Common completion state
    263     HANDSHAKE_FINISHED,
    264     HANDSHAKE_ALREADY_USED
    265   }
    266 
    267   @VisibleForTesting
    268   D2DSpakeEd25519Handshake(State state, byte[] password) throws HandshakeException {
    269     if (password == null || password.length < MIN_PASSWORD_LENGTH) {
    270       throw new HandshakeException("Passwords must be at least " + MIN_PASSWORD_LENGTH + " bytes");
    271     }
    272 
    273     handshakeState = state;
    274     passwordHash = new BigInteger(1 /* positive */, hash(password));
    275 
    276     do {
    277       valueX = new BigInteger(POINT_SIZE_BITS, new SecureRandom());
    278     } while (valueX.equals(BigInteger.ZERO));
    279 
    280     try {
    281       pointX = Ed25519.scalarMultiplyExtendedPoint(B, valueX);
    282     } catch (Ed25519Exception e) {
    283       throw new HandshakeException("Could not make public key point", e);
    284     }
    285   }
    286 
    287   @Override
    288   public boolean isHandshakeComplete() {
    289     switch (handshakeState) {
    290       case HANDSHAKE_FINISHED:
    291         // fall-through intentional
    292       case HANDSHAKE_ALREADY_USED:
    293         return true;
    294 
    295       default:
    296         return false;
    297     }
    298   }
    299 
    300   @Override
    301   public byte[] getNextHandshakeMessage() throws HandshakeException {
    302     byte[] nextMessage;
    303 
    304     switch(handshakeState) {
    305       case INITIATOR_START:
    306         nextMessage = makeCommitmentPointMessage(true /* is initiator */);
    307         handshakeState = State.INITIATOR_WAITING_FOR_RESPONDER_COMMITMENT;
    308         break;
    309 
    310       case RESPONDER_AFTER_INITIATOR_COMMITMENT:
    311         nextMessage = makeCommitmentPointMessage(false /* is initiator */);
    312         handshakeState = State.RESPONDER_WAITING_FOR_INITIATOR_HASH;
    313         break;
    314 
    315       case INITIATOR_AFTER_RESPONDER_COMMITMENT:
    316         nextMessage = makeSharedKeyHashMessage(true /* is initiator */, null /* no payload */);
    317         handshakeState = State.INITIATOR_WAITING_FOR_RESPONDER_HASH;
    318         break;
    319 
    320       case RESPONDER_AFTER_INITIATOR_HASH:
    321         nextMessage = makeSharedKeyHashMessage(false /* is initiator */, null /* no payload */);
    322         handshakeState = State.HANDSHAKE_FINISHED;
    323         break;
    324 
    325       default:
    326         throw new HandshakeException("Cannot get next message in state: " + handshakeState);
    327     }
    328 
    329     return nextMessage;
    330   }
    331 
    332   @Override
    333   public byte[] getNextHandshakeMessage(byte[] payload) throws HandshakeException {
    334     byte[] nextMessage;
    335 
    336     switch (handshakeState) {
    337       case RESPONDER_AFTER_INITIATOR_HASH:
    338         nextMessage = makeSharedKeyHashMessage(false /* is initiator */, payload);
    339         handshakeState = State.HANDSHAKE_FINISHED;
    340         break;
    341 
    342       default:
    343         throw new HandshakeException(
    344             "Cannot send handshake message with payload in state: " + handshakeState);
    345     }
    346 
    347     return nextMessage;
    348   }
    349 
    350   private byte[] makeCommitmentPointMessage(boolean isInitiator) throws HandshakeException {
    351     try {
    352       ourCommitmentPointExtended =
    353           Ed25519.scalarMultiplyExtendedPoint(isInitiator ? KM : KN, passwordHash);
    354       ourCommitmentPointExtended = Ed25519.addExtendedPoints(ourCommitmentPointExtended, pointX);
    355       ourCommitmentPointAffine = Ed25519.toAffine(ourCommitmentPointExtended);
    356 
    357       return SpakeHandshakeMessage.newBuilder()
    358           .setEcPoint(
    359               EcPoint.newBuilder()
    360                 .setCurve(DeviceToDeviceMessagesProto.Curve.ED_25519)
    361                 .setX(ByteString.copyFrom(ourCommitmentPointAffine[0].toByteArray()))
    362                 .setY(ByteString.copyFrom(ourCommitmentPointAffine[1].toByteArray()))
    363                 .build())
    364           .setFlowNumber(isInitiator ? 1 : 2 /* first or second message */)
    365           .build()
    366           .toByteArray();
    367     } catch (Ed25519Exception e) {
    368       throw new HandshakeException("Could not make commitment point message", e);
    369     }
    370   }
    371 
    372   private void makeSharedKey(boolean isInitiator) throws HandshakeException {
    373 
    374     if (handshakeState != State.RESPONDER_START
    375         && handshakeState != State.INITIATOR_WAITING_FOR_RESPONDER_COMMITMENT) {
    376       throw new HandshakeException("Cannot make shared key in state: " + handshakeState);
    377     }
    378 
    379     try {
    380       BigInteger[] kNMP = Ed25519.scalarMultiplyExtendedPoint(isInitiator ? KN : KM, passwordHash);
    381 
    382       // TheirPublicKey = TheirCommitment - kNMP = (TheirPublicKey + kNMP) - kNMP
    383       BigInteger[] theirPublicKey =
    384           Ed25519.subtractExtendedPoints(theirCommitmentPointExtended, kNMP);
    385 
    386       BigInteger[] sharedKeyPoint = Ed25519.scalarMultiplyExtendedPoint(theirPublicKey, valueX);
    387       sharedKey = hash(pointToByteArray(Ed25519.toAffine(sharedKeyPoint)));
    388     } catch (Ed25519Exception e) {
    389       throw new HandshakeException("Error computing shared key", e);
    390     }
    391   }
    392 
    393   private byte[] makeSharedKeyHashMessage(boolean isInitiator, byte[] payload)
    394       throws HandshakeException {
    395     SpakeHandshakeMessage.Builder handshakeMessage = SpakeHandshakeMessage.newBuilder()
    396         .setHashValue(ByteString.copyFrom(computeOurKeyHash(isInitiator)))
    397         .setFlowNumber(isInitiator ? 3 : 4 /* third or fourth message */);
    398 
    399     if (canSendPayloadInHandshakeMessage() && payload != null) {
    400       DeviceToDeviceMessage deviceToDeviceMessage =
    401           D2DConnectionContext.createDeviceToDeviceMessage(payload, 1 /* sequence number */);
    402       try {
    403         handshakeMessage.setPayload(ByteString.copyFrom(
    404             D2DCryptoOps.signcryptPayload(
    405               new Payload(PayloadType.DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD,
    406                   deviceToDeviceMessage.toByteArray()),
    407               new SecretKeySpec(sharedKey, "AES"))));
    408       } catch (InvalidKeyException | NoSuchAlgorithmException e) {
    409         throw new HandshakeException("Cannot set payload", e);
    410       }
    411     }
    412 
    413     return handshakeMessage.build().toByteArray();
    414   }
    415 
    416   private byte[] computeOurKeyHash(boolean isInitiator) throws HandshakeException {
    417     return hash(concat(
    418         new byte[] { (byte) (isInitiator ? 0 : 1) },
    419         pointToByteArray(theirCommitmentPointAffine),
    420         pointToByteArray(ourCommitmentPointAffine),
    421         sharedKey));
    422   }
    423 
    424   private byte[] computeTheirKeyHash(boolean isInitiator) throws HandshakeException {
    425     return hash(concat(
    426         new byte[] { (byte) (isInitiator ? 1 : 0) },
    427         pointToByteArray(ourCommitmentPointAffine),
    428         pointToByteArray(theirCommitmentPointAffine),
    429         sharedKey));
    430   }
    431 
    432   private byte[] pointToByteArray(BigInteger[] p) {
    433     return concat(p[0].toByteArray(), p[1].toByteArray());
    434   }
    435 
    436   @Override
    437   public boolean canSendPayloadInHandshakeMessage() {
    438     return handshakeState == State.RESPONDER_AFTER_INITIATOR_HASH;
    439   }
    440 
    441   @Override
    442   public byte[] parseHandshakeMessage(byte[] handshakeMessage) throws HandshakeException {
    443     if (handshakeMessage == null || handshakeMessage.length == 0) {
    444       throw new HandshakeException("Handshake message too short");
    445     }
    446 
    447     byte[] payload = new byte[0];
    448 
    449     switch(handshakeState) {
    450       case RESPONDER_START:
    451         // no payload can be sent in this message
    452         parseCommitmentMessage(handshakeMessage, false /* is initiator */);
    453         makeSharedKey(false /* is initiator */);
    454         handshakeState = State.RESPONDER_AFTER_INITIATOR_COMMITMENT;
    455         break;
    456 
    457       case INITIATOR_WAITING_FOR_RESPONDER_COMMITMENT:
    458         // no payload can be sent in this message
    459         parseCommitmentMessage(handshakeMessage, true /* is initiator */);
    460         makeSharedKey(true /* is initiator */);
    461         handshakeState = State.INITIATOR_AFTER_RESPONDER_COMMITMENT;
    462         break;
    463 
    464       case RESPONDER_WAITING_FOR_INITIATOR_HASH:
    465         // no payload can be sent in this message
    466         parseHashMessage(handshakeMessage, false /* is initiator */);
    467         handshakeState = State.RESPONDER_AFTER_INITIATOR_HASH;
    468         break;
    469 
    470       case INITIATOR_WAITING_FOR_RESPONDER_HASH:
    471         payload = parseHashMessage(handshakeMessage, true /* is initiator */);
    472         handshakeState = State.HANDSHAKE_FINISHED;
    473         break;
    474 
    475       default:
    476         throw new HandshakeException("Cannot parse message in state: " + handshakeState);
    477     }
    478 
    479     return payload;
    480   }
    481 
    482   private byte[] parseHashMessage(byte[] handshakeMessage, boolean isInitiator)
    483       throws HandshakeException {
    484     SpakeHandshakeMessage hashMessage;
    485 
    486     // Parse the message
    487     try {
    488       hashMessage = SpakeHandshakeMessage.parseFrom(handshakeMessage);
    489     } catch (InvalidProtocolBufferException e) {
    490       throw new HandshakeException("Could not parse hash message", e);
    491     }
    492 
    493     // Check flow number
    494     if (!hashMessage.hasFlowNumber()) {
    495       throw new HandshakeException("Hash message missing flow number");
    496     }
    497     int expectedFlowNumber = isInitiator ? 4 : 3;
    498     int actualFlowNumber = hashMessage.getFlowNumber();
    499     if (actualFlowNumber != expectedFlowNumber) {
    500       throw new HandshakeException("Hash message has flow number " + actualFlowNumber
    501           + ", but expected flow number " + expectedFlowNumber);
    502     }
    503 
    504     // Check and extract hash
    505     if (!hashMessage.hasHashValue()) {
    506       throw new HandshakeException("Hash message missing hash value");
    507     }
    508 
    509     byte[] theirHash = hashMessage.getHashValue().toByteArray();
    510     byte[] theirCorrectHash = computeTheirKeyHash(isInitiator);
    511 
    512     if (!constantTimeArrayEquals(theirCorrectHash, theirHash)) {
    513       throw new HandshakeException("Hash message had incorrect hash value");
    514     }
    515 
    516     if (isInitiator && hashMessage.hasPayload()) {
    517       try {
    518         DeviceToDeviceMessage message = D2DCryptoOps.decryptResponderHelloMessage(
    519             new SecretKeySpec(sharedKey, "AES"),
    520             hashMessage.getPayload().toByteArray());
    521 
    522         if (message.getSequenceNumber() != 1) {
    523           throw new HandshakeException("Incorrect sequence number in responder hello");
    524         }
    525 
    526         return message.getMessage().toByteArray();
    527 
    528       } catch (SignatureException e) {
    529         throw new HandshakeException("Error recovering payload from hash message", e);
    530       }
    531     }
    532 
    533     // empty/no payload
    534     return new byte[0];
    535   }
    536 
    537   private void parseCommitmentMessage(byte[] handshakeMessage, boolean isInitiator)
    538       throws HandshakeException {
    539     SpakeHandshakeMessage commitmentMessage;
    540 
    541     // Parse the message
    542     try {
    543       commitmentMessage = SpakeHandshakeMessage.parseFrom(handshakeMessage);
    544     } catch (InvalidProtocolBufferException e) {
    545       throw new HandshakeException("Could not parse commitment message", e);
    546     }
    547 
    548     // Check flow number
    549     if (!commitmentMessage.hasFlowNumber()) {
    550       throw new HandshakeException("Commitment message missing flow number");
    551     }
    552     if (commitmentMessage.getFlowNumber() != (isInitiator ? 2 : 1)) {
    553       throw new HandshakeException("Commitment message has wrong flow number");
    554     }
    555 
    556     // Check point and curve; and extract point
    557     if (!commitmentMessage.hasEcPoint()) {
    558       throw new HandshakeException("Commitment message missing point");
    559     }
    560     EcPoint commitmentPoint = commitmentMessage.getEcPoint();
    561     if (!commitmentPoint.hasCurve()
    562         || commitmentPoint.getCurve() != DeviceToDeviceMessagesProto.Curve.ED_25519) {
    563       throw new HandshakeException("Commitment message has wrong curve");
    564     }
    565 
    566     if (!commitmentPoint.hasX()) {
    567       throw new HandshakeException("Commitment point missing x coordinate");
    568     }
    569 
    570     if (!commitmentPoint.hasY()) {
    571       throw new HandshakeException("Commitment point missing y coordinate");
    572     }
    573 
    574     // Build the point
    575     theirCommitmentPointAffine = new BigInteger[] {
    576         new BigInteger(commitmentPoint.getX().toByteArray()),
    577         new BigInteger(commitmentPoint.getY().toByteArray())
    578     };
    579 
    580     // Validate the point to be sure
    581     try {
    582       Ed25519.validateAffinePoint(theirCommitmentPointAffine);
    583       theirCommitmentPointExtended = Ed25519.toExtended(theirCommitmentPointAffine);
    584     } catch (Ed25519Exception e) {
    585       throw new HandshakeException("Error validating their commitment point", e);
    586     }
    587   }
    588 
    589   @Override
    590   public D2DConnectionContext toConnectionContext() throws HandshakeException {
    591     if (handshakeState == State.HANDSHAKE_ALREADY_USED) {
    592       throw new HandshakeException("Cannot reuse handshake context; is has already been used");
    593     }
    594 
    595     if (!isHandshakeComplete()) {
    596       throw new HandshakeException("Handshake is not complete; cannot create connection context");
    597     }
    598 
    599     handshakeState = State.HANDSHAKE_ALREADY_USED;
    600 
    601     // Both sides start with an initial sequence number of 1 because the last message of the
    602     // handshake had an optional payload with sequence number 1.  D2DConnectionContext remembers
    603     // the last sequence number used.
    604     return new D2DConnectionContextV0(
    605         new SecretKeySpec(sharedKey, "AES"), 1 /* initialSequenceNumber */);
    606   }
    607 
    608   /**
    609    * Implementation of byte array concatenation copied from Guava.
    610    */
    611   private static byte[] concat(byte[]... arrays) {
    612     int length = 0;
    613     for (byte[] array : arrays) {
    614       length += array.length;
    615     }
    616 
    617     byte[] result = new byte[length];
    618     int pos = 0;
    619     for (byte[] array : arrays) {
    620       System.arraycopy(array, 0, result, pos, array.length);
    621       pos += array.length;
    622     }
    623 
    624     return result;
    625   }
    626 
    627   private static byte[] hash(byte[] message) throws HandshakeException {
    628     try {
    629       return MessageDigest.getInstance(SHA256).digest(message);
    630     } catch (NoSuchAlgorithmException e) {
    631       throw new HandshakeException("Error performing hash", e);
    632     }
    633   }
    634 
    635   private static boolean constantTimeArrayEquals(byte[] a, byte[] b) {
    636     if (a == null || b == null) {
    637       return (a == b);
    638     }
    639     if (a.length != b.length) {
    640       return false;
    641     }
    642     byte result = 0;
    643     for (int i = 0; i < b.length; i++) {
    644       result = (byte) (result | (a[i] ^ b[i]));
    645     }
    646     return (result == 0);
    647   }
    648 }
    649