mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-11 22:01:52 +00:00
210 lines
7.5 KiB
Java
210 lines
7.5 KiB
Java
/*
|
|
* Copyright (C) 2011 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.thoughtcrime.redphone.crypto.zrtp;
|
|
|
|
import android.content.Context;
|
|
|
|
import org.thoughtcrime.redphone.crypto.SecureRtpSocket;
|
|
import org.thoughtcrime.redphone.crypto.zrtp.retained.InitiatorRetainedSecretsCalculator;
|
|
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecrets;
|
|
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsCalculator;
|
|
import org.thoughtcrime.redphone.crypto.zrtp.retained.RetainedSecretsDerivatives;
|
|
|
|
import java.security.MessageDigest;
|
|
import java.security.NoSuchAlgorithmException;
|
|
|
|
/**
|
|
* The "initiator" side of a ZRTP handshake. This side just hangs out and waits
|
|
* for the "responder" to send a hello packet, then proceeds through the ZRTP handshake.
|
|
*
|
|
* @author Moxie Marlinspike
|
|
*
|
|
*/
|
|
|
|
public class ZRTPInitiatorSocket extends ZRTPSocket {
|
|
|
|
private HelloPacket foreignHello;
|
|
private HelloPacket localHello;
|
|
private CommitPacket commitPacket;
|
|
|
|
private DHPartOnePacket foreignDH;
|
|
private DHPartTwoPacket localDH;
|
|
|
|
private ConfirmOnePacket confirmPacket;
|
|
|
|
private RetainedSecretsCalculator retainedSecretsCalculator;
|
|
private boolean includeLegacyHeaderBug;
|
|
|
|
public ZRTPInitiatorSocket(Context context, SecureRtpSocket socket,
|
|
byte[] localZid, String foreignNumber)
|
|
{
|
|
super(context, socket, localZid, foreignNumber, EXPECTING_HELLO);
|
|
this.includeLegacyHeaderBug = false;
|
|
}
|
|
|
|
@Override
|
|
protected void handleCommit(HandshakePacket packet) {
|
|
throw new AssertionError("Invalid state!");
|
|
}
|
|
|
|
@Override
|
|
protected void handleConfirmAck(HandshakePacket packet) {
|
|
boolean continuity = retainedSecretsCalculator.hasContinuity(foreignDH.getDerivativeSecretOne(),
|
|
foreignDH.getDerivativeSecretTwo());
|
|
byte[] foreignZid = foreignHello.getZID();
|
|
byte[] rs1 = masterSecret.getRetainedSecret();
|
|
long expiration = System.currentTimeMillis() + (confirmPacket.getCacheTime() * 1000L);
|
|
|
|
// cacheRetainedSecret(remoteNumber, foreignZid, rs1, expiration, continuity);
|
|
setState(HANDSHAKE_COMPLETE);
|
|
}
|
|
|
|
@Override
|
|
protected void handleConfirmOne(HandshakePacket packet) throws InvalidPacketException {
|
|
confirmPacket = new ConfirmOnePacket(packet, isLegacyConfirmConnection());
|
|
|
|
confirmPacket.verifyMac(masterSecret.getResponderMacKey());
|
|
confirmPacket.decrypt(masterSecret.getResponderZrtpKey());
|
|
|
|
byte[] preimage = confirmPacket.getPreimage();
|
|
foreignDH.verifyMac(preimage);
|
|
|
|
setState(EXPECTING_CONFIRM_ACK);
|
|
sendFreshPacket(new ConfirmTwoPacket(masterSecret.getInitiatorMacKey(),
|
|
masterSecret.getInitiatorZrtpKey(),
|
|
this.hashChain, isLegacyConfirmConnection(),
|
|
includeLegacyHeaderBug));
|
|
}
|
|
|
|
@Override
|
|
protected void handleConfirmTwo(HandshakePacket packet) throws InvalidPacketException {
|
|
throw new InvalidPacketException("Initiator received a Confirm2 packet?");
|
|
}
|
|
|
|
@Override
|
|
protected void handleDH(HandshakePacket packet) throws InvalidPacketException {
|
|
assert(localDH != null);
|
|
|
|
SecretCalculator calculator;
|
|
|
|
switch (getKeyAgreementType()) {
|
|
case KA_TYPE_EC25:
|
|
foreignDH = new EC25DHPartOnePacket(packet, true);
|
|
calculator = new EC25SecretCalculator();
|
|
break;
|
|
case KA_TYPE_DH3K:
|
|
foreignDH = new DH3KDHPartOnePacket(packet, true);
|
|
calculator = new DH3KSecretCalculator();
|
|
break;
|
|
default:
|
|
throw new AssertionError("Unknown KA type: " + getKeyAgreementType());
|
|
}
|
|
|
|
byte[] h1 = foreignDH.getHash();
|
|
byte[] h2 = calculateH2(h1);
|
|
|
|
foreignHello.verifyMac(h2);
|
|
|
|
byte[] dhResult = calculator.calculateKeyAgreement(getKeyPair(), foreignDH.getPvr());
|
|
|
|
byte[] totalHash = calculator.calculateTotalHash(foreignHello, commitPacket,
|
|
foreignDH, localDH);
|
|
|
|
byte[] s1 = retainedSecretsCalculator.getS1(foreignDH.getDerivativeSecretOne(),
|
|
foreignDH.getDerivativeSecretTwo());
|
|
|
|
byte[] sharedSecret = calculator.calculateSharedSecret(dhResult, totalHash, s1,
|
|
localHello.getZID(),
|
|
foreignHello.getZID());
|
|
|
|
this.masterSecret = new MasterSecret(sharedSecret, totalHash, localHello.getZID(),
|
|
foreignHello.getZID());
|
|
|
|
setState(EXPECTING_CONFIRM_ONE);
|
|
sendFreshPacket(localDH);
|
|
}
|
|
|
|
@Override
|
|
protected void handleHelloAck(HandshakePacket packet) throws InvalidPacketException {
|
|
// RetainedSecrets retainedSecrets = getRetainedSecrets(remoteNumber, foreignHello.getZID());
|
|
RetainedSecrets retainedSecrets = new RetainedSecrets(null, null);
|
|
retainedSecretsCalculator = new InitiatorRetainedSecretsCalculator(retainedSecrets);
|
|
RetainedSecretsDerivatives derivatives = retainedSecretsCalculator.getRetainedSecretsDerivatives();
|
|
|
|
switch (getKeyAgreementType()) {
|
|
case KA_TYPE_EC25:
|
|
localDH = new EC25DHPartTwoPacket(hashChain, getPublicKey(), derivatives, includeLegacyHeaderBug);
|
|
break;
|
|
case KA_TYPE_DH3K:
|
|
localDH = new DH3KDHPartTwoPacket(hashChain, getPublicKey(), derivatives, includeLegacyHeaderBug);
|
|
break;
|
|
}
|
|
|
|
commitPacket = new CommitPacket(hashChain, foreignHello.getMessageBytes(), localDH, localZid, includeLegacyHeaderBug);
|
|
|
|
setState(EXPECTING_DH_1);
|
|
sendFreshPacket(commitPacket);
|
|
}
|
|
|
|
@Override
|
|
protected void handleHello(HandshakePacket packet) throws InvalidPacketException {
|
|
foreignHello = new HelloPacket(packet, true);
|
|
includeLegacyHeaderBug = foreignHello.isLegacyHeaderBugPresent();
|
|
localHello = new HelloPacket(hashChain, localZid, includeLegacyHeaderBug);
|
|
|
|
setState(EXPECTING_HELLO_ACK);
|
|
sendFreshPacket(localHello);
|
|
}
|
|
|
|
private byte[] calculateH2(byte[] h1) {
|
|
try {
|
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
|
return md.digest(h1);
|
|
} catch (NoSuchAlgorithmException e) {
|
|
throw new IllegalArgumentException(e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void negotiateStart() throws NegotiationFailedException {
|
|
super.negotiateStart();
|
|
}
|
|
|
|
@Override
|
|
protected int getKeyAgreementType() {
|
|
if (foreignHello == null)
|
|
throw new AssertionError("We can't project agreement type until we've seen a hello!");
|
|
|
|
RedPhoneClientId foreignClientId = new RedPhoneClientId(foreignHello.getClientId());
|
|
|
|
if (foreignClientId.isImplicitDh3kVersion() ||
|
|
foreignHello.getKeyAgreementOptions().contains("EC25"))
|
|
{
|
|
return KA_TYPE_EC25;
|
|
} else {
|
|
return KA_TYPE_DH3K;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected HelloPacket getForeignHello() {
|
|
return foreignHello;
|
|
}
|
|
|
|
}
|