/// <summary> /// Validates the round 3 (key confirmation) DTO received from the partner participant. /// </summary> /// <param name="round3Received">Round 3 DTO received from partner participant.</param> /// <param name="keyingMaterial">Shared secret to be derived further before use as a key (e.g. by a KDF).</param> /// <exception cref="InvalidOperationException"> /// Key calculation and/or prior rounds (1 and 2) have not been completed yet, or /// method may have been called more than once. /// </exception> /// <exception cref="CryptoException"> /// Key confirmation failed - partner participant derived a different key. The passphrase used differs. /// Possible attempted impersonation / MiTM. /// </exception> public void ValidateRound3Received(JpakeRound3 round3Received, out byte[] keyingMaterial) { Contract.Requires <InvalidOperationException>(ProtocolState < State.Round3Validated, "Validation already attempted for round 3 payload."); // Contract.Requires<InvalidOperationException>(ProtocolState > State.KeyCalculated, // "Keying material must be calculated validated prior to validating round 3."); Contract.Requires <InvalidOperationException>(ProtocolState > State.Round2Validated, "Round 2 must be validated prior to validating round 3."); Contract.Requires <ConfigurationInvalidException>(String.IsNullOrEmpty(round3Received.ParticipantId) == false, "Partner participant ID in round 3 DTO received is null or empty."); Contract.Requires <CryptoException>(PartnerParticipantId.Equals(round3Received.ParticipantId, StringComparison.Ordinal), "Partner participant ID of round 3 DTO does not match value from prior rounds (1 and 2)."); var receivedTag = new BigInteger(round3Received.VerifiedOutput); if (_keyingMaterial == null) { CalculateKeyingMaterialInternal(); } BigInteger expectedTag = CalculateMacTag(PartnerParticipantId, ParticipantId, _gx3, _gx4, _gx1, _gx2, _keyingMaterial); byte[] expectedMacTagBytes = expectedTag.ToByteArrayUnsigned(); byte[] receivedMacTagBytes = receivedTag.ToByteArrayUnsigned(); if (expectedMacTagBytes.SequenceEqual_ConstantTime(receivedMacTagBytes) == false) { throw new CryptoException("Key confirmation failed - partner MAC tag failed to match expected value."); } // Return the confirmed key to the participant keyingMaterial = _keyingMaterial.ToByteArrayUnsigned(); // Clear sensitive state _keyingMaterial = null; _passwordBytes = null; _macTag = null; _x2 = null; _gx1 = null; _gx2 = null; _gx3 = null; _gx4 = null; ProtocolState = State.Round3Validated; }
/// <summary> /// Creates a round 3 (key confirmation) DTO to send to the partner participant. /// </summary> /// <exception cref="InvalidOperationException"> /// Prior rounds (1 and 2) have not been completed yet, or method may have been called more than once. /// </exception> public JpakeRound3 CreateRound3ToSend() { Contract.Requires <InvalidOperationException>(ProtocolState < State.Round3Created, "Round 3 already created."); Contract.Requires <InvalidOperationException>(ProtocolState == State.Round2Validated, "Round 2 payload must be validated prior to creating round 3 payload."); _keyingMaterial = CalculateKeyingMaterialInternal(); _macTag = CalculateMacTag(ParticipantId, PartnerParticipantId, _gx1, _gx2, _gx3, _gx4, _keyingMaterial); var dto = new JpakeRound3 { ParticipantId = ParticipantId, VerifiedOutput = _macTag.ToByteArrayUnsigned() }; ProtocolState = State.Round3Created; return(dto); }
/// <summary> /// Restores the state of an incomplete J-PAKE session, /// given private keys and DTO objects created/received from that session. /// </summary> /// <param name="x2">Private key.</param> /// <param name="round1Created">Round 1 created/sent.</param> /// <param name="round1Received">Round 1 received.</param> /// <param name="round2Created">Round 2 created/sent.</param> /// <param name="round2Received">Round 2 received.</param> /// <param name="round3Created">Round 3 created/sent.</param> public void RestoreState(byte[] x2, ECJpakeRound1 round1Created, ECJpakeRound1 round1Received = null, ECJpakeRound2 round2Created = null, ECJpakeRound2 round2Received = null, JpakeRound3 round3Created = null) { Contract.Requires(ProtocolState == State.Initialised, "Cannot restore state of already-active protocol session!"); Contract.Requires(round1Created != null); _gx1 = _domain.Curve.DecodePoint(round1Created.GX1); _gx2 = _domain.Curve.DecodePoint(round1Created.GX2); ProtocolState = State.Round1Created; if (round1Received != null) { if (String.IsNullOrEmpty(round1Received.ParticipantId)) { throw new ArgumentException("Partner participant ID in round 1 received is null or empty."); } PartnerParticipantId = round1Received.ParticipantId; _gx3 = _domain.Curve.DecodePoint(round1Received.GX1); _gx4 = _domain.Curve.DecodePoint(round1Received.GX2); ProtocolState = State.Round1Validated; } else { return; } if (round2Created != null) { ProtocolState = State.Round2Created; } else { return; } if (round2Received != null) { if (PartnerParticipantId.Equals(round2Received.ParticipantId, StringComparison.Ordinal) == false) { throw new ArgumentException("Partner participant ID of round 2 does not match value from round 1."); } _b = _domain.Curve.DecodePoint(round2Received.A); ProtocolState = State.Round2Validated; } else { return; } if (round3Created != null) { // Keying material has been calculated _b = _domain.Curve.DecodePoint(round2Received.A); ProtocolState = State.Round3Created; } else { if (x2.IsNullOrZeroLength()) { throw new ArgumentException("Session cannot be resumed without private key x2Export. Aborting."); } } }