/// <summary> /// Validates the round 2 (zero-knowledge proof) DTO received from the partner participant. /// </summary> /// <param name="round2Received">Round 2 DTO received form partner participant.</param> /// <exception cref="InvalidOperationException"> /// Prior round (1) has not been completed yet, or method may have been called more than once. /// </exception> /// <exception cref="CryptoException"> /// Verification of zero-knowledge proof failed. Possible attempted impersonation, e.g. MiTM. /// </exception> public void ValidateRound2Received(ECJpakeRound2 round2Received) { Contract.Requires <InvalidOperationException>(ProtocolState < State.Round2Validated, "Validation already attempted for round 2 payload."); Contract.Requires <InvalidOperationException>(ProtocolState > State.Round1Validated, "Round 1 payload must be validated prior to validating round 2 payload."); Contract.Requires <ConfigurationInvalidException>(String.IsNullOrEmpty(round2Received.ParticipantId) == false, "Partner participant ID in round 2 DTO received is null or empty."); Contract.Requires <CryptoException>(PartnerParticipantId.Equals(round2Received.ParticipantId, StringComparison.Ordinal), "Partner participant ID of round 2 DTO does not match value from round 1."); ECPoint X4sV = _domain.Curve.DecodePoint(round2Received.X2sV); var X4sR = new BigInteger(round2Received.X2sR); _b = _domain.Curve.DecodePoint(round2Received.A); // Calculate GB : GX1 + GX3 + GX4 symmetrically ECPoint GB = _gx3.Add(_gx1).Add(_gx2); if (ZeroKnowledgeProofValid(GB, _b, X4sV, X4sR, PartnerParticipantId) == false) { throw new CryptoException("Round 2 validation failed. Possible impersonation attempt."); } ProtocolState = State.Round2Validated; }
/// <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> /// 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."); } } }