content: /**
   * Copyright (C) 2014-2016 Open Whisper Systems
   *
   * Licensed according to the LICENSE file in this repository.
   */
  package org.whispersystems.libsignal.ratchet;
  
  import org.whispersystems.libsignal.InvalidKeyException;
  import org.whispersystems.libsignal.ecc.Curve;
  import org.whispersystems.libsignal.ecc.ECKeyPair;
  import org.whispersystems.libsignal.ecc.ECPublicKey;
  import org.whispersystems.libsignal.kdf.HKDF;
  import org.whispersystems.libsignal.kdf.HKDFv3;
  import org.whispersystems.libsignal.protocol.CiphertextMessage;
  import org.whispersystems.libsignal.state.SessionState;
  import org.whispersystems.libsignal.util.ByteUtil;
  import org.whispersystems.libsignal.util.Pair;
  import org.whispersystems.libsignal.util.guava.Optional;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.Arrays;
  
  public class RatchetingSession {
  
    public static void initializeSession(SessionState sessionState, SymmetricSignalProtocolParameters parameters)
        throws InvalidKeyException
    {
      if (isAlice(parameters.getOurBaseKey().getPublicKey(), parameters.getTheirBaseKey())) {
        AliceSignalProtocolParameters.Builder aliceParameters = AliceSignalProtocolParameters.newBuilder();
  
        aliceParameters.setOurBaseKey(parameters.getOurBaseKey())
                       .setOurIdentityKey(parameters.getOurIdentityKey())
                       .setTheirRatchetKey(parameters.getTheirRatchetKey())
                       .setTheirIdentityKey(parameters.getTheirIdentityKey())
                       .setTheirSignedPreKey(parameters.getTheirBaseKey())
                       .setTheirOneTimePreKey(Optional.<ECPublicKey>absent());
  
        RatchetingSession.initializeSession(sessionState, aliceParameters.create());
      } else {
        BobSignalProtocolParameters.Builder bobParameters = BobSignalProtocolParameters.newBuilder();
  
        bobParameters.setOurIdentityKey(parameters.getOurIdentityKey())
                     .setOurRatchetKey(parameters.getOurRatchetKey())
                     .setOurSignedPreKey(parameters.getOurBaseKey())
                     .setOurOneTimePreKey(Optional.<ECKeyPair>absent())
                     .setTheirBaseKey(parameters.getTheirBaseKey())
                     .setTheirIdentityKey(parameters.getTheirIdentityKey());
  
        RatchetingSession.initializeSession(sessionState, bobParameters.create());
      }
    }
  
    public static void initializeSession(SessionState sessionState, AliceSignalProtocolParameters parameters)
        throws InvalidKeyException
    {
      try {
        sessionState.setSessionVersion(CiphertextMessage.CURRENT_VERSION);
        sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey());
        sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey());
  
        ECKeyPair             sendingRatchetKey = Curve.generateKeyPair();
        ByteArrayOutputStream secrets           = new ByteArrayOutputStream();
  
        secrets.write(getDiscontinuityBytes());
  
        secrets.write(Curve.calculateAgreement(parameters.getTheirSignedPreKey(),
                                               parameters.getOurIdentityKey().getPrivateKey()));
        secrets.write(Curve.calculateAgreement(parameters.getTheirIdentityKey().getPublicKey(),
                                               parameters.getOurBaseKey().getPrivateKey()));
        secrets.write(Curve.calculateAgreement(parameters.getTheirSignedPreKey(),
                                               parameters.getOurBaseKey().getPrivateKey()));
  
        if (parameters.getTheirOneTimePreKey().isPresent()) {
          secrets.write(Curve.calculateAgreement(parameters.getTheirOneTimePreKey().get(),
                                                 parameters.getOurBaseKey().getPrivateKey()));
        }
  
        DerivedKeys             derivedKeys  = calculateDerivedKeys(secrets.toByteArray());
        Pair<RootKey, ChainKey> sendingChain = derivedKeys.getRootKey().createChain(parameters.getTheirRatchetKey(), sendingRatchetKey);
  
        sessionState.addReceiverChain(parameters.getTheirRatchetKey(), derivedKeys.getChainKey());
        sessionState.setSenderChain(sendingRatchetKey, sendingChain.second());
        sessionState.setRootKey(sendingChain.first());
      } catch (IOException e) {
        throw new AssertionError(e);
      }
    }
  
    public static void initializeSession(SessionState sessionState, BobSignalProtocolParameters parameters)
        throws InvalidKeyException
    {
  
      try {
        sessionState.setSessionVersion(CiphertextMessage.CURRENT_VERSION);
        sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey());
        sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey());
  
        ByteArrayOutputStream secrets = new ByteArrayOutputStream();
  
        secrets.write(getDiscontinuityBytes());
  
        secrets.write(Curve.calculateAgreement(parameters.getTheirIdentityKey().getPublicKey(),
                                               parameters.getOurSignedPreKey().getPrivateKey()));
        secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
                                               parameters.getOurIdentityKey().getPrivateKey()));
        secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
                                               parameters.getOurSignedPreKey().getPrivateKey()));
  
        if (parameters.getOurOneTimePreKey().isPresent()) {
          secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(),
                                                 parameters.getOurOneTimePreKey().get().getPrivateKey()));
        }
  
        DerivedKeys derivedKeys = calculateDerivedKeys(secrets.toByteArray());
  
        sessionState.setSenderChain(parameters.getOurRatchetKey(), derivedKeys.getChainKey());
        sessionState.setRootKey(derivedKeys.getRootKey());
      } catch (IOException e) {
        throw new AssertionError(e);
      }
    }
  
    private static byte[] getDiscontinuityBytes() {
      byte[] discontinuity = new byte[32];
      Arrays.fill(discontinuity, (byte) 0xFF);
      return discontinuity;
    }
  
    private static DerivedKeys calculateDerivedKeys(byte[] masterSecret) {
      HKDF     kdf                = new HKDFv3();
      byte[]   derivedSecretBytes = kdf.deriveSecrets(masterSecret, "WhisperText".getBytes(), 64);
      byte[][] derivedSecrets     = ByteUtil.split(derivedSecretBytes, 32, 32);
  
      return new DerivedKeys(new RootKey(kdf, derivedSecrets[0]),
                             new ChainKey(kdf, derivedSecrets[1], 0));
    }
  
    private static boolean isAlice(ECPublicKey ourKey, ECPublicKey theirKey) {
      return ourKey.compareTo(theirKey) < 0;
    }
  
    private static class DerivedKeys {
      private final RootKey   rootKey;
      private final ChainKey  chainKey;
  
      private DerivedKeys(RootKey rootKey, ChainKey chainKey) {
        this.rootKey   = rootKey;
        this.chainKey  = chainKey;
      }
  
      public RootKey getRootKey() {
        return rootKey;
      }
  
      public ChainKey getChainKey() {
        return chainKey;
      }
    }
  }
  