diff options
author | Moxie Marlinspike <moxie@thoughtcrime.org> | 2014-11-24 12:54:30 -0800 |
---|---|---|
committer | Moxie Marlinspike <moxie@thoughtcrime.org> | 2014-11-24 12:54:30 -0800 |
commit | 60800e155612bea797eed93c67046a23d26054cc (patch) | |
tree | d88368c1c26162e27e790195133ca2b526597afe /src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java |
Break out into separate repo.
Diffstat (limited to 'src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java')
-rw-r--r-- | src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java b/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java new file mode 100644 index 00000000..8c094ec0 --- /dev/null +++ b/src/main/java/org/whispersystems/libaxolotl/ratchet/RatchetingSession.java @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2014 Open Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +package org.whispersystems.libaxolotl.ratchet; + +import org.whispersystems.libaxolotl.InvalidKeyException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.ecc.ECPublicKey; +import org.whispersystems.libaxolotl.kdf.HKDF; +import org.whispersystems.libaxolotl.state.SessionState; +import org.whispersystems.libaxolotl.util.ByteUtil; +import org.whispersystems.libaxolotl.util.Pair; +import org.whispersystems.libaxolotl.util.guava.Optional; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public class RatchetingSession { + + public static void initializeSession(SessionState sessionState, + int sessionVersion, + SymmetricAxolotlParameters parameters) + throws InvalidKeyException + { + if (isAlice(parameters.getOurBaseKey().getPublicKey(), parameters.getTheirBaseKey())) { + AliceAxolotlParameters.Builder aliceParameters = AliceAxolotlParameters.newBuilder(); + + aliceParameters.setOurBaseKey(parameters.getOurBaseKey()) + .setOurIdentityKey(parameters.getOurIdentityKey()) + .setTheirRatchetKey(parameters.getTheirRatchetKey()) + .setTheirIdentityKey(parameters.getTheirIdentityKey()) + .setTheirSignedPreKey(parameters.getTheirBaseKey()) + .setTheirOneTimePreKey(Optional.<ECPublicKey>absent()); + + RatchetingSession.initializeSession(sessionState, sessionVersion, aliceParameters.create()); + } else { + BobAxolotlParameters.Builder bobParameters = BobAxolotlParameters.newBuilder(); + + bobParameters.setOurIdentityKey(parameters.getOurIdentityKey()) + .setOurRatchetKey(parameters.getOurRatchetKey()) + .setOurSignedPreKey(parameters.getOurBaseKey()) + .setOurOneTimePreKey(Optional.<ECKeyPair>absent()) + .setTheirBaseKey(parameters.getTheirBaseKey()) + .setTheirIdentityKey(parameters.getTheirIdentityKey()); + + RatchetingSession.initializeSession(sessionState, sessionVersion, bobParameters.create()); + } + } + + public static void initializeSession(SessionState sessionState, + int sessionVersion, + AliceAxolotlParameters parameters) + throws InvalidKeyException + { + try { + sessionState.setSessionVersion(sessionVersion); + sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey()); + sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey()); + + ECKeyPair sendingRatchetKey = Curve.generateKeyPair(); + ByteArrayOutputStream secrets = new ByteArrayOutputStream(); + + if (sessionVersion >= 3) { + 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 (sessionVersion >= 3 & parameters.getTheirOneTimePreKey().isPresent()) { + secrets.write(Curve.calculateAgreement(parameters.getTheirOneTimePreKey().get(), + parameters.getOurBaseKey().getPrivateKey())); + } + + DerivedKeys derivedKeys = calculateDerivedKeys(sessionVersion, 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, + int sessionVersion, + BobAxolotlParameters parameters) + throws InvalidKeyException + { + + try { + sessionState.setSessionVersion(sessionVersion); + sessionState.setRemoteIdentityKey(parameters.getTheirIdentityKey()); + sessionState.setLocalIdentityKey(parameters.getOurIdentityKey().getPublicKey()); + + ByteArrayOutputStream secrets = new ByteArrayOutputStream(); + + if (sessionVersion >= 3) { + 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 (sessionVersion >= 3 && parameters.getOurOneTimePreKey().isPresent()) { + secrets.write(Curve.calculateAgreement(parameters.getTheirBaseKey(), + parameters.getOurOneTimePreKey().get().getPrivateKey())); + } + + DerivedKeys derivedKeys = calculateDerivedKeys(sessionVersion, 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(int sessionVersion, byte[] masterSecret) { + HKDF kdf = HKDF.createFor(sessionVersion); + 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; + } + } +} |