From 9b1a5b045607b31d724e72b88112b192138f8a68 Mon Sep 17 00:00:00 2001 From: Alex van Poppelen Date: Sat, 17 Oct 2015 02:25:54 +0200 Subject: ported jpake library and tests from java --- .../src/crypto/agreement/jpake/JPAKEParticipant.cs | 525 +++++++++++++++++++ .../crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs | 117 +++++ .../agreement/jpake/JPAKEPrimeOrderGroups.cs | 108 ++++ .../crypto/agreement/jpake/JPAKERound1Payload.cs | 101 ++++ .../crypto/agreement/jpake/JPAKERound2Payload.cs | 72 +++ .../crypto/agreement/jpake/JPAKERound3Payload.cs | 51 ++ crypto/src/crypto/agreement/jpake/JPAKEUtil.cs | 404 +++++++++++++++ .../test/src/crypto/test/JPAKEParticipantTest.cs | 566 +++++++++++++++++++++ .../src/crypto/test/JPAKEPrimeOrderGroupTest.cs | 117 +++++ crypto/test/src/crypto/test/JPAKEUtilTest.cs | 306 +++++++++++ 10 files changed, 2367 insertions(+) create mode 100755 crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPAKEUtil.cs create mode 100644 crypto/test/src/crypto/test/JPAKEParticipantTest.cs create mode 100644 crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs create mode 100644 crypto/test/src/crypto/test/JPAKEUtilTest.cs (limited to 'crypto') diff --git a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs new file mode 100755 index 000000000..f36069262 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs @@ -0,0 +1,525 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +/** + * A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. + *

+ * The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper + * + * "Password Authenticated Key Exchange by Juggling, 2008." + *

+ * The J-PAKE protocol is symmetric. + * There is no notion of a client or server, but rather just two participants. + * An instance of {@link JPAKEParticipant} represents one participant, and + * is the primary interface for executing the exchange. + *

+ * To execute an exchange, construct a {@link JPAKEParticipant} on each end, + * and call the following 7 methods + * (once and only once, in the given order, for each participant, sending messages between them as described): + *

    + *
  1. {@link #createRound1PayloadToSend()} - and send the payload to the other participant
  2. + *
  3. {@link #validateRound1PayloadReceived(JPAKERound1Payload)} - use the payload received from the other participant
  4. + *
  5. {@link #createRound2PayloadToSend()} - and send the payload to the other participant
  6. + *
  7. {@link #validateRound2PayloadReceived(JPAKERound2Payload)} - use the payload received from the other participant
  8. + *
  9. {@link #calculateKeyingMaterial()}
  10. + *
  11. {@link #createRound3PayloadToSend(BigInteger)} - and send the payload to the other participant
  12. + *
  13. {@link #validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} - use the payload received from the other participant
  14. + *
+ *

+ * Each side should derive a session key from the keying material returned by {@link #calculateKeyingMaterial()}. + * The caller is responsible for deriving the session key using a secure key derivation function (KDF). + *

+ * Round 3 is an optional key confirmation process. + * If you do not execute round 3, then there is no assurance that both participants are using the same key. + * (i.e. if the participants used different passwords, then their session keys will differ.) + *

+ * If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. + *

+ * The symmetric design can easily support the asymmetric cases when one party initiates the communication. + * e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. + * Also, in some cases, the key confirmation payload can be sent together with the round2 payload. + * These are the trivial techniques to optimize the communication. + *

+ * The key confirmation process is implemented as specified in + * NIST SP 800-56A Revision 1, + * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + *

+ * This class is stateful and NOT threadsafe. + * Each instance should only be used for ONE complete J-PAKE exchange + * (i.e. a new {@link JPAKEParticipant} should be constructed for each new J-PAKE exchange). + *

+ */ +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + public class JPAKEParticipant + { + /* + * Possible internal states. Used for state checking. + */ + + public static readonly int STATE_INITIALIZED = 0; + public static readonly int STATE_ROUND_1_CREATED = 10; + public static readonly int STATE_ROUND_1_VALIDATED = 20; + public static readonly int STATE_ROUND_2_CREATED = 30; + public static readonly int STATE_ROUND_2_VALIDATED = 40; + public static readonly int STATE_KEY_CALCULATED = 50; + public static readonly int STATE_ROUND_3_CREATED = 60; + public static readonly int STATE_ROUND_3_VALIDATED = 70; + + /** + * Unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + */ + private string participantId; + + /** + * Shared secret. This only contains the secret between construction + * and the call to {@link #calculateKeyingMaterial()}. + *

+ * i.e. When {@link #calculateKeyingMaterial()} is called, this buffer overwritten with 0's, + * and the field is set to null. + *

+ */ + private char[] password; + + /** + * Digest to use during calculations. + */ + private IDigest digest; + + /** + * Source of secure random data. + */ + private readonly SecureRandom random; + + private readonly BigInteger p; + private readonly BigInteger q; + private readonly BigInteger g; + + /** + * The participantId of the other participant in this exchange. + */ + private string partnerParticipantId; + + /** + * Alice's x1 or Bob's x3. + */ + private BigInteger x1; + /** + * Alice's x2 or Bob's x4. + */ + private BigInteger x2; + /** + * Alice's g^x1 or Bob's g^x3. + */ + private BigInteger gx1; + /** + * Alice's g^x2 or Bob's g^x4. + */ + private BigInteger gx2; + /** + * Alice's g^x3 or Bob's g^x1. + */ + private BigInteger gx3; + /** + * Alice's g^x4 or Bob's g^x2. + */ + private BigInteger gx4; + /** + * Alice's B or Bob's A. + */ + private BigInteger b; + + /** + * The current state. + * See the STATE_* constants for possible values. + */ + private int state; + + /** + * Convenience constructor for a new {@link JPAKEParticipant} that uses + * the {@link JPAKEPrimeOrderGroups#NIST_3072} prime order group, + * a SHA-256 digest, and a default {@link SecureRandom} implementation. + *

+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. + * + * @param participantId unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + * @param password shared secret. + * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). + * Caller should clear the input password as soon as possible. + * @throws NullReferenceException if any argument is null + * @throws ArgumentException if password is empty + */ + public JPAKEParticipant(string participantId, char[] password) + : this(participantId, password, JPAKEPrimeOrderGroups.NIST_3072) { } + + /** + * Convenience constructor for a new {@link JPAKEParticipant} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *

+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. + * + * @param participantId unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + * @param password shared secret. + * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). + * Caller should clear the input password as soon as possible. + * @param group prime order group. + * See {@link JPAKEPrimeOrderGroups} for standard groups + * @throws NullReferenceException if any argument is null + * @throws ArgumentException if password is empty + */ + public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group) + : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } + + + /** + * Construct a new {@link JPAKEParticipant}. + *

+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. + * + * @param participantId unique identifier of this participant. + * The two participants in the exchange must NOT share the same id. + * @param password shared secret. + * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). + * Caller should clear the input password as soon as possible. + * @param group prime order group. + * See {@link JPAKEPrimeOrderGroups} for standard groups + * @param digest digest to use during zero knowledge proofs and key confirmation (SHA-256 or stronger preferred) + * @param random source of secure random data for x1 and x2, and for the zero knowledge proofs + * @throws NullReferenceException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group, IDigest digest, SecureRandom random) + { + JPAKEUtil.ValidateNotNull(participantId, "participantId"); + JPAKEUtil.ValidateNotNull(password, "password"); + JPAKEUtil.ValidateNotNull(group, "p"); + JPAKEUtil.ValidateNotNull(digest, "digest"); + JPAKEUtil.ValidateNotNull(random, "random"); + + if (password.Length == 0) + { + throw new ArgumentException("Password must not be empty."); + } + + this.participantId = participantId; + + /* + * Create a defensive copy so as to fully encapsulate the password. + * + * This array will contain the password for the lifetime of this + * participant BEFORE {@link #calculateKeyingMaterial()} is called. + * + * i.e. When {@link #calculateKeyingMaterial()} is called, the array will be cleared + * in order to remove the password from memory. + * + * The caller is responsible for clearing the original password array + * given as input to this constructor. + */ + this.password = new char[password.Length]; + Array.Copy(password, this.password, password.Length); + + this.p = group.P; + this.q = group.Q; + this.g = group.G; + + this.digest = digest; + this.random = random; + + this.state = STATE_INITIALIZED; + } + + /** + * Gets the current state of this participant. + * See the STATE_* constants for possible values. + */ + public int State + { + get { return state; } + } + + + /** + * Creates and returns the payload to send to the other participant during round 1. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_1_CREATED}. + */ + public JPAKERound1Payload CreateRound1PayloadToSend() + { + if (this.state >= STATE_ROUND_1_CREATED) + { + throw new InvalidOperationException("Round 1 payload already created for " + this.participantId); + } + + this.x1 = JPAKEUtil.GenerateX1(q, random); + this.x2 = JPAKEUtil.GenerateX2(q, random); + + this.gx1 = JPAKEUtil.CalculateGx(p, g, x1); + this.gx2 = JPAKEUtil.CalculateGx(p, g, x2); + BigInteger[] knowledgeProofForX1 = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); + BigInteger[] knowledgeProofForX2 = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); + + this.state = STATE_ROUND_1_CREATED; + + return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + } + + /** + * Validates the payload received from the other participant during round 1. + *

+ * Must be called prior to {@link #createRound2PayloadToSend()}. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_1_VALIDATED}. + * + * @throws CryptoException if validation fails. + * @throws InvalidOperationException if called multiple times. + */ + public void ValidateRound1PayloadReceived(JPAKERound1Payload round1PayloadReceived) + { + if (this.state >= STATE_ROUND_1_VALIDATED) + { + throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId); + } + + this.partnerParticipantId = round1PayloadReceived.ParticipantId; + this.gx3 = round1PayloadReceived.Gx1; + this.gx4 = round1PayloadReceived.Gx2; + + BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1; + BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2; + + JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); + JPAKEUtil.ValidateGx4(gx4); + JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); + JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); + + this.state = STATE_ROUND_1_VALIDATED; + } + + /** + * Creates and returns the payload to send to the other participant during round 2. + *

+ * {@link #validateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_2_CREATED}. + * + * @throws InvalidOperationException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times + */ + public JPAKERound2Payload CreateRound2PayloadToSend() + { + if (this.state >= STATE_ROUND_2_CREATED) + { + throw new InvalidOperationException("Round 2 payload already created for " + this.participantId); + } + if (this.state < STATE_ROUND_1_VALIDATED) + { + throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId); + } + + BigInteger gA = JPAKEUtil.CalculateGA(p, gx1, gx3, gx4); + BigInteger s = JPAKEUtil.CalculateS(password); + BigInteger x2s = JPAKEUtil.CalculateX2s(q, x2, s); + BigInteger A = JPAKEUtil.CalculateA(p, q, gA, x2s); + BigInteger[] knowledgeProofForX2s = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); + + this.state = STATE_ROUND_2_CREATED; + + return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s); + } + + /** + * Validates the payload received from the other participant during round 2. + *

+ * Note that this DOES NOT detect a non-common password. + * The only indication of a non-common password is through derivation + * of different keys (which can be detected explicitly by executing round 3 and round 4) + *

+ * Must be called prior to {@link #calculateKeyingMaterial()}. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_2_VALIDATED}. + * + * @throws CryptoException if validation fails. + * @throws InvalidOperationException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times + */ + public void ValidateRound2PayloadReceived(JPAKERound2Payload round2PayloadReceived) + { + if (this.state >= STATE_ROUND_2_VALIDATED) + { + throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId); + } + if (this.state < STATE_ROUND_1_VALIDATED) + { + throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId); + } + + BigInteger gB = JPAKEUtil.CalculateGA(p, gx3, gx1, gx2); + this.b = round2PayloadReceived.A; + BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s; + + JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId); + JPAKEUtil.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId); + JPAKEUtil.ValidateGa(gB); + JPAKEUtil.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest); + + this.state = STATE_ROUND_2_VALIDATED; + } + + /** + * Calculates and returns the key material. + * A session key must be derived from this key material using a secure key derivation function (KDF). + * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}). + *

+ * The keying material will be identical for each participant if and only if + * each participant's password is the same. i.e. If the participants do not + * share the same password, then each participant will derive a different key. + * Therefore, if you immediately start using a key derived from + * the keying material, then you must handle detection of incorrect keys. + * If you want to handle this detection explicitly, you can optionally perform + * rounds 3 and 4. See {@link JPAKEParticipant} for details on how to execute + * rounds 3 and 4. + *

+ * The keying material will be in the range [0, p-1]. + *

+ * {@link #validateRound2PayloadReceived(JPAKERound2Payload)} must be called prior to this method. + *

+ * As a side effect, the internal {@link #password} array is cleared, since it is no longer needed. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_KEY_CALCULATED}. + * + * @throws InvalidOperationException if called prior to {@link #validateRound2PayloadReceived(JPAKERound2Payload)}, + * or if called multiple times. + */ + public BigInteger CalculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + { + throw new InvalidOperationException("Key already calculated for " + participantId); + } + if (this.state < STATE_ROUND_2_VALIDATED) + { + throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId); + } + + BigInteger s = JPAKEUtil.CalculateS(password); + + /* + * Clear the password array from memory, since we don't need it anymore. + * + * Also set the field to null as a flag to indicate that the key has already been calculated. + */ + Array.Clear(password, 0, password.Length); + this.password = null; + + BigInteger keyingMaterial = JPAKEUtil.CalculateKeyingMaterial(p, q, gx4, x2, s, b); + + /* + * Clear the ephemeral private key fields as well. + * Note that we're relying on the garbage collector to do its job to clean these up. + * The old objects will hang around in memory until the garbage collector destroys them. + * + * If the ephemeral private keys x1 and x2 are leaked, + * the attacker might be able to brute-force the password. + */ + this.x1 = null; + this.x2 = null; + this.b = null; + + /* + * Do not clear gx* yet, since those are needed by round 3. + */ + + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /** + * Creates and returns the payload to send to the other participant during round 3. + *

+ * See {@link JPAKEParticipant} for more details on round 3. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_CREATED}. + * + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws InvalidOperationException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public JPAKERound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_CREATED) + { + throw new InvalidOperationException("Round 3 payload already created for " + this.participantId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId); + } + + BigInteger macTag = JPAKEUtil.CalculateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest); + + this.state = STATE_ROUND_3_CREATED; + + return new JPAKERound3Payload(participantId, macTag); + } + + /** + * Validates the payload received from the other participant during round 3. + *

+ * See {@link JPAKEParticipant} for more details on round 3. + *

+ * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_VALIDATED}. + * + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws CryptoException if validation fails. + * @throws InvalidOperationException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public void ValidateRound3PayloadReceived(JPAKERound3Payload round3PayloadReceived, BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_VALIDATED) + { + throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId); + } + + JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId); + JPAKEUtil.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId); + + JPAKEUtil.ValidateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest, + round3PayloadReceived.MacTag); + + /* + * Clear the rest of the fields. + */ + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_ROUND_3_VALIDATED; + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs new file mode 100755 index 000000000..1b0c514bb --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs @@ -0,0 +1,117 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + /** + * A pre-computed prime order group for use during a J-PAKE exchange. + *

+ * Typically a Schnorr group is used. In general, J-PAKE can use any prime order group + * that is suitable for public key cryptography, including elliptic curve cryptography. + *

+ * See {@link JPAKEPrimeOrderGroups} for convenient standard groups. + *

+ * NIST publishes + * many groups that can be used for the desired level of security. + */ + public class JPAKEPrimeOrderGroup + { + private readonly BigInteger p; + private readonly BigInteger q; + private readonly BigInteger g; + + /** + * Constructs a new {@link JPAKEPrimeOrderGroup}. + *

+ * In general, you should use one of the pre-approved groups from + * {@link JPAKEPrimeOrderGroups}, rather than manually constructing one. + *

+ * The following basic checks are performed: + *

+ *

+ * The prime checks are performed using {@link BigInteger#isProbablePrime(int)}, + * and are therefore subject to the same probability guarantees. + *

+ * These checks prevent trivial mistakes. + * However, due to the small uncertainties if p and q are not prime, + * advanced attacks are not prevented. + * Use it at your own risk. + * + * @throws NullReferenceException if any argument is null + * @throws InvalidOperationException if any of the above validations fail + */ + public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) + : this(p, q, g, false) + { + /* + * Don't skip the checks on user-specified groups. + */ + } + + /** + * Internal package-private constructor used by the pre-approved + * groups in {@link JPAKEPrimeOrderGroups}. + * These pre-approved groups can avoid the expensive checks. + */ + public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) + { + JPAKEUtil.ValidateNotNull(p, "p"); + JPAKEUtil.ValidateNotNull(q, "q"); + JPAKEUtil.ValidateNotNull(g, "g"); + + if (!skipChecks) + { + if (!p.Subtract(JPAKEUtil.ONE).Mod(q).Equals(JPAKEUtil.ZERO)) + { + throw new ArgumentException("p-1 must be evenly divisible by q"); + } + if (g.CompareTo(BigInteger.ValueOf(2)) == -1 || g.CompareTo(p.Subtract(JPAKEUtil.ONE)) == 1) + { + throw new ArgumentException("g must be in [2, p-1]"); + } + if (!g.ModPow(q, p).Equals(JPAKEUtil.ONE)) + { + throw new ArgumentException("g^q mod p must equal 1"); + } + /* + * Note these checks do not guarantee that p and q are prime. + * We just have reasonable certainty that they are prime. + */ + if (!p.IsProbablePrime(20)) + { + throw new ArgumentException("p must be prime"); + } + if (!q.IsProbablePrime(20)) + { + throw new ArgumentException("q must be prime"); + } + } + + this.p = p; + this.q = q; + this.g = g; + } + + public BigInteger P + { + get { return p; } + } + + public BigInteger Q + { + get { return q; } + } + + public BigInteger G + { + get { return g; } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs new file mode 100755 index 000000000..aeaff6f72 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs @@ -0,0 +1,108 @@ +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + ///

+ /// Standard pre-computed prime order groups for use by J-PAKE. + /// (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) + ///

+ /// This class contains some convenient constants for use as input for + /// constructing {@link JPAKEParticipant}s. + ///

+ /// The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), + /// and from the prime order groups + /// published by NIST. + ///

+ public class JPAKEPrimeOrderGroups + { + /// + /// From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) + /// 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. + /// + public static readonly JPAKEPrimeOrderGroup SUN_JCE_1024 = new JPAKEPrimeOrderGroup( + // p + new BigInteger( + "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + + "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" + + "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" + + "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16), + // q + new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16), + // g + new BigInteger( + "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" + + "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" + + "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" + + "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16), + true + ); + + /// + /// From NIST. + /// 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. + /// + public static readonly JPAKEPrimeOrderGroup NIST_2048 = new JPAKEPrimeOrderGroup( + // p + new BigInteger( + "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" + + "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" + + "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" + + "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" + + "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" + + "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" + + "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" + + "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16), + // q + new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16), + // g + new BigInteger( + "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" + + "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" + + "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" + + "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" + + "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" + + "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" + + "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" + + "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16), + true + ); + + /// + /// From NIST. + /// 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. + /// + public static readonly JPAKEPrimeOrderGroup NIST_3072 = new JPAKEPrimeOrderGroup( + // p + new BigInteger( + "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + + "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" + + "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" + + "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" + + "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" + + "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" + + "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" + + "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" + + "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" + + "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" + + "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" + + "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16), + // q + new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16), + // g + new BigInteger( + "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" + + "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" + + "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" + + "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" + + "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" + + "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" + + "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" + + "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" + + "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" + + "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" + + "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" + + "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16), + true + ); + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs new file mode 100755 index 000000000..7b638dabd --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs @@ -0,0 +1,101 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + /// + /// The payload sent/received during the first round of a J-PAKE exchange. + /// + /// Each JPAKEParticipant creates and sends an instance of this payload to + /// the other. The payload to send should be created via + /// JPAKEParticipant.CreateRound1PayloadToSend(). + /// + /// Each participant must also validate the payload received from the other. + /// The received payload should be validated via + /// JPAKEParticipant.ValidateRound1PayloadReceived(JPAKERound1Payload). + /// + public class JPAKERound1Payload + { + /// + /// The id of the JPAKEParticipant who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of g^x1 + /// + private readonly BigInteger gx1; + + /// + /// The value of g^x2 + /// + private readonly BigInteger gx2; + + /// + /// The zero knowledge proof for x1. + /// + /// This is a two element array, containing {g^v, r} for x1. + /// + private readonly BigInteger[] knowledgeProofForX1; + + /// + /// The zero knowledge proof for x2. + /// + /// This is a two element array, containing {g^v, r} for x2. + /// + private readonly BigInteger[] knowledgeProofForX2; + + public JPAKERound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2) + { + JPAKEUtil.ValidateNotNull(participantId, "participantId"); + JPAKEUtil.ValidateNotNull(gx1, "gx1"); + JPAKEUtil.ValidateNotNull(gx2, "gx2"); + JPAKEUtil.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + JPAKEUtil.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + + this.participantId = participantId; + this.gx1 = gx1; + this.gx2 = gx2; + this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length]; + Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length); + this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length]; + Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length); + } + + public string ParticipantId + { + get { return participantId; } + } + + public BigInteger Gx1 + { + get { return gx1; } + } + + public BigInteger Gx2 + { + get { return gx2; } + } + + public BigInteger[] KnowledgeProofForX1 + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length]; + Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length); + return kp; + } + } + + public BigInteger[] KnowledgeProofForX2 + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX2.Length]; + Array.Copy(knowledgeProofForX2, kp, knowledgeProofForX2.Length); + return kp; + } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs new file mode 100755 index 000000000..cf1a8575e --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + /// + /// The payload sent/received during the second round of a J-PAKE exchange. + /// + /// Each JPAKEParticipant creates and sends an instance + /// of this payload to the other JPAKEParticipant. + /// The payload to send should be created via + /// JPAKEParticipant#createRound2PayloadToSend() + /// + /// Each JPAKEParticipant must also validate the payload + /// received from the other JPAKEParticipant. + /// The received payload should be validated via + /// JPAKEParticipant#validateRound2PayloadReceived(JPAKERound2Payload) + /// + public class JPAKERound2Payload + { + /// + /// The id of the JPAKEParticipant who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of A, as computed during round 2. + /// + private readonly BigInteger a; + + /// + /// The zero knowledge proof for x2 * s. + /// + /// This is a two element array, containing {g^v, r} for x2 * s. + /// + private readonly BigInteger[] knowledgeProofForX2s; + + public JPAKERound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s) + { + JPAKEUtil.ValidateNotNull(participantId, "participantId"); + JPAKEUtil.ValidateNotNull(a, "a"); + JPAKEUtil.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); + + this.participantId = participantId; + this.a = a; + this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length]; + knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0); + } + + public string ParticipantId + { + get { return participantId; } + } + + public BigInteger A + { + get { return a; } + } + + public BigInteger[] KnowledgeProofForX2s + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX2s.Length]; + Array.Copy(knowledgeProofForX2s, kp, knowledgeProofForX2s.Length); + return kp; + } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs new file mode 100755 index 000000000..95e0f24ce --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + /// + /// The payload sent/received during the optional third round of a J-PAKE exchange, + /// which is for explicit key confirmation. + /// + /// Each JPAKEParticipant creates and sends an instance + /// of this payload to the other JPAKEParticipant. + /// The payload to send should be created via + /// JPAKEParticipant#createRound3PayloadToSend(BigInteger) + /// + /// Eeach JPAKEParticipant must also validate the payload + /// received from the other JPAKEParticipant. + /// The received payload should be validated via + /// JPAKEParticipant#validateRound3PayloadReceived(JPAKERound3Payload, BigInteger) + /// + public class JPAKERound3Payload + { + /// + /// The id of the {@link JPAKEParticipant} who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of MacTag, as computed by round 3. + /// + /// See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest) + /// + private readonly BigInteger macTag; + + public JPAKERound3Payload(string participantId, BigInteger magTag) + { + this.participantId = participantId; + this.macTag = magTag; + } + + public string ParticipantId + { + get { return participantId; } + } + + public BigInteger MacTag + { + get { return macTag; } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs b/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs new file mode 100755 index 000000000..34f8c2685 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs @@ -0,0 +1,404 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.Jpake +{ + /// + /// Primitives needed for a J-PAKE exchange. + /// + /// The recommended way to perform a J-PAKE exchange is by using + /// two JPAKEParticipants. Internally, those participants + /// call these primitive operations in JPAKEUtil. + /// + /// The primitives, however, can be used without a JPAKEParticipant if needed. + /// + public class JPAKEUtil + { + public static BigInteger ZERO = BigInteger.ValueOf(0); + public static BigInteger ONE = BigInteger.ValueOf(1); + + /// + /// Return a value that can be used as x1 or x3 during round 1. + /// The returned value is a random value in the range [0, q-1]. + /// + public static BigInteger GenerateX1(BigInteger q, SecureRandom random) + { + BigInteger min = ZERO; + BigInteger max = q.Subtract(ONE); + return BigIntegers.CreateRandomInRange(min, max, random); + } + + /// + /// Return a value that can be used as x2 or x4 during round 1. + /// The returned value is a random value in the range [1, q-1]. + /// + public static BigInteger GenerateX2(BigInteger q, SecureRandom random) + { + BigInteger min = ONE; + BigInteger max = q.Subtract(ONE); + return BigIntegers.CreateRandomInRange(min, max, random); + } + + /// + /// Converts the given password to a BigInteger + /// for use in arithmetic calculations. + /// + public static BigInteger CalculateS(char[] password) + { + return new BigInteger(Strings.ToUtf8ByteArray(password)); + } + + /// + /// Calculate g^x mod p as done in round 1. + /// + public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x) + { + return g.ModPow(x, p); + } + + /// + /// Calculate ga as done in round 2. + /// + public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4) + { + // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 + return gx1.Multiply(gx3).Multiply(gx4).Mod(p); + } + + /// + /// Calculate x2 * s as done in round 2. + /// + public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s) + { + return x2.Multiply(s).Mod(q); + } + + /// + /// Calculate A as done in round 2. + /// + public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s) + { + // A = ga^(x*s) + return gA.ModPow(x2s, p); + } + + /// + /// Calculate a zero knowledge proof of x using Schnorr's signature. + /// The returned array has two elements {g^v, r = v-x*h} for x. + /// + public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, + BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random) + { + BigInteger[] zeroKnowledgeProof = new BigInteger[2]; + + /* Generate a random v, and compute g^v */ + BigInteger vMin = ZERO; + BigInteger vMax = q.Subtract(ONE); + BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random); + + BigInteger gv = g.ModPow(v, p); + BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h + + zeroKnowledgeProof[0] = gv; + zeroKnowledgeProof[1] = v.Subtract(x.Multiply(h)).Mod(q); // r = v-x*h + + return zeroKnowledgeProof; + } + + private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx, + string participantId, IDigest digest) + { + digest.Reset(); + + UpdateDigestIncludingSize(digest, g); + + UpdateDigestIncludingSize(digest, gr); + + UpdateDigestIncludingSize(digest, gx); + + UpdateDigestIncludingSize(digest, participantId); + + byte[] output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + + return new BigInteger(output); + } + + /// + /// Validates that g^x4 is not 1. + /// throws CryptoException if g^x4 is 1 + /// + public static void ValidateGx4(BigInteger gx4) + { + if (gx4.Equals(ONE)) + { + throw new CryptoException("g^x validation failed. g^x should not be 1."); + } + } + + /// + /// Validates that ga is not 1. + /// + /// As described by Feng Hao... + /// Alice could simply check ga != 1 to ensure it is a generator. + /// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks. + /// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q. + /// + /// throws CryptoException if ga is 1 + /// + public static void ValidateGa(BigInteger ga) + { + if (ga.Equals(ONE)) + { + throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again."); + } + } + + /// + /// Validates the zero knowledge proof (generated by + /// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom) + /// is correct. + /// + /// throws CryptoException if the zero knowledge proof is not correct + /// + public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, + BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest) + { + /* sig={g^v,r} */ + BigInteger gv = zeroKnowledgeProof[0]; + BigInteger r = zeroKnowledgeProof[1]; + + BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); + if (!(gx.CompareTo(ZERO) == 1 && // g^x > 0 + gx.CompareTo(p) == -1 && // g^x < p + gx.ModPow(q, p).CompareTo(ONE) == 0 && // g^x^q mod q = 1 + /* + * Below, I took a straightforward way to compute g^r * g^x^h, + * which needs 2 exp. Using a simultaneous computation technique + * would only need 1 exp. + */ + g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h + { + throw new CryptoException("Zero-knowledge proof validation failed"); + } + } + + /// + /// Calculates the keying material, which can be done after round 2 has completed. + /// A session key must be derived from this key material using a secure key derivation function (KDF). + /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). + /// + /// KeyingMaterial = (B/g^{x2*x4*s})^x2 + /// + public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q, + BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B) + { + return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p); + } + + /// + /// Validates that the given participant ids are not equal. + /// (For the J-PAKE exchange, each participant must use a unique id.) + /// + /// Throws CryptoException if the participantId strings are equal. + /// + public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2) + { + if (participantId1.Equals(participantId2)) + { + throw new CryptoException( + "Both participants are using the same participantId (" + + participantId1 + + "). This is not allowed. " + + "Each participant must use a unique participantId."); + } + } + + /// + /// Validates that the given participant ids are equal. + /// This is used to ensure that the payloads received from + /// each round all come from the same participant. + /// + public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId) + { + if (!expectedParticipantId.Equals(actualParticipantId)) + { + throw new CryptoException( + "Received payload from incorrect partner (" + + actualParticipantId + + "). Expected to receive payload from " + + expectedParticipantId + + "."); + } + } + + /// + /// Validates that the given object is not null. + /// throws NullReferenceException if the object is null. + /// + /// object in question + /// name of the object (to be used in exception message) + public static void ValidateNotNull(Object obj, string description) + { + if (obj == null) + { + throw new NullReferenceException(description + " must not be null"); + } + } + + /// + /// Calculates the MacTag (to be used for key confirmation), as defined by + /// NIST SP 800-56A Revision 1, + /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + /// + /// MacTag = HMAC(MacKey, MacLen, MacData) + /// MacKey = H(K || "JPAKE_KC") + /// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4 + /// + /// Note that both participants use "KC_1_U" because the sender of the round 3 message + /// is always the initiator for key confirmation. + /// + /// HMAC = {@link HMac} used with the given {@link Digest} + /// H = The given {@link Digest} + /// MacLen = length of MacTag + /// + public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId, + BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest) + { + byte[] macKey = CalculateMacKey(keyingMaterial, digest); + + HMac mac = new HMac(digest); + byte[] macOutput = new byte[mac.GetMacSize()]; + mac.Init(new KeyParameter(macKey)); + + /* + * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4. + */ + UpdateMac(mac, "KC_1_U"); + UpdateMac(mac, participantId); + UpdateMac(mac, partnerParticipantId); + UpdateMac(mac, gx1); + UpdateMac(mac, gx2); + UpdateMac(mac, gx3); + UpdateMac(mac, gx4); + + mac.DoFinal(macOutput, 0); + + Arrays.Fill(macKey, (byte)0); + + return new BigInteger(macOutput); + } + + /// + /// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). + /// + /// MacKey = H(K || "JPAKE_KC") + /// + private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest) + { + digest.Reset(); + + UpdateDigest(digest, keyingMaterial); + /* + * This constant is used to ensure that the macKey is NOT the same as the derived key. + */ + UpdateDigest(digest, "JPAKE_KC"); + + byte[] output = new byte[digest.GetDigestSize()]; + digest.DoFinal(output, 0); + + return output; + } + + /// + /// Validates the MacTag received from the partner participant. + /// + /// throws CryptoException if the participantId strings are equal. + /// + /// the MacTag received from the partner + public static void ValidateMacTag(string participantId, string partnerParticipantId, + BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, + BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag) + { + /* + * Calculate the expected MacTag using the parameters as the partner + * would have used when the partner called calculateMacTag. + * + * i.e. basically all the parameters are reversed. + * participantId <-> partnerParticipantId + * x1 <-> x3 + * x2 <-> x4 + */ + BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest); + + if (!expectedMacTag.Equals(partnerMacTag)) + { + throw new CryptoException( + "Partner MacTag validation failed. " + + "Therefore, the password, MAC, or digest algorithm of each participant does not match."); + } + } + + private static void UpdateDigest(IDigest digest, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger); + digest.BlockUpdate(byteArray, 0, byteArray.Length); + Arrays.Fill(byteArray, (byte)0); + } + + private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger); + digest.BlockUpdate(IntToByteArray(byteArray.Length), 0, 4); + digest.BlockUpdate(byteArray, 0, byteArray.Length); + Arrays.Fill(byteArray, (byte)0); + } + + private static void UpdateDigest(IDigest digest, string str) + { + byte[] byteArray = Encoding.UTF8.GetBytes(str); + digest.BlockUpdate(byteArray, 0, byteArray.Length); + Arrays.Fill(byteArray, (byte)0); + } + + private static void UpdateDigestIncludingSize(IDigest digest, string str) + { + byte[] byteArray = Encoding.UTF8.GetBytes(str); + digest.BlockUpdate(IntToByteArray(byteArray.Length), 0, 4); + digest.BlockUpdate(byteArray, 0, byteArray.Length); + Arrays.Fill(byteArray, (byte)0); + } + + private static void UpdateMac(IMac mac, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger); + mac.BlockUpdate(byteArray, 0, byteArray.Length); + Arrays.Fill(byteArray, (byte)0); + } + + private static void UpdateMac(IMac mac, string str) + { + byte[] byteArray = Encoding.UTF8.GetBytes(str); + mac.BlockUpdate(byteArray, 0, byteArray.Length); + Arrays.Fill(byteArray, (byte)0); + } + + private static byte[] IntToByteArray(int value) + { + return new byte[]{ + (byte)((uint)value >> 24), + (byte)((uint)value >> 16), + (byte)((uint)value >> 8), + (byte)value + }; + } + + } +} diff --git a/crypto/test/src/crypto/test/JPAKEParticipantTest.cs b/crypto/test/src/crypto/test/JPAKEParticipantTest.cs new file mode 100644 index 000000000..7c01bf237 --- /dev/null +++ b/crypto/test/src/crypto/test/JPAKEParticipantTest.cs @@ -0,0 +1,566 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.Jpake; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Tests +{ + [TestFixture] + public class JPAKEParticipantTest + : SimpleTest + { + public override void PerformTest() + { + TestConstruction(); + TestSuccessfulExchange(); + TestIncorrectPassword(); + TestStateValidation(); + TestValidateRound1PayloadReceived(); + TestValidateRound2PayloadReceived(); + } + + public override string Name + { + get { return "JPAKEParticipant"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPAKEParticipantTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestConstruction() + { + JPAKEPrimeOrderGroup group = JPAKEPrimeOrderGroups.SUN_JCE_1024; + SecureRandom random = new SecureRandom(); + IDigest digest = new Sha256Digest(); + string participantId = "participantId"; + char[] password = "password".ToCharArray(); + + // should succeed + new JPAKEParticipant(participantId, password, group, digest, random); + + // null participantId + try + { + new JPAKEParticipant(null, password, group, digest, random); + + Fail("failed to throw exception on null participantId"); + } + catch (NullReferenceException) + { + // expected + } + + // null password + try + { + new JPAKEParticipant(participantId, null, group, digest, random); + + Fail("failed to throw exception on null password"); + } + catch (NullReferenceException) + { + // expected + } + + // empty password + try + { + new JPAKEParticipant(participantId, "".ToCharArray(), group, digest, random); + + Fail("failed to throw exception on empty password"); + } + catch (ArgumentException) + { + // expected + } + + // null group + try + { + new JPAKEParticipant(participantId, password, null, digest, random); + + Fail("failed to throw exception on null group"); + } + catch (NullReferenceException) + { + // expected + } + + // null digest + try + { + new JPAKEParticipant(participantId, password, group, null, random); + + Fail("failed to throw exception on null digest"); + } + catch (NullReferenceException) + { + // expected + } + + // null random + try + { + new JPAKEParticipant(participantId, password, group, digest, null); + + Fail("failed to throw exception on null random"); + } + catch (NullReferenceException) + { + // expected + } + } + + public void TestSuccessfulExchange() + { + JPAKEParticipant alice = CreateAlice(); + JPAKEParticipant bob = CreateBob(); + + ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); + + alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); + bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + JPAKERound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + JPAKERound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + + Assert.AreEqual(aliceKeyingMaterial, bobKeyingMaterial); + } + + public void TestIncorrectPassword() + { + JPAKEParticipant alice = CreateAlice(); + JPAKEParticipant bob = CreateBobWithWrongPassword(); + + ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); + + alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); + bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + JPAKERound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + JPAKERound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + + try + { + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + + Fail("failed to throw exception on incorrect password"); + } + catch (CryptoException) + { + // expected + } + + try + { + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + + Fail("failed to throw exception on incorrect password"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestStateValidation() + { + JPAKEParticipant alice = CreateAlice(); + JPAKEParticipant bob = CreateBob(); + + // We're testing alice here. Bob is just used for help. + + // START ROUND 1 CHECKS + + Assert.AreEqual(JPAKEParticipant.STATE_INITIALIZED, alice.State); + + // create round 2 before round 1 + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation before 1"); + } + catch (InvalidOperationException) + { + // expected + } + + JPAKERound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); + Assert.AreEqual(JPAKEParticipant.STATE_ROUND_1_CREATED, alice.State); + + // create round 1 twice + try + { + alice.CreateRound1PayloadToSend(); + + Fail("failed to throw on round 1 creation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + // create round 2 before validation round 1 + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation before round 1 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + // validate round 2 before validation round 1 + try + { + alice.ValidateRound2PayloadReceived(null); + + Fail("failed to throw on round 2 validation before round 1 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + JPAKERound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); + alice.ValidateRound1PayloadReceived(bobRound1Payload); + Assert.AreEqual(JPAKEParticipant.STATE_ROUND_1_VALIDATED, alice.State); + + // validate round 1 payload twice + try + { + alice.ValidateRound1PayloadReceived(bobRound1Payload); + + Fail("failed to throw on round 1 validation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound1PayloadReceived(aliceRound1Payload); + + // START ROUND 2 CHECKS + + JPAKERound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); + Assert.AreEqual(JPAKEParticipant.STATE_ROUND_2_CREATED, alice.State); + + // create round 2 payload twice + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + // create key before validation round 2 + try + { + alice.CalculateKeyingMaterial(); + + Fail("failed to throw on calculating keying material before round 2 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + // validate round 3 before validating round 2 + try + { + alice.ValidateRound3PayloadReceived(null, null); + + Fail("failed to throw on validating round 3 before 2"); + } + catch (InvalidOperationException) + { + // expected + } + + JPAKERound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); + alice.ValidateRound2PayloadReceived(bobRound2Payload); + Assert.AreEqual(JPAKEParticipant.STATE_ROUND_2_VALIDATED, alice.State); + + // validate round 2 payload twice + try + { + alice.ValidateRound2PayloadReceived(bobRound2Payload); + + Fail("failed to throw on validating round 2 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound2PayloadReceived(aliceRound2Payload); + + // create round 3 before calculating key + try + { + alice.CreateRound3PayloadToSend(BigInteger.One); + + Fail("failed to throw on creating round 3 before calculating key aterial"); + } + catch (InvalidOperationException) + { + // expected + } + + // START KEY CALCULATION CHECKS + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + Assert.AreEqual(JPAKEParticipant.STATE_KEY_CALCULATED, alice.State); + + // calculate key twice + try + { + alice.CalculateKeyingMaterial(); + + Fail("failed to throw on calculating key twice"); + } + catch (InvalidOperationException) + { + // expected + } + + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + // START ROUND 3 CHECKS + + JPAKERound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + Assert.AreEqual(JPAKEParticipant.STATE_ROUND_3_CREATED, alice.State); + + // create round 3 payload twice + try + { + alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + + Fail("failed to throw on creation round 3 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + JPAKERound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + Assert.AreEqual(JPAKEParticipant.STATE_ROUND_3_VALIDATED, alice.State); + + // validate round 3 payload twice + try + { + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + + Fail("failed to throw on validation round 3 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + } + + public void TestValidateRound1PayloadReceived() + { + // We're testing alice here. Bob is just used for help. + + JPAKERound1Payload bobRound1Payload = CreateBob().CreateRound1PayloadToSend(); + + // should succeed + CreateAlice().ValidateRound1PayloadReceived(bobRound1Payload); + + // alice verifies alice's payload + try + { + JPAKEParticipant alice = CreateAlice(); + alice.ValidateRound1PayloadReceived(alice.CreateRound1PayloadToSend()); + + Fail("failed to throw on participant validating own payload"); + } + catch (CryptoException) + { + // expected + } + + // g^x4 == 1 + try + { + CreateAlice().ValidateRound1PayloadReceived(new JPAKERound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + BigInteger.One, + bobRound1Payload.KnowledgeProofForX1, + bobRound1Payload.KnowledgeProofForX2)); + + Fail("failed to throw on g^x4 == 1"); + } + catch (CryptoException) + { + // expected + } + + // zero knowledge proof for x3 fails + try + { + JPAKERound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); + CreateAlice().ValidateRound1PayloadReceived(new JPAKERound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + bobRound1Payload.Gx2, + bobRound1Payload2.KnowledgeProofForX1, + bobRound1Payload.KnowledgeProofForX2)); + + Fail("failed to throw on incorrect zero knowledge proof for x3"); + } + catch (CryptoException) + { + // expected + } + + // zero knowledge proof for x4 fails + try + { + JPAKERound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); + CreateAlice().ValidateRound1PayloadReceived(new JPAKERound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + bobRound1Payload.Gx2, + bobRound1Payload.KnowledgeProofForX1, + bobRound1Payload2.KnowledgeProofForX2)); + + Fail("failed to throw on incorrect zero knowledge proof for x4"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateRound2PayloadReceived() + { + // We're testing alice here. Bob is just used for help. + + // should succeed + ExchangeAfterRound2Creation exchange1 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + exchange1.alice.ValidateRound2PayloadReceived(exchange1.bobRound2Payload); + + // alice verified alice's payload + ExchangeAfterRound2Creation exchange2 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + try + { + exchange2.alice.ValidateRound2PayloadReceived(exchange2.aliceRound2Payload); + + Fail("failed to throw on participant verifying own payload 2"); + } + catch (CryptoException) + { + // expected + } + + // wrong z + ExchangeAfterRound2Creation exchange3 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + ExchangeAfterRound2Creation exchange4 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + try + { + exchange3.alice.ValidateRound2PayloadReceived(exchange4.bobRound2Payload); + + Fail("failed to throw on wrong z"); + } + catch (CryptoException) + { + // expected + } + } + + private class ExchangeAfterRound2Creation + { + public JPAKEParticipant alice; + public JPAKERound2Payload aliceRound2Payload; + public JPAKERound2Payload bobRound2Payload; + + public ExchangeAfterRound2Creation( + JPAKEParticipant alice, + JPAKERound2Payload aliceRound2Payload, + JPAKERound2Payload bobRound2Payload) + { + this.alice = alice; + this.aliceRound2Payload = aliceRound2Payload; + this.bobRound2Payload = bobRound2Payload; + } + } + + private ExchangeAfterRound2Creation RunExchangeUntilRound2Creation(JPAKEParticipant alice, JPAKEParticipant bob) + { + JPAKERound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); + JPAKERound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); + + alice.ValidateRound1PayloadReceived(bobRound1Payload); + bob.ValidateRound1PayloadReceived(aliceRound1Payload); + + JPAKERound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); + JPAKERound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); + + return new ExchangeAfterRound2Creation( + alice, + aliceRound2Payload, + bobRound2Payload); + } + + private JPAKEParticipant CreateAlice() + { + return CreateParticipant("alice", "password"); + } + + private JPAKEParticipant CreateBob() + { + return CreateParticipant("bob", "password"); + } + + private JPAKEParticipant CreateBobWithWrongPassword() + { + return CreateParticipant("bob", "wrong"); + } + + private JPAKEParticipant CreateParticipant(string participantId, string password) + { + return new JPAKEParticipant( + participantId, + password.ToCharArray(), + JPAKEPrimeOrderGroups.SUN_JCE_1024); + } + } +} diff --git a/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs b/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs new file mode 100644 index 000000000..d9749cb53 --- /dev/null +++ b/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs @@ -0,0 +1,117 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.Jpake; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Tests +{ + [TestFixture] + public class JPAKEPrimeOrderGroupTest + : SimpleTest + { + public override void PerformTest() + { + TestConstruction(); + } + + public override string Name + { + get { return "JPAKEPrimeOrderGroup"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPAKEPrimeOrderGroupTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestConstruction() + { + // p-1 not evenly divisible by q + try + { + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(5), BigInteger.ValueOf(6)); + + Fail("failed to throw exception on p-1 not evenly divisible by q"); + } + catch (ArgumentException) + { + // expected + } + + // g < 2 + try + { + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(1)); + + Fail("failed to throw exception on g < 2"); + } + catch (ArgumentException) + { + // expected + } + + // g > p - 1 + try + { + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(11)); + + Fail("failed to throw exception on g > p - 1"); + } + catch (ArgumentException) + { + // expected + } + + //g^q mod p not equal 1 + try + { + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(6)); + + Fail("failed to throw exception on g^q mod p not equal 1"); + } + catch (ArgumentException) + { + // expected + } + + // p not prime + try + { + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(15), BigInteger.ValueOf(2), BigInteger.ValueOf(4)); + + Fail("failed to throw exception on p not prime"); + } + catch (ArgumentException) + { + // expected + } + + // q not prime + try + { + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(6), BigInteger.ValueOf(3)); + + Fail("failed to throw exception on q not prime"); + } + catch (ArgumentException) + { + // expected + } + + // should succeed + new JPAKEPrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(3), BigInteger.ValueOf(4)); + } + } +} diff --git a/crypto/test/src/crypto/test/JPAKEUtilTest.cs b/crypto/test/src/crypto/test/JPAKEUtilTest.cs new file mode 100644 index 000000000..7ac54aba0 --- /dev/null +++ b/crypto/test/src/crypto/test/JPAKEUtilTest.cs @@ -0,0 +1,306 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.Jpake; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Tests +{ + [TestFixture] + public class JPAKEUtilTest + : SimpleTest + { + private static readonly BigInteger Ten = BigInteger.ValueOf(10); + + public override void PerformTest() + { + TestValidateGx4(); + TestValidateGa(); + TestValidateParticipantIdsDiffer(); + TestValidateParticipantsIdsEqual(); + TestValidateMacTag(); + TestValidateNotNull(); + TestValidateZeroKnowledgeProof(); + } + + public override string Name + { + get { return "JPAKEUtil"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPAKEUtilTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestValidateGx4() + { + JPAKEUtil.ValidateGx4(Ten); + + try + { + JPAKEUtil.ValidateGx4(BigInteger.One); + + Fail("exception not thrown for g^x4 equal to 1"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateGa() + { + JPAKEUtil.ValidateGa(Ten); + + try + { + JPAKEUtil.ValidateGa(BigInteger.One); + + Fail("exception not thrown for g^a equal to 1"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateParticipantIdsDiffer() + { + JPAKEUtil.ValidateParticipantIdsDiffer("a", "b"); + JPAKEUtil.ValidateParticipantIdsDiffer("a", "A"); + + try + { + JPAKEUtil.ValidateParticipantIdsDiffer("a", "a"); + + Fail("validate participant ids differ not throwing exception for equal participant ids"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateParticipantsIdsEqual() + { + JPAKEUtil.ValidateParticipantIdsEqual("a", "a"); + + try + { + JPAKEUtil.ValidateParticipantIdsEqual("a", "b"); + + Fail("validate participant ids equal not throwing exception for different participant ids"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateMacTag() + { + JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024; + + SecureRandom random = new SecureRandom(); + IDigest digest = new Sha256Digest(); + + BigInteger x1 = JPAKEUtil.GenerateX1(pg1.Q, random); + BigInteger x2 = JPAKEUtil.GenerateX2(pg1.Q, random); + BigInteger x3 = JPAKEUtil.GenerateX1(pg1.Q, random); + BigInteger x4 = JPAKEUtil.GenerateX2(pg1.Q, random); + + BigInteger gx1 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x1); + BigInteger gx2 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x2); + BigInteger gx3 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x3); + BigInteger gx4 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x4); + + BigInteger gB = JPAKEUtil.CalculateGA(pg1.P, gx3, gx1, gx2); + + BigInteger s = JPAKEUtil.CalculateS("password".ToCharArray()); + + BigInteger xs = JPAKEUtil.CalculateX2s(pg1.Q, x4, s); + + BigInteger B = JPAKEUtil.CalculateA(pg1.P, pg1.Q, gB, xs); + + BigInteger keyingMaterial = JPAKEUtil.CalculateKeyingMaterial(pg1.P, pg1.Q, gx4, x2, s, B); + + BigInteger macTag = JPAKEUtil.CalculateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest); + + // should succeed + JPAKEUtil.ValidateMacTag("partnerParticipantId", "participantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag); + + // validating own macTag (as opposed to the other party's mactag) + try + { + JPAKEUtil.ValidateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest, macTag); + + Fail("failed to throw exception on validating own macTag (calculated partner macTag)"); + } + catch (CryptoException) + { + // expected + } + + // participant ids switched + try + { + JPAKEUtil.ValidateMacTag("participantId", "partnerParticipantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag); + + Fail("failed to throw exception on validating own macTag (calculated partner macTag"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateNotNull() + { + JPAKEUtil.ValidateNotNull("a", "description"); + + try + { + JPAKEUtil.ValidateNotNull(null, "description"); + + Fail("failed to throw exception on null"); + } + catch (NullReferenceException) + { + // expected + } + } + + public void TestValidateZeroKnowledgeProof() + { + JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024; + + SecureRandom random = new SecureRandom(); + IDigest digest1 = new Sha256Digest(); + + BigInteger x1 = JPAKEUtil.GenerateX1(pg1.Q, random); + BigInteger gx1 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x1); + string participantId1 = "participant1"; + + BigInteger[] zkp1 = JPAKEUtil.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, x1, participantId1, digest1, random); + + // should succeed + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest1); + + // wrong group + JPAKEPrimeOrderGroup pg2 = JPAKEPrimeOrderGroups.NIST_3072; + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg2.P, pg2.Q, pg2.G, gx1, zkp1, participantId1, digest1); + + Fail("failed to throw exception on wrong prime order group"); + } + catch (CryptoException) + { + // expected + } + + // wrong digest + IDigest digest2 = new Sha1Digest(); + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest2); + + Fail("failed to throw exception on wrong digest"); + } + catch (CryptoException) + { + // expected + } + + // wrong participant + string participantId2 = "participant2"; + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId2, digest1); + + Fail("failed to throw exception on wrong participant"); + } + catch (CryptoException) + { + // expected + } + + // wrong gx + BigInteger x2 = JPAKEUtil.GenerateX2(pg1.Q, random); + BigInteger gx2 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x2); + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, zkp1, participantId1, digest1); + + Fail("failed to throw exception on wrong gx"); + } + catch (CryptoException) + { + // expected + } + + // wrong zkp + BigInteger[] zkp2 = JPAKEUtil.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, x2, participantId1, digest1, random); + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp2, participantId1, digest1); + + Fail("failed to throw exception on wrong zero knowledge proof"); + } + catch (CryptoException) + { + // expected + } + + // gx <= 0 + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, BigInteger.Zero, zkp1, participantId1, digest1); + + Fail("failed to throw exception on g^x <= 0"); + } + catch (CryptoException) + { + // expected + } + + // gx >= p + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.P, zkp1, participantId1, digest1); + + Fail("failed to throw exception on g^x >= p"); + } + catch (CryptoException) + { + // expected + } + + // gx mod q == 1 + try + { + JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.Q.Add(BigInteger.One), zkp1, participantId1, digest1); + + Fail("failed to throw exception on g^x mod q == 1"); + } + catch (CryptoException) + { + // expected + } + } + } +} -- cgit 1.5.1 From 5436cd3ce98c9c831a7c85e19ab0d838aa7323df Mon Sep 17 00:00:00 2001 From: Alex van Poppelen Date: Sat, 17 Oct 2015 03:30:46 +0200 Subject: cleaned up commenting a bit --- .../src/crypto/agreement/jpake/JPAKEParticipant.cs | 486 ++++++++++----------- .../crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs | 93 ++-- 2 files changed, 263 insertions(+), 316 deletions(-) (limited to 'crypto') diff --git a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs index f36069262..bd18765b4 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs @@ -5,62 +5,57 @@ using Org.BouncyCastle.Crypto.Digests; using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; -/** - * A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. - *

- * The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper - * - * "Password Authenticated Key Exchange by Juggling, 2008." - *

- * The J-PAKE protocol is symmetric. - * There is no notion of a client or server, but rather just two participants. - * An instance of {@link JPAKEParticipant} represents one participant, and - * is the primary interface for executing the exchange. - *

- * To execute an exchange, construct a {@link JPAKEParticipant} on each end, - * and call the following 7 methods - * (once and only once, in the given order, for each participant, sending messages between them as described): - *

    - *
  1. {@link #createRound1PayloadToSend()} - and send the payload to the other participant
  2. - *
  3. {@link #validateRound1PayloadReceived(JPAKERound1Payload)} - use the payload received from the other participant
  4. - *
  5. {@link #createRound2PayloadToSend()} - and send the payload to the other participant
  6. - *
  7. {@link #validateRound2PayloadReceived(JPAKERound2Payload)} - use the payload received from the other participant
  8. - *
  9. {@link #calculateKeyingMaterial()}
  10. - *
  11. {@link #createRound3PayloadToSend(BigInteger)} - and send the payload to the other participant
  12. - *
  13. {@link #validateRound3PayloadReceived(JPAKERound3Payload, BigInteger)} - use the payload received from the other participant
  14. - *
- *

- * Each side should derive a session key from the keying material returned by {@link #calculateKeyingMaterial()}. - * The caller is responsible for deriving the session key using a secure key derivation function (KDF). - *

- * Round 3 is an optional key confirmation process. - * If you do not execute round 3, then there is no assurance that both participants are using the same key. - * (i.e. if the participants used different passwords, then their session keys will differ.) - *

- * If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. - *

- * The symmetric design can easily support the asymmetric cases when one party initiates the communication. - * e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. - * Also, in some cases, the key confirmation payload can be sent together with the round2 payload. - * These are the trivial techniques to optimize the communication. - *

- * The key confirmation process is implemented as specified in - * NIST SP 800-56A Revision 1, - * Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. - *

- * This class is stateful and NOT threadsafe. - * Each instance should only be used for ONE complete J-PAKE exchange - * (i.e. a new {@link JPAKEParticipant} should be constructed for each new J-PAKE exchange). - *

- */ namespace Org.BouncyCastle.Crypto.Agreement.Jpake { + ///

+ /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. + /// + /// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper + /// + /// "Password Authenticated Key Exchange by Juggling, 2008." + /// + /// The J-PAKE protocol is symmetric. + /// There is no notion of a client or server, but rather just two participants. + /// An instance of JPAKEParticipant represents one participant, and + /// is the primary interface for executing the exchange. + /// + /// To execute an exchange, construct a JPAKEParticipant on each end, + /// and call the following 7 methods + /// (once and only once, in the given order, for each participant, sending messages between them as described): + /// + /// CreateRound1PayloadToSend() - and send the payload to the other participant + /// ValidateRound1PayloadReceived(JPAKERound1Payload) - use the payload received from the other participant + /// CreateRound2PayloadToSend() - and send the payload to the other participant + /// ValidateRound2PayloadReceived(JPAKERound2Payload) - use the payload received from the other participant + /// CalculateKeyingMaterial() + /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant + /// ValidateRound3PayloadReceived(JPAKERound3Payload, BigInteger) - use the payload received from the other participant + /// + /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial(). + /// The caller is responsible for deriving the session key using a secure key derivation function (KDF). + /// + /// Round 3 is an optional key confirmation process. + /// If you do not execute round 3, then there is no assurance that both participants are using the same key. + /// (i.e. if the participants used different passwords, then their session keys will differ.) + /// + /// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. + /// + /// The symmetric design can easily support the asymmetric cases when one party initiates the communication. + /// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. + /// Also, in some cases, the key confirmation payload can be sent together with the round2 payload. + /// These are the trivial techniques to optimize the communication. + /// + /// The key confirmation process is implemented as specified in + /// NIST SP 800-56A Revision 1, + /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + /// + /// This class is stateful and NOT threadsafe. + /// Each instance should only be used for ONE complete J-PAKE exchange + /// (i.e. a new JPAKEParticipant should be constructed for each new J-PAKE exchange). + /// public class JPAKEParticipant { - /* - * Possible internal states. Used for state checking. - */ - + // Possible internal states. Used for state checking. public static readonly int STATE_INITIALIZED = 0; public static readonly int STATE_ROUND_1_CREATED = 10; public static readonly int STATE_ROUND_1_VALIDATED = 20; @@ -70,131 +65,103 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake public static readonly int STATE_ROUND_3_CREATED = 60; public static readonly int STATE_ROUND_3_VALIDATED = 70; - /** - * Unique identifier of this participant. - * The two participants in the exchange must NOT share the same id. - */ + // Unique identifier of this participant. + // The two participants in the exchange must NOT share the same id. private string participantId; - /** - * Shared secret. This only contains the secret between construction - * and the call to {@link #calculateKeyingMaterial()}. - *

- * i.e. When {@link #calculateKeyingMaterial()} is called, this buffer overwritten with 0's, - * and the field is set to null. - *

- */ + // Shared secret. This only contains the secret between construction + // and the call to CalculateKeyingMaterial(). + // + // i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's, + // and the field is set to null. private char[] password; - /** - * Digest to use during calculations. - */ + // Digest to use during calculations. private IDigest digest; - /** - * Source of secure random data. - */ + // Source of secure random data. private readonly SecureRandom random; private readonly BigInteger p; private readonly BigInteger q; private readonly BigInteger g; - /** - * The participantId of the other participant in this exchange. - */ + // The participantId of the other participant in this exchange. private string partnerParticipantId; - /** - * Alice's x1 or Bob's x3. - */ + // Alice's x1 or Bob's x3. private BigInteger x1; - /** - * Alice's x2 or Bob's x4. - */ + // Alice's x2 or Bob's x4. private BigInteger x2; - /** - * Alice's g^x1 or Bob's g^x3. - */ + // Alice's g^x1 or Bob's g^x3. private BigInteger gx1; - /** - * Alice's g^x2 or Bob's g^x4. - */ + // Alice's g^x2 or Bob's g^x4. private BigInteger gx2; - /** - * Alice's g^x3 or Bob's g^x1. - */ + // Alice's g^x3 or Bob's g^x1. private BigInteger gx3; - /** - * Alice's g^x4 or Bob's g^x2. - */ + // Alice's g^x4 or Bob's g^x2. private BigInteger gx4; - /** - * Alice's B or Bob's A. - */ + // Alice's B or Bob's A. private BigInteger b; - /** - * The current state. - * See the STATE_* constants for possible values. - */ + // The current state. + // See the STATE_* constants for possible values. private int state; - /** - * Convenience constructor for a new {@link JPAKEParticipant} that uses - * the {@link JPAKEPrimeOrderGroups#NIST_3072} prime order group, - * a SHA-256 digest, and a default {@link SecureRandom} implementation. - *

- * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. - * - * @param participantId unique identifier of this participant. - * The two participants in the exchange must NOT share the same id. - * @param password shared secret. - * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). - * Caller should clear the input password as soon as possible. - * @throws NullReferenceException if any argument is null - * @throws ArgumentException if password is empty - */ + /// Convenience constructor for a new JPAKEParticipant that uses + /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. public JPAKEParticipant(string participantId, char[] password) : this(participantId, password, JPAKEPrimeOrderGroups.NIST_3072) { } - /** - * Convenience constructor for a new {@link JPAKEParticipant} that uses - * a SHA-256 digest and a default {@link SecureRandom} implementation. - *

- * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. - * - * @param participantId unique identifier of this participant. - * The two participants in the exchange must NOT share the same id. - * @param password shared secret. - * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). - * Caller should clear the input password as soon as possible. - * @param group prime order group. - * See {@link JPAKEPrimeOrderGroups} for standard groups - * @throws NullReferenceException if any argument is null - * @throws ArgumentException if password is empty - */ + /// Convenience constructor for a new JPAKEParticipant that uses + /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + /// Prime order group. See JPAKEPrimeOrderGroups for standard groups. public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group) : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } - /** - * Construct a new {@link JPAKEParticipant}. - *

- * After construction, the {@link #getState() state} will be {@link #STATE_INITIALIZED}. - * - * @param participantId unique identifier of this participant. - * The two participants in the exchange must NOT share the same id. - * @param password shared secret. - * A defensive copy of this array is made (and cleared once {@link #calculateKeyingMaterial()} is called). - * Caller should clear the input password as soon as possible. - * @param group prime order group. - * See {@link JPAKEPrimeOrderGroups} for standard groups - * @param digest digest to use during zero knowledge proofs and key confirmation (SHA-256 or stronger preferred) - * @param random source of secure random data for x1 and x2, and for the zero knowledge proofs - * @throws NullReferenceException if any argument is null - * @throws IllegalArgumentException if password is empty - */ + /// Convenience constructor for a new JPAKEParticipant that uses + /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + /// Prime order group. See JPAKEPrimeOrderGroups for standard groups. + /// Digest to use during zero knowledge proofs and key confirmation + /// (SHA-256 or stronger preferred). + /// Source of secure random data for x1 and x2, and for the zero knowledge proofs. public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group, IDigest digest, SecureRandom random) { JPAKEUtil.ValidateNotNull(participantId, "participantId"); @@ -210,18 +177,16 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake this.participantId = participantId; - /* - * Create a defensive copy so as to fully encapsulate the password. - * - * This array will contain the password for the lifetime of this - * participant BEFORE {@link #calculateKeyingMaterial()} is called. - * - * i.e. When {@link #calculateKeyingMaterial()} is called, the array will be cleared - * in order to remove the password from memory. - * - * The caller is responsible for clearing the original password array - * given as input to this constructor. - */ + // Create a defensive copy so as to fully encapsulate the password. + // + // This array will contain the password for the lifetime of this + // participant BEFORE CalculateKeyingMaterial() is called. + // + // i.e. When CalculateKeyingMaterial() is called, the array will be cleared + // in order to remove the password from memory. + // + // The caller is responsible for clearing the original password array + // given as input to this constructor. this.password = new char[password.Length]; Array.Copy(password, this.password, password.Length); @@ -235,21 +200,21 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake this.state = STATE_INITIALIZED; } - /** - * Gets the current state of this participant. - * See the STATE_* constants for possible values. - */ + ///

+ /// Gets the current state of this participant. + /// See the STATE_* constants for possible values. + /// public int State { get { return state; } } - /** - * Creates and returns the payload to send to the other participant during round 1. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_1_CREATED}. - */ + ///

+ /// Creates and returns the payload to send to the other participant during round 1. + /// + /// After execution, the State state} will be STATE_ROUND_1_CREATED}. + /// public JPAKERound1Payload CreateRound1PayloadToSend() { if (this.state >= STATE_ROUND_1_CREATED) @@ -270,16 +235,15 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); } - /** - * Validates the payload received from the other participant during round 1. - *

- * Must be called prior to {@link #createRound2PayloadToSend()}. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_1_VALIDATED}. - * - * @throws CryptoException if validation fails. - * @throws InvalidOperationException if called multiple times. - */ + ///

+ /// Validates the payload received from the other participant during round 1. + /// + /// Must be called prior to CreateRound2PayloadToSend(). + /// + /// After execution, the State state will be STATE_ROUND_1_VALIDATED. + /// Throws CryptoException if validation fails. Throws InvalidOperationException + /// if called multiple times. + /// public void ValidateRound1PayloadReceived(JPAKERound1Payload round1PayloadReceived) { if (this.state >= STATE_ROUND_1_VALIDATED) @@ -297,20 +261,19 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); JPAKEUtil.ValidateGx4(gx4); JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); - JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); - + JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); this.state = STATE_ROUND_1_VALIDATED; } - /** - * Creates and returns the payload to send to the other participant during round 2. - *

- * {@link #validateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_2_CREATED}. - * - * @throws InvalidOperationException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times - */ + ///

+ /// Creates and returns the payload to send to the other participant during round 2. + /// + /// ValidateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method. + /// + //// After execution, the State state will be STATE_ROUND_2_CREATED. + /// + /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times + /// public JPAKERound2Payload CreateRound2PayloadToSend() { if (this.state >= STATE_ROUND_2_CREATED) @@ -333,20 +296,19 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s); } - /** - * Validates the payload received from the other participant during round 2. - *

- * Note that this DOES NOT detect a non-common password. - * The only indication of a non-common password is through derivation - * of different keys (which can be detected explicitly by executing round 3 and round 4) - *

- * Must be called prior to {@link #calculateKeyingMaterial()}. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_2_VALIDATED}. - * - * @throws CryptoException if validation fails. - * @throws InvalidOperationException if called prior to {@link #validateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times - */ + ///

+ /// Validates the payload received from the other participant during round 2. + /// Note that this DOES NOT detect a non-common password. + /// The only indication of a non-common password is through derivation + /// of different keys (which can be detected explicitly by executing round 3 and round 4) + /// + /// Must be called prior to CalculateKeyingMaterial(). + /// + /// After execution, the State state will be STATE_ROUND_2_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws + /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload), or multiple times + /// public void ValidateRound2PayloadReceived(JPAKERound2Payload round2PayloadReceived) { if (this.state >= STATE_ROUND_2_VALIDATED) @@ -370,31 +332,31 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake this.state = STATE_ROUND_2_VALIDATED; } - /** - * Calculates and returns the key material. - * A session key must be derived from this key material using a secure key derivation function (KDF). - * The KDF used to derive the key is handled externally (i.e. not by {@link JPAKEParticipant}). - *

- * The keying material will be identical for each participant if and only if - * each participant's password is the same. i.e. If the participants do not - * share the same password, then each participant will derive a different key. - * Therefore, if you immediately start using a key derived from - * the keying material, then you must handle detection of incorrect keys. - * If you want to handle this detection explicitly, you can optionally perform - * rounds 3 and 4. See {@link JPAKEParticipant} for details on how to execute - * rounds 3 and 4. - *

- * The keying material will be in the range [0, p-1]. - *

- * {@link #validateRound2PayloadReceived(JPAKERound2Payload)} must be called prior to this method. - *

- * As a side effect, the internal {@link #password} array is cleared, since it is no longer needed. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_KEY_CALCULATED}. - * - * @throws InvalidOperationException if called prior to {@link #validateRound2PayloadReceived(JPAKERound2Payload)}, - * or if called multiple times. - */ + ///

+ /// Calculates and returns the key material. + /// A session key must be derived from this key material using a secure key derivation function (KDF). + /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). + /// + /// The keying material will be identical for each participant if and only if + /// each participant's password is the same. i.e. If the participants do not + /// share the same password, then each participant will derive a different key. + /// Therefore, if you immediately start using a key derived from + /// the keying material, then you must handle detection of incorrect keys. + /// If you want to handle this detection explicitly, you can optionally perform + /// rounds 3 and 4. See JPAKEParticipant for details on how to execute + /// rounds 3 and 4. + /// + /// The keying material will be in the range [0, p-1]. + /// + /// ValidateRound2PayloadReceived(JPAKERound2Payload) must be called prior to this method. + /// + /// As a side effect, the internal password array is cleared, since it is no longer needed. + /// + /// After execution, the State state will be STATE_KEY_CALCULATED. + /// + /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPAKERound2Payload), + /// or if called multiple times. + /// public BigInteger CalculateKeyingMaterial() { if (this.state >= STATE_KEY_CALCULATED) @@ -408,47 +370,40 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake BigInteger s = JPAKEUtil.CalculateS(password); - /* - * Clear the password array from memory, since we don't need it anymore. - * - * Also set the field to null as a flag to indicate that the key has already been calculated. - */ + // Clear the password array from memory, since we don't need it anymore. + // Also set the field to null as a flag to indicate that the key has already been calculated. Array.Clear(password, 0, password.Length); this.password = null; BigInteger keyingMaterial = JPAKEUtil.CalculateKeyingMaterial(p, q, gx4, x2, s, b); - /* - * Clear the ephemeral private key fields as well. - * Note that we're relying on the garbage collector to do its job to clean these up. - * The old objects will hang around in memory until the garbage collector destroys them. - * - * If the ephemeral private keys x1 and x2 are leaked, - * the attacker might be able to brute-force the password. - */ + // Clear the ephemeral private key fields as well. + // Note that we're relying on the garbage collector to do its job to clean these up. + // The old objects will hang around in memory until the garbage collector destroys them. + // + // If the ephemeral private keys x1 and x2 are leaked, + // the attacker might be able to brute-force the password. this.x1 = null; this.x2 = null; this.b = null; - /* - * Do not clear gx* yet, since those are needed by round 3. - */ + // Do not clear gx* yet, since those are needed by round 3. this.state = STATE_KEY_CALCULATED; return keyingMaterial; } - /** - * Creates and returns the payload to send to the other participant during round 3. - *

- * See {@link JPAKEParticipant} for more details on round 3. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_CREATED}. - * - * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. - * @throws InvalidOperationException if called prior to {@link #calculateKeyingMaterial()}, or multiple times - */ + ///

+ /// Creates and returns the payload to send to the other participant during round 3. + /// + /// See JPAKEParticipant for more details on round 3. + /// + /// After execution, the State state} will be STATE_ROUND_3_CREATED. + /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple + /// times. + /// + /// The keying material as returned from CalculateKeyingMaterial(). public JPAKERound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) { if (this.state >= STATE_ROUND_3_CREATED) @@ -475,17 +430,16 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake return new JPAKERound3Payload(participantId, macTag); } - /** - * Validates the payload received from the other participant during round 3. - *

- * See {@link JPAKEParticipant} for more details on round 3. - *

- * After execution, the {@link #getState() state} will be {@link #STATE_ROUND_3_VALIDATED}. - * - * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. - * @throws CryptoException if validation fails. - * @throws InvalidOperationException if called prior to {@link #calculateKeyingMaterial()}, or multiple times - */ + ///

+ /// Validates the payload received from the other participant during round 3. + /// + /// See JPAKEParticipant for more details on round 3. + /// + /// After execution, the State state will be STATE_ROUND_3_VALIDATED. + /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to + /// CalculateKeyingMaterial or multiple times + /// + /// The keying material as returned from CalculateKeyingMaterial(). public void ValidateRound3PayloadReceived(JPAKERound3Payload round3PayloadReceived, BigInteger keyingMaterial) { if (this.state >= STATE_ROUND_3_VALIDATED) @@ -511,9 +465,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake this.digest, round3PayloadReceived.MacTag); - /* - * Clear the rest of the fields. - */ + // Clear the rest of the fields. this.gx1 = null; this.gx2 = null; this.gx3 = null; diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs index 1b0c514bb..3a142f713 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs @@ -4,62 +4,59 @@ using Org.BouncyCastle.Math; namespace Org.BouncyCastle.Crypto.Agreement.Jpake { - /** - * A pre-computed prime order group for use during a J-PAKE exchange. - *

- * Typically a Schnorr group is used. In general, J-PAKE can use any prime order group - * that is suitable for public key cryptography, including elliptic curve cryptography. - *

- * See {@link JPAKEPrimeOrderGroups} for convenient standard groups. - *

- * NIST publishes - * many groups that can be used for the desired level of security. - */ + ///

+ /// A pre-computed prime order group for use during a J-PAKE exchange. + /// + /// Typically a Schnorr group is used. In general, J-PAKE can use any prime order group + /// that is suitable for public key cryptography, including elliptic curve cryptography. + /// + /// See JPAKEPrimeOrderGroups for convenient standard groups. + /// + /// NIST publishes + /// many groups that can be used for the desired level of security. + /// public class JPAKEPrimeOrderGroup { private readonly BigInteger p; private readonly BigInteger q; private readonly BigInteger g; - /** - * Constructs a new {@link JPAKEPrimeOrderGroup}. - *

- * In general, you should use one of the pre-approved groups from - * {@link JPAKEPrimeOrderGroups}, rather than manually constructing one. - *

- * The following basic checks are performed: - *

- *

- * The prime checks are performed using {@link BigInteger#isProbablePrime(int)}, - * and are therefore subject to the same probability guarantees. - *

- * These checks prevent trivial mistakes. - * However, due to the small uncertainties if p and q are not prime, - * advanced attacks are not prevented. - * Use it at your own risk. - * - * @throws NullReferenceException if any argument is null - * @throws InvalidOperationException if any of the above validations fail - */ + ///

+ /// Constructs a new JPAKEPrimeOrderGroup. + /// + /// In general, you should use one of the pre-approved groups from + /// JPAKEPrimeOrderGroups, rather than manually constructing one. + /// + /// The following basic checks are performed: + /// + /// p-1 must be evenly divisible by q + /// g must be in [2, p-1] + /// g^q mod p must equal 1 + /// p must be prime (within reasonably certainty) + /// q must be prime (within reasonably certainty) + /// + /// The prime checks are performed using BigInteger#isProbablePrime(int), + /// and are therefore subject to the same probability guarantees. + /// + /// These checks prevent trivial mistakes. + /// However, due to the small uncertainties if p and q are not prime, + /// advanced attacks are not prevented. + /// Use it at your own risk. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// InvalidOperationException is any of the above validations fail. + /// public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) : this(p, q, g, false) { - /* - * Don't skip the checks on user-specified groups. - */ + // Don't skip the checks on user-specified groups. } - /** - * Internal package-private constructor used by the pre-approved - * groups in {@link JPAKEPrimeOrderGroups}. - * These pre-approved groups can avoid the expensive checks. - */ + /// + /// Constructor used by the pre-approved groups in JPAKEPrimeOrderGroups. + /// These pre-approved groups can avoid the expensive checks. + /// User-specified groups should not use this constructor. + /// public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) { JPAKEUtil.ValidateNotNull(p, "p"); @@ -80,10 +77,8 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake { throw new ArgumentException("g^q mod p must equal 1"); } - /* - * Note these checks do not guarantee that p and q are prime. - * We just have reasonable certainty that they are prime. - */ + // Note these checks do not guarantee that p and q are prime. + // We just have reasonable certainty that they are prime. if (!p.IsProbablePrime(20)) { throw new ArgumentException("p must be prime"); -- cgit 1.5.1 From 4397adf4acf11009caf62d34b5f74bb5703db4e2 Mon Sep 17 00:00:00 2001 From: Alex van Poppelen Date: Sat, 17 Oct 2015 03:45:48 +0200 Subject: more commenting fixes --- crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'crypto') diff --git a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs index bd18765b4..0874f3d83 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs @@ -108,6 +108,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake // See the STATE_* constants for possible values. private int state; + /// /// Convenience constructor for a new JPAKEParticipant that uses /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, /// a SHA-256 digest, and a default SecureRandom implementation. @@ -125,8 +126,8 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake public JPAKEParticipant(string participantId, char[] password) : this(participantId, password, JPAKEPrimeOrderGroups.NIST_3072) { } + /// /// Convenience constructor for a new JPAKEParticipant that uses - /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, /// a SHA-256 digest, and a default SecureRandom implementation. /// /// After construction, the State state will be STATE_INITIALIZED. @@ -144,9 +145,8 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } - /// Convenience constructor for a new JPAKEParticipant that uses - /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, - /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// Constructor for a new JPAKEParticipant. /// /// After construction, the State state will be STATE_INITIALIZED. /// @@ -241,6 +241,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Must be called prior to CreateRound2PayloadToSend(). /// /// After execution, the State state will be STATE_ROUND_1_VALIDATED. + /// /// Throws CryptoException if validation fails. Throws InvalidOperationException /// if called multiple times. /// @@ -268,11 +269,11 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// Creates and returns the payload to send to the other participant during round 2. /// - /// ValidateRound1PayloadReceived(JPAKERound1Payload)} must be called prior to this method. + /// ValidateRound1PayloadReceived(JPAKERound1Payload) must be called prior to this method. /// - //// After execution, the State state will be STATE_ROUND_2_CREATED. + /// After execution, the State state will be STATE_ROUND_2_CREATED. /// - /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload)}, or multiple times + /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload), or multiple times /// public JPAKERound2Payload CreateRound2PayloadToSend() { @@ -304,7 +305,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// Must be called prior to CalculateKeyingMaterial(). /// - /// After execution, the State state will be STATE_ROUND_2_VALIDATED. + /// After execution, the State state will be STATE_ROUND_2_VALIDATED. /// /// Throws CryptoException if validation fails. Throws /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload), or multiple times @@ -436,6 +437,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// See JPAKEParticipant for more details on round 3. /// /// After execution, the State state will be STATE_ROUND_3_VALIDATED. + /// /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to /// CalculateKeyingMaterial or multiple times /// -- cgit 1.5.1 From 2bb4400d228fd41191bc7aac4f1529c9c02a4e83 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 17 Oct 2015 14:35:17 +0700 Subject: Various JPAKE changes to fit existing code conventions - Update project file with new entries - Tests moved to crypto/agreement/test --- crypto-test/CryptoTest.cs | 43 +- crypto/crypto.csproj | 55 ++ .../src/crypto/agreement/jpake/JPAKEParticipant.cs | 162 +++--- .../crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs | 45 +- .../agreement/jpake/JPAKEPrimeOrderGroups.cs | 10 +- .../crypto/agreement/jpake/JPAKERound1Payload.cs | 28 +- .../crypto/agreement/jpake/JPAKERound2Payload.cs | 20 +- .../crypto/agreement/jpake/JPAKERound3Payload.cs | 12 +- crypto/src/crypto/agreement/jpake/JPAKEUtil.cs | 404 --------------- .../src/crypto/agreement/jpake/JPakeUtilities.cs | 391 ++++++++++++++ crypto/test/src/crypto/agreement/test/AllTests.cs | 31 ++ .../crypto/agreement/test/JPAKEParticipantTest.cs | 566 +++++++++++++++++++++ .../agreement/test/JPakePrimeOrderGroupTest.cs | 117 +++++ .../crypto/agreement/test/JPakeUtilitiesTest.cs | 306 +++++++++++ .../test/src/crypto/test/JPAKEParticipantTest.cs | 566 --------------------- .../src/crypto/test/JPAKEPrimeOrderGroupTest.cs | 117 ----- crypto/test/src/crypto/test/JPAKEUtilTest.cs | 306 ----------- 17 files changed, 1610 insertions(+), 1569 deletions(-) delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKEUtil.cs create mode 100644 crypto/src/crypto/agreement/jpake/JPakeUtilities.cs create mode 100644 crypto/test/src/crypto/agreement/test/AllTests.cs create mode 100644 crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs create mode 100644 crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs create mode 100644 crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs delete mode 100644 crypto/test/src/crypto/test/JPAKEParticipantTest.cs delete mode 100644 crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs delete mode 100644 crypto/test/src/crypto/test/JPAKEUtilTest.cs (limited to 'crypto') diff --git a/crypto-test/CryptoTest.cs b/crypto-test/CryptoTest.cs index 1f6973e28..5a411b482 100644 --- a/crypto-test/CryptoTest.cs +++ b/crypto-test/CryptoTest.cs @@ -14,27 +14,28 @@ namespace crypto_test try { - Org.BouncyCastle.Asn1.Tests.RegressionTest.Main(args); - //Org.BouncyCastle.Bcpg.OpenPgp.Tests.Dsa2Test.? - Org.BouncyCastle.Bcpg.OpenPgp.Tests.RegressionTest.Main(args); - Org.BouncyCastle.Bcpg.OpenPgp.Examples.Tests.AllTests.Main(args); - Org.BouncyCastle.Cms.Tests.AllTests.Main(args); - Org.BouncyCastle.Crypto.Tests.RegressionTest.Main(args); - Org.BouncyCastle.Crypto.IO.Tests.AllTests.Main(args); - Org.BouncyCastle.Math.Tests.AllTests.Main(args); - Org.BouncyCastle.Math.EC.Tests.AllTests.Main(args); - Org.BouncyCastle.Ocsp.Tests.AllTests.Main(args); - //Org.BouncyCastle.Pkcs.Tests.? - Org.BouncyCastle.Pkcs.Tests.EncryptedPrivateKeyInfoTest.Main(args); - Org.BouncyCastle.Pkcs.Tests.Pkcs10Test.Main(args); - Org.BouncyCastle.Pkcs.Tests.Pkcs12StoreTest.Main(args); - //Org.BouncyCastle.OpenSsl.Tests.? - Org.BouncyCastle.OpenSsl.Tests.ReaderTest.Main(args); - Org.BouncyCastle.OpenSsl.Tests.WriterTest.Main(args); - //Org.BouncyCastle.Security.Tests.? - Org.BouncyCastle.Tests.RegressionTest.Main(args); - Org.BouncyCastle.Tsp.Tests.AllTests.Main(args); - //Org.BouncyCastle.X509.Tests.? + Org.BouncyCastle.Asn1.Tests.RegressionTest.Main(args); + //Org.BouncyCastle.Bcpg.OpenPgp.Tests.Dsa2Test.? + Org.BouncyCastle.Bcpg.OpenPgp.Tests.RegressionTest.Main(args); + Org.BouncyCastle.Bcpg.OpenPgp.Examples.Tests.AllTests.Main(args); + Org.BouncyCastle.Cms.Tests.AllTests.Main(args); + Org.BouncyCastle.Crypto.Agreement.Tests.AllTests.Main(args); + Org.BouncyCastle.Crypto.Tests.RegressionTest.Main(args); + Org.BouncyCastle.Crypto.IO.Tests.AllTests.Main(args); + Org.BouncyCastle.Math.Tests.AllTests.Main(args); + Org.BouncyCastle.Math.EC.Tests.AllTests.Main(args); + Org.BouncyCastle.Ocsp.Tests.AllTests.Main(args); + //Org.BouncyCastle.Pkcs.Tests.? + Org.BouncyCastle.Pkcs.Tests.EncryptedPrivateKeyInfoTest.Main(args); + Org.BouncyCastle.Pkcs.Tests.Pkcs10Test.Main(args); + Org.BouncyCastle.Pkcs.Tests.Pkcs12StoreTest.Main(args); + //Org.BouncyCastle.OpenSsl.Tests.? + Org.BouncyCastle.OpenSsl.Tests.ReaderTest.Main(args); + Org.BouncyCastle.OpenSsl.Tests.WriterTest.Main(args); + //Org.BouncyCastle.Security.Tests.? + Org.BouncyCastle.Tests.RegressionTest.Main(args); + Org.BouncyCastle.Tsp.Tests.AllTests.Main(args); + //Org.BouncyCastle.X509.Tests.? } catch (Exception e) { diff --git a/crypto/crypto.csproj b/crypto/crypto.csproj index df7df9f5a..b80a3fec0 100644 --- a/crypto/crypto.csproj +++ b/crypto/crypto.csproj @@ -3198,6 +3198,41 @@ SubType = "Code" BuildAction = "Compile" /> + + + + + + + + + + + /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. @@ -16,20 +16,20 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// The J-PAKE protocol is symmetric. /// There is no notion of a client or server, but rather just two participants. - /// An instance of JPAKEParticipant represents one participant, and + /// An instance of JPakeParticipant represents one participant, and /// is the primary interface for executing the exchange. /// - /// To execute an exchange, construct a JPAKEParticipant on each end, + /// To execute an exchange, construct a JPakeParticipant on each end, /// and call the following 7 methods /// (once and only once, in the given order, for each participant, sending messages between them as described): /// /// CreateRound1PayloadToSend() - and send the payload to the other participant - /// ValidateRound1PayloadReceived(JPAKERound1Payload) - use the payload received from the other participant + /// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant /// CreateRound2PayloadToSend() - and send the payload to the other participant - /// ValidateRound2PayloadReceived(JPAKERound2Payload) - use the payload received from the other participant + /// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant /// CalculateKeyingMaterial() /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant - /// ValidateRound3PayloadReceived(JPAKERound3Payload, BigInteger) - use the payload received from the other participant + /// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant /// /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial(). /// The caller is responsible for deriving the session key using a secure key derivation function (KDF). @@ -51,9 +51,9 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// This class is stateful and NOT threadsafe. /// Each instance should only be used for ONE complete J-PAKE exchange - /// (i.e. a new JPAKEParticipant should be constructed for each new J-PAKE exchange). + /// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange). /// - public class JPAKEParticipant + public class JPakeParticipant { // Possible internal states. Used for state checking. public static readonly int STATE_INITIALIZED = 0; @@ -109,8 +109,8 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake private int state; /// - /// Convenience constructor for a new JPAKEParticipant that uses - /// the JPAKEPrimeOrderGroups#NIST_3072 prime order group, + /// Convenience constructor for a new JPakeParticipant that uses + /// the JPakePrimeOrderGroups#NIST_3072 prime order group, /// a SHA-256 digest, and a default SecureRandom implementation. /// /// After construction, the State state will be STATE_INITIALIZED. @@ -123,11 +123,11 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Shared secret. /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). /// Caller should clear the input password as soon as possible. - public JPAKEParticipant(string participantId, char[] password) - : this(participantId, password, JPAKEPrimeOrderGroups.NIST_3072) { } + public JPakeParticipant(string participantId, char[] password) + : this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { } /// - /// Convenience constructor for a new JPAKEParticipant that uses + /// Convenience constructor for a new JPakeParticipant that uses /// a SHA-256 digest, and a default SecureRandom implementation. /// /// After construction, the State state will be STATE_INITIALIZED. @@ -140,13 +140,13 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Shared secret. /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). /// Caller should clear the input password as soon as possible. - /// Prime order group. See JPAKEPrimeOrderGroups for standard groups. - public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group) + /// Prime order group. See JPakePrimeOrderGroups for standard groups. + public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group) : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } /// - /// Constructor for a new JPAKEParticipant. + /// Constructor for a new JPakeParticipant. /// /// After construction, the State state will be STATE_INITIALIZED. /// @@ -158,17 +158,17 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Shared secret. /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). /// Caller should clear the input password as soon as possible. - /// Prime order group. See JPAKEPrimeOrderGroups for standard groups. + /// Prime order group. See JPakePrimeOrderGroups for standard groups. /// Digest to use during zero knowledge proofs and key confirmation /// (SHA-256 or stronger preferred). /// Source of secure random data for x1 and x2, and for the zero knowledge proofs. - public JPAKEParticipant(string participantId, char[] password, JPAKEPrimeOrderGroup group, IDigest digest, SecureRandom random) + public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random) { - JPAKEUtil.ValidateNotNull(participantId, "participantId"); - JPAKEUtil.ValidateNotNull(password, "password"); - JPAKEUtil.ValidateNotNull(group, "p"); - JPAKEUtil.ValidateNotNull(digest, "digest"); - JPAKEUtil.ValidateNotNull(random, "random"); + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(password, "password"); + JPakeUtilities.ValidateNotNull(group, "p"); + JPakeUtilities.ValidateNotNull(digest, "digest"); + JPakeUtilities.ValidateNotNull(random, "random"); if (password.Length == 0) { @@ -204,7 +204,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Gets the current state of this participant. /// See the STATE_* constants for possible values. /// - public int State + public virtual int State { get { return state; } } @@ -215,24 +215,22 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// After execution, the State state} will be STATE_ROUND_1_CREATED}. /// - public JPAKERound1Payload CreateRound1PayloadToSend() + public virtual JPakeRound1Payload CreateRound1PayloadToSend() { if (this.state >= STATE_ROUND_1_CREATED) - { throw new InvalidOperationException("Round 1 payload already created for " + this.participantId); - } - this.x1 = JPAKEUtil.GenerateX1(q, random); - this.x2 = JPAKEUtil.GenerateX2(q, random); + this.x1 = JPakeUtilities.GenerateX1(q, random); + this.x2 = JPakeUtilities.GenerateX2(q, random); - this.gx1 = JPAKEUtil.CalculateGx(p, g, x1); - this.gx2 = JPAKEUtil.CalculateGx(p, g, x2); - BigInteger[] knowledgeProofForX1 = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); - BigInteger[] knowledgeProofForX2 = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); + this.gx1 = JPakeUtilities.CalculateGx(p, g, x1); + this.gx2 = JPakeUtilities.CalculateGx(p, g, x2); + BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); + BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); this.state = STATE_ROUND_1_CREATED; - return new JPAKERound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); } /// @@ -245,12 +243,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Throws CryptoException if validation fails. Throws InvalidOperationException /// if called multiple times. /// - public void ValidateRound1PayloadReceived(JPAKERound1Payload round1PayloadReceived) + public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived) { if (this.state >= STATE_ROUND_1_VALIDATED) - { throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId); - } this.partnerParticipantId = round1PayloadReceived.ParticipantId; this.gx3 = round1PayloadReceived.Gx1; @@ -259,42 +255,38 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1; BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2; - JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); - JPAKEUtil.ValidateGx4(gx4); - JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); - JPAKEUtil.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); + JPakeUtilities.ValidateGx4(gx4); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); this.state = STATE_ROUND_1_VALIDATED; } /// /// Creates and returns the payload to send to the other participant during round 2. /// - /// ValidateRound1PayloadReceived(JPAKERound1Payload) must be called prior to this method. + /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method. /// /// After execution, the State state will be STATE_ROUND_2_CREATED. /// - /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload), or multiple times + /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times /// - public JPAKERound2Payload CreateRound2PayloadToSend() + public virtual JPakeRound2Payload CreateRound2PayloadToSend() { if (this.state >= STATE_ROUND_2_CREATED) - { throw new InvalidOperationException("Round 2 payload already created for " + this.participantId); - } if (this.state < STATE_ROUND_1_VALIDATED) - { throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId); - } - BigInteger gA = JPAKEUtil.CalculateGA(p, gx1, gx3, gx4); - BigInteger s = JPAKEUtil.CalculateS(password); - BigInteger x2s = JPAKEUtil.CalculateX2s(q, x2, s); - BigInteger A = JPAKEUtil.CalculateA(p, q, gA, x2s); - BigInteger[] knowledgeProofForX2s = JPAKEUtil.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); + BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4); + BigInteger s = JPakeUtilities.CalculateS(password); + BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s); + BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s); + BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); this.state = STATE_ROUND_2_CREATED; - return new JPAKERound2Payload(participantId, A, knowledgeProofForX2s); + return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s); } /// @@ -308,27 +300,23 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// After execution, the State state will be STATE_ROUND_2_VALIDATED. /// /// Throws CryptoException if validation fails. Throws - /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPAKERound1Payload), or multiple times + /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times /// - public void ValidateRound2PayloadReceived(JPAKERound2Payload round2PayloadReceived) + public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived) { if (this.state >= STATE_ROUND_2_VALIDATED) - { throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId); - } if (this.state < STATE_ROUND_1_VALIDATED) - { throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId); - } - BigInteger gB = JPAKEUtil.CalculateGA(p, gx3, gx1, gx2); + BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2); this.b = round2PayloadReceived.A; BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s; - JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId); - JPAKEUtil.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId); - JPAKEUtil.ValidateGa(gB); - JPAKEUtil.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest); + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId); + JPakeUtilities.ValidateGa(gB); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest); this.state = STATE_ROUND_2_VALIDATED; } @@ -336,7 +324,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// Calculates and returns the key material. /// A session key must be derived from this key material using a secure key derivation function (KDF). - /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). + /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant). /// /// The keying material will be identical for each participant if and only if /// each participant's password is the same. i.e. If the participants do not @@ -344,39 +332,35 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Therefore, if you immediately start using a key derived from /// the keying material, then you must handle detection of incorrect keys. /// If you want to handle this detection explicitly, you can optionally perform - /// rounds 3 and 4. See JPAKEParticipant for details on how to execute + /// rounds 3 and 4. See JPakeParticipant for details on how to execute /// rounds 3 and 4. /// /// The keying material will be in the range [0, p-1]. /// - /// ValidateRound2PayloadReceived(JPAKERound2Payload) must be called prior to this method. + /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method. /// /// As a side effect, the internal password array is cleared, since it is no longer needed. /// /// After execution, the State state will be STATE_KEY_CALCULATED. /// - /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPAKERound2Payload), + /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload), /// or if called multiple times. /// - public BigInteger CalculateKeyingMaterial() + public virtual BigInteger CalculateKeyingMaterial() { if (this.state >= STATE_KEY_CALCULATED) - { throw new InvalidOperationException("Key already calculated for " + participantId); - } if (this.state < STATE_ROUND_2_VALIDATED) - { throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId); - } - BigInteger s = JPAKEUtil.CalculateS(password); + BigInteger s = JPakeUtilities.CalculateS(password); // Clear the password array from memory, since we don't need it anymore. // Also set the field to null as a flag to indicate that the key has already been calculated. Array.Clear(password, 0, password.Length); this.password = null; - BigInteger keyingMaterial = JPAKEUtil.CalculateKeyingMaterial(p, q, gx4, x2, s, b); + BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b); // Clear the ephemeral private key fields as well. // Note that we're relying on the garbage collector to do its job to clean these up. @@ -398,25 +382,21 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// Creates and returns the payload to send to the other participant during round 3. /// - /// See JPAKEParticipant for more details on round 3. + /// See JPakeParticipant for more details on round 3. /// /// After execution, the State state} will be STATE_ROUND_3_CREATED. /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple /// times. /// /// The keying material as returned from CalculateKeyingMaterial(). - public JPAKERound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) + public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) { if (this.state >= STATE_ROUND_3_CREATED) - { throw new InvalidOperationException("Round 3 payload already created for " + this.participantId); - } if (this.state < STATE_KEY_CALCULATED) - { throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId); - } - BigInteger macTag = JPAKEUtil.CalculateMacTag( + BigInteger macTag = JPakeUtilities.CalculateMacTag( this.participantId, this.partnerParticipantId, this.gx1, @@ -428,13 +408,13 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake this.state = STATE_ROUND_3_CREATED; - return new JPAKERound3Payload(participantId, macTag); + return new JPakeRound3Payload(participantId, macTag); } /// /// Validates the payload received from the other participant during round 3. /// - /// See JPAKEParticipant for more details on round 3. + /// See JPakeParticipant for more details on round 3. /// /// After execution, the State state will be STATE_ROUND_3_VALIDATED. /// @@ -442,21 +422,17 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// CalculateKeyingMaterial or multiple times /// /// The keying material as returned from CalculateKeyingMaterial(). - public void ValidateRound3PayloadReceived(JPAKERound3Payload round3PayloadReceived, BigInteger keyingMaterial) + public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial) { if (this.state >= STATE_ROUND_3_VALIDATED) - { throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId); - } if (this.state < STATE_KEY_CALCULATED) - { throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId); - } - JPAKEUtil.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId); - JPAKEUtil.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId); - JPAKEUtil.ValidateMacTag( + JPakeUtilities.ValidateMacTag( this.participantId, this.partnerParticipantId, this.gx1, diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs index 3a142f713..08ffe1a55 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs @@ -2,7 +2,7 @@ using Org.BouncyCastle.Math; -namespace Org.BouncyCastle.Crypto.Agreement.Jpake +namespace Org.BouncyCastle.Crypto.Agreement.JPake { /// /// A pre-computed prime order group for use during a J-PAKE exchange. @@ -10,22 +10,22 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Typically a Schnorr group is used. In general, J-PAKE can use any prime order group /// that is suitable for public key cryptography, including elliptic curve cryptography. /// - /// See JPAKEPrimeOrderGroups for convenient standard groups. + /// See JPakePrimeOrderGroups for convenient standard groups. /// /// NIST publishes /// many groups that can be used for the desired level of security. /// - public class JPAKEPrimeOrderGroup + public class JPakePrimeOrderGroup { private readonly BigInteger p; private readonly BigInteger q; private readonly BigInteger g; /// - /// Constructs a new JPAKEPrimeOrderGroup. + /// Constructs a new JPakePrimeOrderGroup. /// /// In general, you should use one of the pre-approved groups from - /// JPAKEPrimeOrderGroups, rather than manually constructing one. + /// JPakePrimeOrderGroups, rather than manually constructing one. /// /// The following basic checks are performed: /// @@ -46,47 +46,38 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Throws NullReferenceException if any argument is null. Throws /// InvalidOperationException is any of the above validations fail. /// - public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) + public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) : this(p, q, g, false) { // Don't skip the checks on user-specified groups. } /// - /// Constructor used by the pre-approved groups in JPAKEPrimeOrderGroups. + /// Constructor used by the pre-approved groups in JPakePrimeOrderGroups. /// These pre-approved groups can avoid the expensive checks. /// User-specified groups should not use this constructor. /// - public JPAKEPrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) + public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) { - JPAKEUtil.ValidateNotNull(p, "p"); - JPAKEUtil.ValidateNotNull(q, "q"); - JPAKEUtil.ValidateNotNull(g, "g"); + JPakeUtilities.ValidateNotNull(p, "p"); + JPakeUtilities.ValidateNotNull(q, "q"); + JPakeUtilities.ValidateNotNull(g, "g"); if (!skipChecks) { - if (!p.Subtract(JPAKEUtil.ONE).Mod(q).Equals(JPAKEUtil.ZERO)) - { + if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero)) throw new ArgumentException("p-1 must be evenly divisible by q"); - } - if (g.CompareTo(BigInteger.ValueOf(2)) == -1 || g.CompareTo(p.Subtract(JPAKEUtil.ONE)) == 1) - { + if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1) throw new ArgumentException("g must be in [2, p-1]"); - } - if (!g.ModPow(q, p).Equals(JPAKEUtil.ONE)) - { + if (!g.ModPow(q, p).Equals(JPakeUtilities.One)) throw new ArgumentException("g^q mod p must equal 1"); - } + // Note these checks do not guarantee that p and q are prime. // We just have reasonable certainty that they are prime. if (!p.IsProbablePrime(20)) - { throw new ArgumentException("p must be prime"); - } if (!q.IsProbablePrime(20)) - { throw new ArgumentException("q must be prime"); - } } this.p = p; @@ -94,17 +85,17 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake this.g = g; } - public BigInteger P + public virtual BigInteger P { get { return p; } } - public BigInteger Q + public virtual BigInteger Q { get { return q; } } - public BigInteger G + public virtual BigInteger G { get { return g; } } diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs index aeaff6f72..33640845a 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs @@ -1,6 +1,6 @@ using Org.BouncyCastle.Math; -namespace Org.BouncyCastle.Crypto.Agreement.Jpake +namespace Org.BouncyCastle.Crypto.Agreement.JPake { /// /// Standard pre-computed prime order groups for use by J-PAKE. @@ -13,13 +13,13 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// and from the prime order groups /// published by NIST. /// - public class JPAKEPrimeOrderGroups + public class JPakePrimeOrderGroups { /// /// From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) /// 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. /// - public static readonly JPAKEPrimeOrderGroup SUN_JCE_1024 = new JPAKEPrimeOrderGroup( + public static readonly JPakePrimeOrderGroup SUN_JCE_1024 = new JPakePrimeOrderGroup( // p new BigInteger( "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + @@ -41,7 +41,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// From NIST. /// 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. /// - public static readonly JPAKEPrimeOrderGroup NIST_2048 = new JPAKEPrimeOrderGroup( + public static readonly JPakePrimeOrderGroup NIST_2048 = new JPakePrimeOrderGroup( // p new BigInteger( "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" + @@ -71,7 +71,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// From NIST. /// 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. /// - public static readonly JPAKEPrimeOrderGroup NIST_3072 = new JPAKEPrimeOrderGroup( + public static readonly JPakePrimeOrderGroup NIST_3072 = new JPakePrimeOrderGroup( // p new BigInteger( "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs index 7b638dabd..9e4ab7a5f 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs @@ -2,7 +2,7 @@ using Org.BouncyCastle.Math; -namespace Org.BouncyCastle.Crypto.Agreement.Jpake +namespace Org.BouncyCastle.Crypto.Agreement.JPake { /// /// The payload sent/received during the first round of a J-PAKE exchange. @@ -13,9 +13,9 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// /// Each participant must also validate the payload received from the other. /// The received payload should be validated via - /// JPAKEParticipant.ValidateRound1PayloadReceived(JPAKERound1Payload). + /// JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload). /// - public class JPAKERound1Payload + public class JPakeRound1Payload { /// /// The id of the JPAKEParticipant who created/sent this payload. @@ -46,13 +46,13 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// private readonly BigInteger[] knowledgeProofForX2; - public JPAKERound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2) + public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2) { - JPAKEUtil.ValidateNotNull(participantId, "participantId"); - JPAKEUtil.ValidateNotNull(gx1, "gx1"); - JPAKEUtil.ValidateNotNull(gx2, "gx2"); - JPAKEUtil.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); - JPAKEUtil.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(gx1, "gx1"); + JPakeUtilities.ValidateNotNull(gx2, "gx2"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); this.participantId = participantId; this.gx1 = gx1; @@ -63,22 +63,22 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length); } - public string ParticipantId + public virtual string ParticipantId { get { return participantId; } } - public BigInteger Gx1 + public virtual BigInteger Gx1 { get { return gx1; } } - public BigInteger Gx2 + public virtual BigInteger Gx2 { get { return gx2; } } - public BigInteger[] KnowledgeProofForX1 + public virtual BigInteger[] KnowledgeProofForX1 { get { @@ -88,7 +88,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake } } - public BigInteger[] KnowledgeProofForX2 + public virtual BigInteger[] KnowledgeProofForX2 { get { diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs index cf1a8575e..47962cb3f 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs @@ -3,7 +3,7 @@ using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; -namespace Org.BouncyCastle.Crypto.Agreement.Jpake +namespace Org.BouncyCastle.Crypto.Agreement.JPake { /// /// The payload sent/received during the second round of a J-PAKE exchange. @@ -16,9 +16,9 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Each JPAKEParticipant must also validate the payload /// received from the other JPAKEParticipant. /// The received payload should be validated via - /// JPAKEParticipant#validateRound2PayloadReceived(JPAKERound2Payload) + /// JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload) /// - public class JPAKERound2Payload + public class JPakeRound2Payload { /// /// The id of the JPAKEParticipant who created/sent this payload. @@ -37,11 +37,11 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// private readonly BigInteger[] knowledgeProofForX2s; - public JPAKERound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s) + public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s) { - JPAKEUtil.ValidateNotNull(participantId, "participantId"); - JPAKEUtil.ValidateNotNull(a, "a"); - JPAKEUtil.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(a, "a"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); this.participantId = participantId; this.a = a; @@ -49,17 +49,17 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0); } - public string ParticipantId + public virtual string ParticipantId { get { return participantId; } } - public BigInteger A + public virtual BigInteger A { get { return a; } } - public BigInteger[] KnowledgeProofForX2s + public virtual BigInteger[] KnowledgeProofForX2s { get { diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs index 95e0f24ce..767702f23 100755 --- a/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs +++ b/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs @@ -2,7 +2,7 @@ using Org.BouncyCastle.Math; -namespace Org.BouncyCastle.Crypto.Agreement.Jpake +namespace Org.BouncyCastle.Crypto.Agreement.JPake { /// /// The payload sent/received during the optional third round of a J-PAKE exchange, @@ -16,9 +16,9 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// Eeach JPAKEParticipant must also validate the payload /// received from the other JPAKEParticipant. /// The received payload should be validated via - /// JPAKEParticipant#validateRound3PayloadReceived(JPAKERound3Payload, BigInteger) + /// JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger) /// - public class JPAKERound3Payload + public class JPakeRound3Payload { /// /// The id of the {@link JPAKEParticipant} who created/sent this payload. @@ -32,18 +32,18 @@ namespace Org.BouncyCastle.Crypto.Agreement.Jpake /// private readonly BigInteger macTag; - public JPAKERound3Payload(string participantId, BigInteger magTag) + public JPakeRound3Payload(string participantId, BigInteger magTag) { this.participantId = participantId; this.macTag = magTag; } - public string ParticipantId + public virtual string ParticipantId { get { return participantId; } } - public BigInteger MacTag + public virtual BigInteger MacTag { get { return macTag; } } diff --git a/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs b/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs deleted file mode 100755 index 34f8c2685..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKEUtil.cs +++ /dev/null @@ -1,404 +0,0 @@ -using System; -using System.Text; - -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Macs; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities; - -namespace Org.BouncyCastle.Crypto.Agreement.Jpake -{ - /// - /// Primitives needed for a J-PAKE exchange. - /// - /// The recommended way to perform a J-PAKE exchange is by using - /// two JPAKEParticipants. Internally, those participants - /// call these primitive operations in JPAKEUtil. - /// - /// The primitives, however, can be used without a JPAKEParticipant if needed. - /// - public class JPAKEUtil - { - public static BigInteger ZERO = BigInteger.ValueOf(0); - public static BigInteger ONE = BigInteger.ValueOf(1); - - /// - /// Return a value that can be used as x1 or x3 during round 1. - /// The returned value is a random value in the range [0, q-1]. - /// - public static BigInteger GenerateX1(BigInteger q, SecureRandom random) - { - BigInteger min = ZERO; - BigInteger max = q.Subtract(ONE); - return BigIntegers.CreateRandomInRange(min, max, random); - } - - /// - /// Return a value that can be used as x2 or x4 during round 1. - /// The returned value is a random value in the range [1, q-1]. - /// - public static BigInteger GenerateX2(BigInteger q, SecureRandom random) - { - BigInteger min = ONE; - BigInteger max = q.Subtract(ONE); - return BigIntegers.CreateRandomInRange(min, max, random); - } - - /// - /// Converts the given password to a BigInteger - /// for use in arithmetic calculations. - /// - public static BigInteger CalculateS(char[] password) - { - return new BigInteger(Strings.ToUtf8ByteArray(password)); - } - - /// - /// Calculate g^x mod p as done in round 1. - /// - public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x) - { - return g.ModPow(x, p); - } - - /// - /// Calculate ga as done in round 2. - /// - public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4) - { - // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 - return gx1.Multiply(gx3).Multiply(gx4).Mod(p); - } - - /// - /// Calculate x2 * s as done in round 2. - /// - public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s) - { - return x2.Multiply(s).Mod(q); - } - - /// - /// Calculate A as done in round 2. - /// - public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s) - { - // A = ga^(x*s) - return gA.ModPow(x2s, p); - } - - /// - /// Calculate a zero knowledge proof of x using Schnorr's signature. - /// The returned array has two elements {g^v, r = v-x*h} for x. - /// - public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, - BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random) - { - BigInteger[] zeroKnowledgeProof = new BigInteger[2]; - - /* Generate a random v, and compute g^v */ - BigInteger vMin = ZERO; - BigInteger vMax = q.Subtract(ONE); - BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random); - - BigInteger gv = g.ModPow(v, p); - BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h - - zeroKnowledgeProof[0] = gv; - zeroKnowledgeProof[1] = v.Subtract(x.Multiply(h)).Mod(q); // r = v-x*h - - return zeroKnowledgeProof; - } - - private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx, - string participantId, IDigest digest) - { - digest.Reset(); - - UpdateDigestIncludingSize(digest, g); - - UpdateDigestIncludingSize(digest, gr); - - UpdateDigestIncludingSize(digest, gx); - - UpdateDigestIncludingSize(digest, participantId); - - byte[] output = new byte[digest.GetDigestSize()]; - digest.DoFinal(output, 0); - - return new BigInteger(output); - } - - /// - /// Validates that g^x4 is not 1. - /// throws CryptoException if g^x4 is 1 - /// - public static void ValidateGx4(BigInteger gx4) - { - if (gx4.Equals(ONE)) - { - throw new CryptoException("g^x validation failed. g^x should not be 1."); - } - } - - /// - /// Validates that ga is not 1. - /// - /// As described by Feng Hao... - /// Alice could simply check ga != 1 to ensure it is a generator. - /// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks. - /// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q. - /// - /// throws CryptoException if ga is 1 - /// - public static void ValidateGa(BigInteger ga) - { - if (ga.Equals(ONE)) - { - throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again."); - } - } - - /// - /// Validates the zero knowledge proof (generated by - /// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom) - /// is correct. - /// - /// throws CryptoException if the zero knowledge proof is not correct - /// - public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, - BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest) - { - /* sig={g^v,r} */ - BigInteger gv = zeroKnowledgeProof[0]; - BigInteger r = zeroKnowledgeProof[1]; - - BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); - if (!(gx.CompareTo(ZERO) == 1 && // g^x > 0 - gx.CompareTo(p) == -1 && // g^x < p - gx.ModPow(q, p).CompareTo(ONE) == 0 && // g^x^q mod q = 1 - /* - * Below, I took a straightforward way to compute g^r * g^x^h, - * which needs 2 exp. Using a simultaneous computation technique - * would only need 1 exp. - */ - g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h - { - throw new CryptoException("Zero-knowledge proof validation failed"); - } - } - - /// - /// Calculates the keying material, which can be done after round 2 has completed. - /// A session key must be derived from this key material using a secure key derivation function (KDF). - /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). - /// - /// KeyingMaterial = (B/g^{x2*x4*s})^x2 - /// - public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q, - BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B) - { - return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p); - } - - /// - /// Validates that the given participant ids are not equal. - /// (For the J-PAKE exchange, each participant must use a unique id.) - /// - /// Throws CryptoException if the participantId strings are equal. - /// - public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2) - { - if (participantId1.Equals(participantId2)) - { - throw new CryptoException( - "Both participants are using the same participantId (" - + participantId1 - + "). This is not allowed. " - + "Each participant must use a unique participantId."); - } - } - - /// - /// Validates that the given participant ids are equal. - /// This is used to ensure that the payloads received from - /// each round all come from the same participant. - /// - public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId) - { - if (!expectedParticipantId.Equals(actualParticipantId)) - { - throw new CryptoException( - "Received payload from incorrect partner (" - + actualParticipantId - + "). Expected to receive payload from " - + expectedParticipantId - + "."); - } - } - - /// - /// Validates that the given object is not null. - /// throws NullReferenceException if the object is null. - /// - /// object in question - /// name of the object (to be used in exception message) - public static void ValidateNotNull(Object obj, string description) - { - if (obj == null) - { - throw new NullReferenceException(description + " must not be null"); - } - } - - /// - /// Calculates the MacTag (to be used for key confirmation), as defined by - /// NIST SP 800-56A Revision 1, - /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. - /// - /// MacTag = HMAC(MacKey, MacLen, MacData) - /// MacKey = H(K || "JPAKE_KC") - /// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4 - /// - /// Note that both participants use "KC_1_U" because the sender of the round 3 message - /// is always the initiator for key confirmation. - /// - /// HMAC = {@link HMac} used with the given {@link Digest} - /// H = The given {@link Digest} - /// MacLen = length of MacTag - /// - public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId, - BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest) - { - byte[] macKey = CalculateMacKey(keyingMaterial, digest); - - HMac mac = new HMac(digest); - byte[] macOutput = new byte[mac.GetMacSize()]; - mac.Init(new KeyParameter(macKey)); - - /* - * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4. - */ - UpdateMac(mac, "KC_1_U"); - UpdateMac(mac, participantId); - UpdateMac(mac, partnerParticipantId); - UpdateMac(mac, gx1); - UpdateMac(mac, gx2); - UpdateMac(mac, gx3); - UpdateMac(mac, gx4); - - mac.DoFinal(macOutput, 0); - - Arrays.Fill(macKey, (byte)0); - - return new BigInteger(macOutput); - } - - /// - /// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). - /// - /// MacKey = H(K || "JPAKE_KC") - /// - private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest) - { - digest.Reset(); - - UpdateDigest(digest, keyingMaterial); - /* - * This constant is used to ensure that the macKey is NOT the same as the derived key. - */ - UpdateDigest(digest, "JPAKE_KC"); - - byte[] output = new byte[digest.GetDigestSize()]; - digest.DoFinal(output, 0); - - return output; - } - - /// - /// Validates the MacTag received from the partner participant. - /// - /// throws CryptoException if the participantId strings are equal. - /// - /// the MacTag received from the partner - public static void ValidateMacTag(string participantId, string partnerParticipantId, - BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, - BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag) - { - /* - * Calculate the expected MacTag using the parameters as the partner - * would have used when the partner called calculateMacTag. - * - * i.e. basically all the parameters are reversed. - * participantId <-> partnerParticipantId - * x1 <-> x3 - * x2 <-> x4 - */ - BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest); - - if (!expectedMacTag.Equals(partnerMacTag)) - { - throw new CryptoException( - "Partner MacTag validation failed. " - + "Therefore, the password, MAC, or digest algorithm of each participant does not match."); - } - } - - private static void UpdateDigest(IDigest digest, BigInteger bigInteger) - { - byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger); - digest.BlockUpdate(byteArray, 0, byteArray.Length); - Arrays.Fill(byteArray, (byte)0); - } - - private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger) - { - byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger); - digest.BlockUpdate(IntToByteArray(byteArray.Length), 0, 4); - digest.BlockUpdate(byteArray, 0, byteArray.Length); - Arrays.Fill(byteArray, (byte)0); - } - - private static void UpdateDigest(IDigest digest, string str) - { - byte[] byteArray = Encoding.UTF8.GetBytes(str); - digest.BlockUpdate(byteArray, 0, byteArray.Length); - Arrays.Fill(byteArray, (byte)0); - } - - private static void UpdateDigestIncludingSize(IDigest digest, string str) - { - byte[] byteArray = Encoding.UTF8.GetBytes(str); - digest.BlockUpdate(IntToByteArray(byteArray.Length), 0, 4); - digest.BlockUpdate(byteArray, 0, byteArray.Length); - Arrays.Fill(byteArray, (byte)0); - } - - private static void UpdateMac(IMac mac, BigInteger bigInteger) - { - byte[] byteArray = BigIntegers.AsUnsignedByteArray(bigInteger); - mac.BlockUpdate(byteArray, 0, byteArray.Length); - Arrays.Fill(byteArray, (byte)0); - } - - private static void UpdateMac(IMac mac, string str) - { - byte[] byteArray = Encoding.UTF8.GetBytes(str); - mac.BlockUpdate(byteArray, 0, byteArray.Length); - Arrays.Fill(byteArray, (byte)0); - } - - private static byte[] IntToByteArray(int value) - { - return new byte[]{ - (byte)((uint)value >> 24), - (byte)((uint)value >> 16), - (byte)((uint)value >> 8), - (byte)value - }; - } - - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs b/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs new file mode 100644 index 000000000..eaf8af676 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs @@ -0,0 +1,391 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Macs; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto.Utilities; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// Primitives needed for a J-PAKE exchange. + /// + /// The recommended way to perform a J-PAKE exchange is by using + /// two JPAKEParticipants. Internally, those participants + /// call these primitive operations in JPakeUtilities. + /// + /// The primitives, however, can be used without a JPAKEParticipant if needed. + /// + public abstract class JPakeUtilities + { + public static readonly BigInteger Zero = BigInteger.Zero; + public static readonly BigInteger One = BigInteger.One; + + /// + /// Return a value that can be used as x1 or x3 during round 1. + /// The returned value is a random value in the range [0, q-1]. + /// + public static BigInteger GenerateX1(BigInteger q, SecureRandom random) + { + BigInteger min = Zero; + BigInteger max = q.Subtract(One); + return BigIntegers.CreateRandomInRange(min, max, random); + } + + /// + /// Return a value that can be used as x2 or x4 during round 1. + /// The returned value is a random value in the range [1, q-1]. + /// + public static BigInteger GenerateX2(BigInteger q, SecureRandom random) + { + BigInteger min = One; + BigInteger max = q.Subtract(One); + return BigIntegers.CreateRandomInRange(min, max, random); + } + + /// + /// Converts the given password to a BigInteger + /// for use in arithmetic calculations. + /// + public static BigInteger CalculateS(char[] password) + { + return new BigInteger(Encoding.UTF8.GetBytes(password)); + } + + /// + /// Calculate g^x mod p as done in round 1. + /// + public static BigInteger CalculateGx(BigInteger p, BigInteger g, BigInteger x) + { + return g.ModPow(x, p); + } + + /// + /// Calculate ga as done in round 2. + /// + public static BigInteger CalculateGA(BigInteger p, BigInteger gx1, BigInteger gx3, BigInteger gx4) + { + // ga = g^(x1+x3+x4) = g^x1 * g^x3 * g^x4 + return gx1.Multiply(gx3).Multiply(gx4).Mod(p); + } + + /// + /// Calculate x2 * s as done in round 2. + /// + public static BigInteger CalculateX2s(BigInteger q, BigInteger x2, BigInteger s) + { + return x2.Multiply(s).Mod(q); + } + + /// + /// Calculate A as done in round 2. + /// + public static BigInteger CalculateA(BigInteger p, BigInteger q, BigInteger gA, BigInteger x2s) + { + // A = ga^(x*s) + return gA.ModPow(x2s, p); + } + + /// + /// Calculate a zero knowledge proof of x using Schnorr's signature. + /// The returned array has two elements {g^v, r = v-x*h} for x. + /// + public static BigInteger[] CalculateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, + BigInteger gx, BigInteger x, string participantId, IDigest digest, SecureRandom random) + { + /* Generate a random v, and compute g^v */ + BigInteger vMin = Zero; + BigInteger vMax = q.Subtract(One); + BigInteger v = BigIntegers.CreateRandomInRange(vMin, vMax, random); + + BigInteger gv = g.ModPow(v, p); + BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); // h + + return new BigInteger[] + { + gv, + v.Subtract(x.Multiply(h)).Mod(q) // r = v-x*h + }; + } + + private static BigInteger CalculateHashForZeroKnowledgeProof(BigInteger g, BigInteger gr, BigInteger gx, + string participantId, IDigest digest) + { + digest.Reset(); + + UpdateDigestIncludingSize(digest, g); + + UpdateDigestIncludingSize(digest, gr); + + UpdateDigestIncludingSize(digest, gx); + + UpdateDigestIncludingSize(digest, participantId); + + byte[] output = DigestUtilities.DoFinal(digest); + + return new BigInteger(output); + } + + /// + /// Validates that g^x4 is not 1. + /// throws CryptoException if g^x4 is 1 + /// + public static void ValidateGx4(BigInteger gx4) + { + if (gx4.Equals(One)) + throw new CryptoException("g^x validation failed. g^x should not be 1."); + } + + /// + /// Validates that ga is not 1. + /// + /// As described by Feng Hao... + /// Alice could simply check ga != 1 to ensure it is a generator. + /// In fact, as we will explain in Section 3, (x1 + x3 + x4 ) is random over Zq even in the face of active attacks. + /// Hence, the probability for ga = 1 is extremely small - on the order of 2^160 for 160-bit q. + /// + /// throws CryptoException if ga is 1 + /// + public static void ValidateGa(BigInteger ga) + { + if (ga.Equals(One)) + throw new CryptoException("ga is equal to 1. It should not be. The chances of this happening are on the order of 2^160 for a 160-bit q. Try again."); + } + + /// + /// Validates the zero knowledge proof (generated by + /// calculateZeroKnowledgeProof(BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, string, Digest, SecureRandom) + /// is correct. + /// + /// throws CryptoException if the zero knowledge proof is not correct + /// + public static void ValidateZeroKnowledgeProof(BigInteger p, BigInteger q, BigInteger g, + BigInteger gx, BigInteger[] zeroKnowledgeProof, string participantId, IDigest digest) + { + /* sig={g^v,r} */ + BigInteger gv = zeroKnowledgeProof[0]; + BigInteger r = zeroKnowledgeProof[1]; + + BigInteger h = CalculateHashForZeroKnowledgeProof(g, gv, gx, participantId, digest); + if (!(gx.CompareTo(Zero) == 1 && // g^x > 0 + gx.CompareTo(p) == -1 && // g^x < p + gx.ModPow(q, p).CompareTo(One) == 0 && // g^x^q mod q = 1 + /* + * Below, I took a straightforward way to compute g^r * g^x^h, + * which needs 2 exp. Using a simultaneous computation technique + * would only need 1 exp. + */ + g.ModPow(r, p).Multiply(gx.ModPow(h, p)).Mod(p).CompareTo(gv) == 0)) // g^v=g^r * g^x^h + { + throw new CryptoException("Zero-knowledge proof validation failed"); + } + } + + /// + /// Calculates the keying material, which can be done after round 2 has completed. + /// A session key must be derived from this key material using a secure key derivation function (KDF). + /// The KDF used to derive the key is handled externally (i.e. not by JPAKEParticipant). + /// + /// KeyingMaterial = (B/g^{x2*x4*s})^x2 + /// + public static BigInteger CalculateKeyingMaterial(BigInteger p, BigInteger q, + BigInteger gx4, BigInteger x2, BigInteger s, BigInteger B) + { + return gx4.ModPow(x2.Multiply(s).Negate().Mod(q), p).Multiply(B).ModPow(x2, p); + } + + /// + /// Validates that the given participant ids are not equal. + /// (For the J-PAKE exchange, each participant must use a unique id.) + /// + /// Throws CryptoException if the participantId strings are equal. + /// + public static void ValidateParticipantIdsDiffer(string participantId1, string participantId2) + { + if (participantId1.Equals(participantId2)) + { + throw new CryptoException( + "Both participants are using the same participantId (" + + participantId1 + + "). This is not allowed. " + + "Each participant must use a unique participantId."); + } + } + + /// + /// Validates that the given participant ids are equal. + /// This is used to ensure that the payloads received from + /// each round all come from the same participant. + /// + public static void ValidateParticipantIdsEqual(string expectedParticipantId, string actualParticipantId) + { + if (!expectedParticipantId.Equals(actualParticipantId)) + { + throw new CryptoException( + "Received payload from incorrect partner (" + + actualParticipantId + + "). Expected to receive payload from " + + expectedParticipantId + + "."); + } + } + + /// + /// Validates that the given object is not null. + /// throws NullReferenceException if the object is null. + /// + /// object in question + /// name of the object (to be used in exception message) + public static void ValidateNotNull(object obj, string description) + { + if (obj == null) + throw new ArgumentNullException(description); + } + + /// + /// Calculates the MacTag (to be used for key confirmation), as defined by + /// NIST SP 800-56A Revision 1, + /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + /// + /// MacTag = HMAC(MacKey, MacLen, MacData) + /// MacKey = H(K || "JPAKE_KC") + /// MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4 + /// + /// Note that both participants use "KC_1_U" because the sender of the round 3 message + /// is always the initiator for key confirmation. + /// + /// HMAC = {@link HMac} used with the given {@link Digest} + /// H = The given {@link Digest} + /// MacLen = length of MacTag + /// + public static BigInteger CalculateMacTag(string participantId, string partnerParticipantId, + BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest) + { + byte[] macKey = CalculateMacKey(keyingMaterial, digest); + + HMac mac = new HMac(digest); + mac.Init(new KeyParameter(macKey)); + Arrays.Fill(macKey, (byte)0); + + /* + * MacData = "KC_1_U" || participantId_Alice || participantId_Bob || gx1 || gx2 || gx3 || gx4. + */ + UpdateMac(mac, "KC_1_U"); + UpdateMac(mac, participantId); + UpdateMac(mac, partnerParticipantId); + UpdateMac(mac, gx1); + UpdateMac(mac, gx2); + UpdateMac(mac, gx3); + UpdateMac(mac, gx4); + + byte[] macOutput = MacUtilities.DoFinal(mac); + + return new BigInteger(macOutput); + } + + /// + /// Calculates the MacKey (i.e. the key to use when calculating the MagTag for key confirmation). + /// + /// MacKey = H(K || "JPAKE_KC") + /// + private static byte[] CalculateMacKey(BigInteger keyingMaterial, IDigest digest) + { + digest.Reset(); + + UpdateDigest(digest, keyingMaterial); + /* + * This constant is used to ensure that the macKey is NOT the same as the derived key. + */ + UpdateDigest(digest, "JPAKE_KC"); + + return DigestUtilities.DoFinal(digest); + } + + /// + /// Validates the MacTag received from the partner participant. + /// + /// throws CryptoException if the participantId strings are equal. + /// + /// the MacTag received from the partner + public static void ValidateMacTag(string participantId, string partnerParticipantId, + BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, + BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag) + { + /* + * Calculate the expected MacTag using the parameters as the partner + * would have used when the partner called calculateMacTag. + * + * i.e. basically all the parameters are reversed. + * participantId <-> partnerParticipantId + * x1 <-> x3 + * x2 <-> x4 + */ + BigInteger expectedMacTag = CalculateMacTag(partnerParticipantId, participantId, gx3, gx4, gx1, gx2, keyingMaterial, digest); + + if (!expectedMacTag.Equals(partnerMacTag)) + { + throw new CryptoException( + "Partner MacTag validation failed. " + + "Therefore, the password, MAC, or digest algorithm of each participant does not match."); + } + } + + private static void UpdateDigest(IDigest digest, BigInteger bigInteger) + { + UpdateDigest(digest, BigIntegers.AsUnsignedByteArray(bigInteger)); + } + + private static void UpdateDigest(IDigest digest, string str) + { + UpdateDigest(digest, Encoding.UTF8.GetBytes(str)); + } + + private static void UpdateDigest(IDigest digest, byte[] bytes) + { + digest.BlockUpdate(bytes, 0, bytes.Length); + Arrays.Fill(bytes, (byte)0); + } + + private static void UpdateDigestIncludingSize(IDigest digest, BigInteger bigInteger) + { + UpdateDigestIncludingSize(digest, BigIntegers.AsUnsignedByteArray(bigInteger)); + } + + private static void UpdateDigestIncludingSize(IDigest digest, string str) + { + UpdateDigestIncludingSize(digest, Encoding.UTF8.GetBytes(str)); + } + + private static void UpdateDigestIncludingSize(IDigest digest, byte[] bytes) + { + digest.BlockUpdate(IntToByteArray(bytes.Length), 0, 4); + digest.BlockUpdate(bytes, 0, bytes.Length); + Arrays.Fill(bytes, (byte)0); + } + + private static void UpdateMac(IMac mac, BigInteger bigInteger) + { + UpdateMac(mac, BigIntegers.AsUnsignedByteArray(bigInteger)); + } + + private static void UpdateMac(IMac mac, string str) + { + UpdateMac(mac, Encoding.UTF8.GetBytes(str)); + } + + private static void UpdateMac(IMac mac, byte[] bytes) + { + mac.BlockUpdate(bytes, 0, bytes.Length); + Arrays.Fill(bytes, (byte)0); + } + + private static byte[] IntToByteArray(int value) + { + return Pack.UInt32_To_BE((uint)value); + } + } +} diff --git a/crypto/test/src/crypto/agreement/test/AllTests.cs b/crypto/test/src/crypto/agreement/test/AllTests.cs new file mode 100644 index 000000000..ea8f438e5 --- /dev/null +++ b/crypto/test/src/crypto/agreement/test/AllTests.cs @@ -0,0 +1,31 @@ +using System; + +using NUnit.Core; +using NUnit.Framework; + +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Agreement.Tests +{ + [TestFixture] + public class AllTests + { + public static void Main(string[] args) + { + Suite.Run(new NullListener(), NUnit.Core.TestFilter.Empty); + } + + [Suite] + public static TestSuite Suite + { + get + { + TestSuite suite = new TestSuite("JPAKE Engine Tests"); + suite.Add(new JPakeParticipantTest()); + suite.Add(new JPakePrimeOrderGroupTest()); + suite.Add(new JPakeUtilitiesTest()); + return suite; + } + } + } +} diff --git a/crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs b/crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs new file mode 100644 index 000000000..c84264aa5 --- /dev/null +++ b/crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs @@ -0,0 +1,566 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.JPake; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Agreement.Tests +{ + [TestFixture] + public class JPakeParticipantTest + : SimpleTest + { + public override void PerformTest() + { + TestConstruction(); + TestSuccessfulExchange(); + TestIncorrectPassword(); + TestStateValidation(); + TestValidateRound1PayloadReceived(); + TestValidateRound2PayloadReceived(); + } + + public override string Name + { + get { return "JPakeParticipant"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPakeParticipantTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestConstruction() + { + JPakePrimeOrderGroup group = JPakePrimeOrderGroups.SUN_JCE_1024; + SecureRandom random = new SecureRandom(); + IDigest digest = new Sha256Digest(); + string participantId = "participantId"; + char[] password = "password".ToCharArray(); + + // should succeed + new JPakeParticipant(participantId, password, group, digest, random); + + // null participantId + try + { + new JPakeParticipant(null, password, group, digest, random); + + Fail("failed to throw exception on null participantId"); + } + catch (ArgumentNullException) + { + // expected + } + + // null password + try + { + new JPakeParticipant(participantId, null, group, digest, random); + + Fail("failed to throw exception on null password"); + } + catch (ArgumentNullException) + { + // expected + } + + // empty password + try + { + new JPakeParticipant(participantId, "".ToCharArray(), group, digest, random); + + Fail("failed to throw exception on empty password"); + } + catch (ArgumentException) + { + // expected + } + + // null group + try + { + new JPakeParticipant(participantId, password, null, digest, random); + + Fail("failed to throw exception on null group"); + } + catch (ArgumentNullException) + { + // expected + } + + // null digest + try + { + new JPakeParticipant(participantId, password, group, null, random); + + Fail("failed to throw exception on null digest"); + } + catch (ArgumentNullException) + { + // expected + } + + // null random + try + { + new JPakeParticipant(participantId, password, group, digest, null); + + Fail("failed to throw exception on null random"); + } + catch (ArgumentNullException) + { + // expected + } + } + + public void TestSuccessfulExchange() + { + JPakeParticipant alice = CreateAlice(); + JPakeParticipant bob = CreateBob(); + + ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); + + alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); + bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + + Assert.AreEqual(aliceKeyingMaterial, bobKeyingMaterial); + } + + public void TestIncorrectPassword() + { + JPakeParticipant alice = CreateAlice(); + JPakeParticipant bob = CreateBobWithWrongPassword(); + + ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); + + alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); + bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + + try + { + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + + Fail("failed to throw exception on incorrect password"); + } + catch (CryptoException) + { + // expected + } + + try + { + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + + Fail("failed to throw exception on incorrect password"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestStateValidation() + { + JPakeParticipant alice = CreateAlice(); + JPakeParticipant bob = CreateBob(); + + // We're testing alice here. Bob is just used for help. + + // START ROUND 1 CHECKS + + Assert.AreEqual(JPakeParticipant.STATE_INITIALIZED, alice.State); + + // create round 2 before round 1 + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation before 1"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_CREATED, alice.State); + + // create round 1 twice + try + { + alice.CreateRound1PayloadToSend(); + + Fail("failed to throw on round 1 creation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + // create round 2 before validation round 1 + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation before round 1 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + // validate round 2 before validation round 1 + try + { + alice.ValidateRound2PayloadReceived(null); + + Fail("failed to throw on round 2 validation before round 1 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); + alice.ValidateRound1PayloadReceived(bobRound1Payload); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_VALIDATED, alice.State); + + // validate round 1 payload twice + try + { + alice.ValidateRound1PayloadReceived(bobRound1Payload); + + Fail("failed to throw on round 1 validation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound1PayloadReceived(aliceRound1Payload); + + // START ROUND 2 CHECKS + + JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_CREATED, alice.State); + + // create round 2 payload twice + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + // create key before validation round 2 + try + { + alice.CalculateKeyingMaterial(); + + Fail("failed to throw on calculating keying material before round 2 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + // validate round 3 before validating round 2 + try + { + alice.ValidateRound3PayloadReceived(null, null); + + Fail("failed to throw on validating round 3 before 2"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); + alice.ValidateRound2PayloadReceived(bobRound2Payload); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_VALIDATED, alice.State); + + // validate round 2 payload twice + try + { + alice.ValidateRound2PayloadReceived(bobRound2Payload); + + Fail("failed to throw on validating round 2 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound2PayloadReceived(aliceRound2Payload); + + // create round 3 before calculating key + try + { + alice.CreateRound3PayloadToSend(BigInteger.One); + + Fail("failed to throw on creating round 3 before calculating key aterial"); + } + catch (InvalidOperationException) + { + // expected + } + + // START KEY CALCULATION CHECKS + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + Assert.AreEqual(JPakeParticipant.STATE_KEY_CALCULATED, alice.State); + + // calculate key twice + try + { + alice.CalculateKeyingMaterial(); + + Fail("failed to throw on calculating key twice"); + } + catch (InvalidOperationException) + { + // expected + } + + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + // START ROUND 3 CHECKS + + JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_CREATED, alice.State); + + // create round 3 payload twice + try + { + alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + + Fail("failed to throw on creation round 3 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_VALIDATED, alice.State); + + // validate round 3 payload twice + try + { + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + + Fail("failed to throw on validation round 3 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + } + + public void TestValidateRound1PayloadReceived() + { + // We're testing alice here. Bob is just used for help. + + JPakeRound1Payload bobRound1Payload = CreateBob().CreateRound1PayloadToSend(); + + // should succeed + CreateAlice().ValidateRound1PayloadReceived(bobRound1Payload); + + // alice verifies alice's payload + try + { + JPakeParticipant alice = CreateAlice(); + alice.ValidateRound1PayloadReceived(alice.CreateRound1PayloadToSend()); + + Fail("failed to throw on participant validating own payload"); + } + catch (CryptoException) + { + // expected + } + + // g^x4 == 1 + try + { + CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + BigInteger.One, + bobRound1Payload.KnowledgeProofForX1, + bobRound1Payload.KnowledgeProofForX2)); + + Fail("failed to throw on g^x4 == 1"); + } + catch (CryptoException) + { + // expected + } + + // zero knowledge proof for x3 fails + try + { + JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); + CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + bobRound1Payload.Gx2, + bobRound1Payload2.KnowledgeProofForX1, + bobRound1Payload.KnowledgeProofForX2)); + + Fail("failed to throw on incorrect zero knowledge proof for x3"); + } + catch (CryptoException) + { + // expected + } + + // zero knowledge proof for x4 fails + try + { + JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); + CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + bobRound1Payload.Gx2, + bobRound1Payload.KnowledgeProofForX1, + bobRound1Payload2.KnowledgeProofForX2)); + + Fail("failed to throw on incorrect zero knowledge proof for x4"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateRound2PayloadReceived() + { + // We're testing alice here. Bob is just used for help. + + // should succeed + ExchangeAfterRound2Creation exchange1 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + exchange1.alice.ValidateRound2PayloadReceived(exchange1.bobRound2Payload); + + // alice verified alice's payload + ExchangeAfterRound2Creation exchange2 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + try + { + exchange2.alice.ValidateRound2PayloadReceived(exchange2.aliceRound2Payload); + + Fail("failed to throw on participant verifying own payload 2"); + } + catch (CryptoException) + { + // expected + } + + // wrong z + ExchangeAfterRound2Creation exchange3 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + ExchangeAfterRound2Creation exchange4 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + try + { + exchange3.alice.ValidateRound2PayloadReceived(exchange4.bobRound2Payload); + + Fail("failed to throw on wrong z"); + } + catch (CryptoException) + { + // expected + } + } + + private class ExchangeAfterRound2Creation + { + public JPakeParticipant alice; + public JPakeRound2Payload aliceRound2Payload; + public JPakeRound2Payload bobRound2Payload; + + public ExchangeAfterRound2Creation( + JPakeParticipant alice, + JPakeRound2Payload aliceRound2Payload, + JPakeRound2Payload bobRound2Payload) + { + this.alice = alice; + this.aliceRound2Payload = aliceRound2Payload; + this.bobRound2Payload = bobRound2Payload; + } + } + + private ExchangeAfterRound2Creation RunExchangeUntilRound2Creation(JPakeParticipant alice, JPakeParticipant bob) + { + JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); + JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); + + alice.ValidateRound1PayloadReceived(bobRound1Payload); + bob.ValidateRound1PayloadReceived(aliceRound1Payload); + + JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); + JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); + + return new ExchangeAfterRound2Creation( + alice, + aliceRound2Payload, + bobRound2Payload); + } + + private JPakeParticipant CreateAlice() + { + return CreateParticipant("alice", "password"); + } + + private JPakeParticipant CreateBob() + { + return CreateParticipant("bob", "password"); + } + + private JPakeParticipant CreateBobWithWrongPassword() + { + return CreateParticipant("bob", "wrong"); + } + + private JPakeParticipant CreateParticipant(string participantId, string password) + { + return new JPakeParticipant( + participantId, + password.ToCharArray(), + JPakePrimeOrderGroups.SUN_JCE_1024); + } + } +} diff --git a/crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs b/crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs new file mode 100644 index 000000000..0f089f93c --- /dev/null +++ b/crypto/test/src/crypto/agreement/test/JPakePrimeOrderGroupTest.cs @@ -0,0 +1,117 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.JPake; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Agreement.Tests +{ + [TestFixture] + public class JPakePrimeOrderGroupTest + : SimpleTest + { + public override void PerformTest() + { + TestConstruction(); + } + + public override string Name + { + get { return "JPakePrimeOrderGroup"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPakePrimeOrderGroupTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestConstruction() + { + // p-1 not evenly divisible by q + try + { + new JPakePrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(5), BigInteger.ValueOf(6)); + + Fail("failed to throw exception on p-1 not evenly divisible by q"); + } + catch (ArgumentException) + { + // expected + } + + // g < 2 + try + { + new JPakePrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(1)); + + Fail("failed to throw exception on g < 2"); + } + catch (ArgumentException) + { + // expected + } + + // g > p - 1 + try + { + new JPakePrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(11)); + + Fail("failed to throw exception on g > p - 1"); + } + catch (ArgumentException) + { + // expected + } + + //g^q mod p not equal 1 + try + { + new JPakePrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(6)); + + Fail("failed to throw exception on g^q mod p not equal 1"); + } + catch (ArgumentException) + { + // expected + } + + // p not prime + try + { + new JPakePrimeOrderGroup(BigInteger.ValueOf(15), BigInteger.ValueOf(2), BigInteger.ValueOf(4)); + + Fail("failed to throw exception on p not prime"); + } + catch (ArgumentException) + { + // expected + } + + // q not prime + try + { + new JPakePrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(6), BigInteger.ValueOf(3)); + + Fail("failed to throw exception on q not prime"); + } + catch (ArgumentException) + { + // expected + } + + // should succeed + new JPakePrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(3), BigInteger.ValueOf(4)); + } + } +} diff --git a/crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs b/crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs new file mode 100644 index 000000000..04a52cc06 --- /dev/null +++ b/crypto/test/src/crypto/agreement/test/JPakeUtilitiesTest.cs @@ -0,0 +1,306 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.JPake; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Agreement.Tests +{ + [TestFixture] + public class JPakeUtilitiesTest + : SimpleTest + { + private static readonly BigInteger Ten = BigInteger.ValueOf(10); + + public override void PerformTest() + { + TestValidateGx4(); + TestValidateGa(); + TestValidateParticipantIdsDiffer(); + TestValidateParticipantsIdsEqual(); + TestValidateMacTag(); + TestValidateNotNull(); + TestValidateZeroKnowledgeProof(); + } + + public override string Name + { + get { return "JPakeUtilities"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPakeUtilitiesTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestValidateGx4() + { + JPakeUtilities.ValidateGx4(Ten); + + try + { + JPakeUtilities.ValidateGx4(BigInteger.One); + + Fail("exception not thrown for g^x4 equal to 1"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateGa() + { + JPakeUtilities.ValidateGa(Ten); + + try + { + JPakeUtilities.ValidateGa(BigInteger.One); + + Fail("exception not thrown for g^a equal to 1"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateParticipantIdsDiffer() + { + JPakeUtilities.ValidateParticipantIdsDiffer("a", "b"); + JPakeUtilities.ValidateParticipantIdsDiffer("a", "A"); + + try + { + JPakeUtilities.ValidateParticipantIdsDiffer("a", "a"); + + Fail("validate participant ids differ not throwing exception for equal participant ids"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateParticipantsIdsEqual() + { + JPakeUtilities.ValidateParticipantIdsEqual("a", "a"); + + try + { + JPakeUtilities.ValidateParticipantIdsEqual("a", "b"); + + Fail("validate participant ids equal not throwing exception for different participant ids"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateMacTag() + { + JPakePrimeOrderGroup pg1 = JPakePrimeOrderGroups.SUN_JCE_1024; + + SecureRandom random = new SecureRandom(); + IDigest digest = new Sha256Digest(); + + BigInteger x1 = JPakeUtilities.GenerateX1(pg1.Q, random); + BigInteger x2 = JPakeUtilities.GenerateX2(pg1.Q, random); + BigInteger x3 = JPakeUtilities.GenerateX1(pg1.Q, random); + BigInteger x4 = JPakeUtilities.GenerateX2(pg1.Q, random); + + BigInteger gx1 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x1); + BigInteger gx2 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x2); + BigInteger gx3 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x3); + BigInteger gx4 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x4); + + BigInteger gB = JPakeUtilities.CalculateGA(pg1.P, gx3, gx1, gx2); + + BigInteger s = JPakeUtilities.CalculateS("password".ToCharArray()); + + BigInteger xs = JPakeUtilities.CalculateX2s(pg1.Q, x4, s); + + BigInteger B = JPakeUtilities.CalculateA(pg1.P, pg1.Q, gB, xs); + + BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(pg1.P, pg1.Q, gx4, x2, s, B); + + BigInteger macTag = JPakeUtilities.CalculateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest); + + // should succeed + JPakeUtilities.ValidateMacTag("partnerParticipantId", "participantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag); + + // validating own macTag (as opposed to the other party's mactag) + try + { + JPakeUtilities.ValidateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest, macTag); + + Fail("failed to throw exception on validating own macTag (calculated partner macTag)"); + } + catch (CryptoException) + { + // expected + } + + // participant ids switched + try + { + JPakeUtilities.ValidateMacTag("participantId", "partnerParticipantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag); + + Fail("failed to throw exception on validating own macTag (calculated partner macTag"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateNotNull() + { + JPakeUtilities.ValidateNotNull("a", "description"); + + try + { + JPakeUtilities.ValidateNotNull(null, "description"); + + Fail("failed to throw exception on null"); + } + catch (ArgumentNullException) + { + // expected + } + } + + public void TestValidateZeroKnowledgeProof() + { + JPakePrimeOrderGroup pg1 = JPakePrimeOrderGroups.SUN_JCE_1024; + + SecureRandom random = new SecureRandom(); + IDigest digest1 = new Sha256Digest(); + + BigInteger x1 = JPakeUtilities.GenerateX1(pg1.Q, random); + BigInteger gx1 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x1); + string participantId1 = "participant1"; + + BigInteger[] zkp1 = JPakeUtilities.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, x1, participantId1, digest1, random); + + // should succeed + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest1); + + // wrong group + JPakePrimeOrderGroup pg2 = JPakePrimeOrderGroups.NIST_3072; + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg2.P, pg2.Q, pg2.G, gx1, zkp1, participantId1, digest1); + + Fail("failed to throw exception on wrong prime order group"); + } + catch (CryptoException) + { + // expected + } + + // wrong digest + IDigest digest2 = new Sha1Digest(); + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest2); + + Fail("failed to throw exception on wrong digest"); + } + catch (CryptoException) + { + // expected + } + + // wrong participant + string participantId2 = "participant2"; + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId2, digest1); + + Fail("failed to throw exception on wrong participant"); + } + catch (CryptoException) + { + // expected + } + + // wrong gx + BigInteger x2 = JPakeUtilities.GenerateX2(pg1.Q, random); + BigInteger gx2 = JPakeUtilities.CalculateGx(pg1.P, pg1.G, x2); + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, zkp1, participantId1, digest1); + + Fail("failed to throw exception on wrong gx"); + } + catch (CryptoException) + { + // expected + } + + // wrong zkp + BigInteger[] zkp2 = JPakeUtilities.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, x2, participantId1, digest1, random); + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp2, participantId1, digest1); + + Fail("failed to throw exception on wrong zero knowledge proof"); + } + catch (CryptoException) + { + // expected + } + + // gx <= 0 + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, BigInteger.Zero, zkp1, participantId1, digest1); + + Fail("failed to throw exception on g^x <= 0"); + } + catch (CryptoException) + { + // expected + } + + // gx >= p + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.P, zkp1, participantId1, digest1); + + Fail("failed to throw exception on g^x >= p"); + } + catch (CryptoException) + { + // expected + } + + // gx mod q == 1 + try + { + JPakeUtilities.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.Q.Add(BigInteger.One), zkp1, participantId1, digest1); + + Fail("failed to throw exception on g^x mod q == 1"); + } + catch (CryptoException) + { + // expected + } + } + } +} diff --git a/crypto/test/src/crypto/test/JPAKEParticipantTest.cs b/crypto/test/src/crypto/test/JPAKEParticipantTest.cs deleted file mode 100644 index 7c01bf237..000000000 --- a/crypto/test/src/crypto/test/JPAKEParticipantTest.cs +++ /dev/null @@ -1,566 +0,0 @@ -using System; - -using NUnit.Framework; - -using Org.BouncyCastle.Crypto.Agreement.Jpake; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities.Test; - -namespace Org.BouncyCastle.Crypto.Tests -{ - [TestFixture] - public class JPAKEParticipantTest - : SimpleTest - { - public override void PerformTest() - { - TestConstruction(); - TestSuccessfulExchange(); - TestIncorrectPassword(); - TestStateValidation(); - TestValidateRound1PayloadReceived(); - TestValidateRound2PayloadReceived(); - } - - public override string Name - { - get { return "JPAKEParticipant"; } - } - - public static void Main( - string[] args) - { - RunTest(new JPAKEParticipantTest()); - } - - [Test] - public void TestFunction() - { - string resultText = Perform().ToString(); - - Assert.AreEqual(Name + ": Okay", resultText); - } - - public void TestConstruction() - { - JPAKEPrimeOrderGroup group = JPAKEPrimeOrderGroups.SUN_JCE_1024; - SecureRandom random = new SecureRandom(); - IDigest digest = new Sha256Digest(); - string participantId = "participantId"; - char[] password = "password".ToCharArray(); - - // should succeed - new JPAKEParticipant(participantId, password, group, digest, random); - - // null participantId - try - { - new JPAKEParticipant(null, password, group, digest, random); - - Fail("failed to throw exception on null participantId"); - } - catch (NullReferenceException) - { - // expected - } - - // null password - try - { - new JPAKEParticipant(participantId, null, group, digest, random); - - Fail("failed to throw exception on null password"); - } - catch (NullReferenceException) - { - // expected - } - - // empty password - try - { - new JPAKEParticipant(participantId, "".ToCharArray(), group, digest, random); - - Fail("failed to throw exception on empty password"); - } - catch (ArgumentException) - { - // expected - } - - // null group - try - { - new JPAKEParticipant(participantId, password, null, digest, random); - - Fail("failed to throw exception on null group"); - } - catch (NullReferenceException) - { - // expected - } - - // null digest - try - { - new JPAKEParticipant(participantId, password, group, null, random); - - Fail("failed to throw exception on null digest"); - } - catch (NullReferenceException) - { - // expected - } - - // null random - try - { - new JPAKEParticipant(participantId, password, group, digest, null); - - Fail("failed to throw exception on null random"); - } - catch (NullReferenceException) - { - // expected - } - } - - public void TestSuccessfulExchange() - { - JPAKEParticipant alice = CreateAlice(); - JPAKEParticipant bob = CreateBob(); - - ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); - - alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); - bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); - - BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); - BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); - - JPAKERound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - JPAKERound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); - - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); - - Assert.AreEqual(aliceKeyingMaterial, bobKeyingMaterial); - } - - public void TestIncorrectPassword() - { - JPAKEParticipant alice = CreateAlice(); - JPAKEParticipant bob = CreateBobWithWrongPassword(); - - ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); - - alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); - bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); - - BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); - BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); - - JPAKERound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - JPAKERound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); - - try - { - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - - Fail("failed to throw exception on incorrect password"); - } - catch (CryptoException) - { - // expected - } - - try - { - bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); - - Fail("failed to throw exception on incorrect password"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestStateValidation() - { - JPAKEParticipant alice = CreateAlice(); - JPAKEParticipant bob = CreateBob(); - - // We're testing alice here. Bob is just used for help. - - // START ROUND 1 CHECKS - - Assert.AreEqual(JPAKEParticipant.STATE_INITIALIZED, alice.State); - - // create round 2 before round 1 - try - { - alice.CreateRound2PayloadToSend(); - - Fail("failed to throw on round 2 creation before 1"); - } - catch (InvalidOperationException) - { - // expected - } - - JPAKERound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); - Assert.AreEqual(JPAKEParticipant.STATE_ROUND_1_CREATED, alice.State); - - // create round 1 twice - try - { - alice.CreateRound1PayloadToSend(); - - Fail("failed to throw on round 1 creation twice"); - } - catch (InvalidOperationException) - { - // expected - } - - // create round 2 before validation round 1 - try - { - alice.CreateRound2PayloadToSend(); - - Fail("failed to throw on round 2 creation before round 1 validation"); - } - catch (InvalidOperationException) - { - // expected - } - - // validate round 2 before validation round 1 - try - { - alice.ValidateRound2PayloadReceived(null); - - Fail("failed to throw on round 2 validation before round 1 validation"); - } - catch (InvalidOperationException) - { - // expected - } - - JPAKERound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); - alice.ValidateRound1PayloadReceived(bobRound1Payload); - Assert.AreEqual(JPAKEParticipant.STATE_ROUND_1_VALIDATED, alice.State); - - // validate round 1 payload twice - try - { - alice.ValidateRound1PayloadReceived(bobRound1Payload); - - Fail("failed to throw on round 1 validation twice"); - } - catch (InvalidOperationException) - { - // expected - } - - bob.ValidateRound1PayloadReceived(aliceRound1Payload); - - // START ROUND 2 CHECKS - - JPAKERound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); - Assert.AreEqual(JPAKEParticipant.STATE_ROUND_2_CREATED, alice.State); - - // create round 2 payload twice - try - { - alice.CreateRound2PayloadToSend(); - - Fail("failed to throw on round 2 creation twice"); - } - catch (InvalidOperationException) - { - // expected - } - - // create key before validation round 2 - try - { - alice.CalculateKeyingMaterial(); - - Fail("failed to throw on calculating keying material before round 2 validation"); - } - catch (InvalidOperationException) - { - // expected - } - - // validate round 3 before validating round 2 - try - { - alice.ValidateRound3PayloadReceived(null, null); - - Fail("failed to throw on validating round 3 before 2"); - } - catch (InvalidOperationException) - { - // expected - } - - JPAKERound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); - alice.ValidateRound2PayloadReceived(bobRound2Payload); - Assert.AreEqual(JPAKEParticipant.STATE_ROUND_2_VALIDATED, alice.State); - - // validate round 2 payload twice - try - { - alice.ValidateRound2PayloadReceived(bobRound2Payload); - - Fail("failed to throw on validating round 2 twice"); - } - catch (InvalidOperationException) - { - // expected - } - - bob.ValidateRound2PayloadReceived(aliceRound2Payload); - - // create round 3 before calculating key - try - { - alice.CreateRound3PayloadToSend(BigInteger.One); - - Fail("failed to throw on creating round 3 before calculating key aterial"); - } - catch (InvalidOperationException) - { - // expected - } - - // START KEY CALCULATION CHECKS - - BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); - Assert.AreEqual(JPAKEParticipant.STATE_KEY_CALCULATED, alice.State); - - // calculate key twice - try - { - alice.CalculateKeyingMaterial(); - - Fail("failed to throw on calculating key twice"); - } - catch (InvalidOperationException) - { - // expected - } - - BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); - - // START ROUND 3 CHECKS - - JPAKERound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - Assert.AreEqual(JPAKEParticipant.STATE_ROUND_3_CREATED, alice.State); - - // create round 3 payload twice - try - { - alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - - Fail("failed to throw on creation round 3 twice"); - } - catch (InvalidOperationException) - { - // expected - } - - JPAKERound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - Assert.AreEqual(JPAKEParticipant.STATE_ROUND_3_VALIDATED, alice.State); - - // validate round 3 payload twice - try - { - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - - Fail("failed to throw on validation round 3 twice"); - } - catch (InvalidOperationException) - { - // expected - } - - bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); - } - - public void TestValidateRound1PayloadReceived() - { - // We're testing alice here. Bob is just used for help. - - JPAKERound1Payload bobRound1Payload = CreateBob().CreateRound1PayloadToSend(); - - // should succeed - CreateAlice().ValidateRound1PayloadReceived(bobRound1Payload); - - // alice verifies alice's payload - try - { - JPAKEParticipant alice = CreateAlice(); - alice.ValidateRound1PayloadReceived(alice.CreateRound1PayloadToSend()); - - Fail("failed to throw on participant validating own payload"); - } - catch (CryptoException) - { - // expected - } - - // g^x4 == 1 - try - { - CreateAlice().ValidateRound1PayloadReceived(new JPAKERound1Payload( - bobRound1Payload.ParticipantId, - bobRound1Payload.Gx1, - BigInteger.One, - bobRound1Payload.KnowledgeProofForX1, - bobRound1Payload.KnowledgeProofForX2)); - - Fail("failed to throw on g^x4 == 1"); - } - catch (CryptoException) - { - // expected - } - - // zero knowledge proof for x3 fails - try - { - JPAKERound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); - CreateAlice().ValidateRound1PayloadReceived(new JPAKERound1Payload( - bobRound1Payload.ParticipantId, - bobRound1Payload.Gx1, - bobRound1Payload.Gx2, - bobRound1Payload2.KnowledgeProofForX1, - bobRound1Payload.KnowledgeProofForX2)); - - Fail("failed to throw on incorrect zero knowledge proof for x3"); - } - catch (CryptoException) - { - // expected - } - - // zero knowledge proof for x4 fails - try - { - JPAKERound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); - CreateAlice().ValidateRound1PayloadReceived(new JPAKERound1Payload( - bobRound1Payload.ParticipantId, - bobRound1Payload.Gx1, - bobRound1Payload.Gx2, - bobRound1Payload.KnowledgeProofForX1, - bobRound1Payload2.KnowledgeProofForX2)); - - Fail("failed to throw on incorrect zero knowledge proof for x4"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateRound2PayloadReceived() - { - // We're testing alice here. Bob is just used for help. - - // should succeed - ExchangeAfterRound2Creation exchange1 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - exchange1.alice.ValidateRound2PayloadReceived(exchange1.bobRound2Payload); - - // alice verified alice's payload - ExchangeAfterRound2Creation exchange2 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - try - { - exchange2.alice.ValidateRound2PayloadReceived(exchange2.aliceRound2Payload); - - Fail("failed to throw on participant verifying own payload 2"); - } - catch (CryptoException) - { - // expected - } - - // wrong z - ExchangeAfterRound2Creation exchange3 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - ExchangeAfterRound2Creation exchange4 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - try - { - exchange3.alice.ValidateRound2PayloadReceived(exchange4.bobRound2Payload); - - Fail("failed to throw on wrong z"); - } - catch (CryptoException) - { - // expected - } - } - - private class ExchangeAfterRound2Creation - { - public JPAKEParticipant alice; - public JPAKERound2Payload aliceRound2Payload; - public JPAKERound2Payload bobRound2Payload; - - public ExchangeAfterRound2Creation( - JPAKEParticipant alice, - JPAKERound2Payload aliceRound2Payload, - JPAKERound2Payload bobRound2Payload) - { - this.alice = alice; - this.aliceRound2Payload = aliceRound2Payload; - this.bobRound2Payload = bobRound2Payload; - } - } - - private ExchangeAfterRound2Creation RunExchangeUntilRound2Creation(JPAKEParticipant alice, JPAKEParticipant bob) - { - JPAKERound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); - JPAKERound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); - - alice.ValidateRound1PayloadReceived(bobRound1Payload); - bob.ValidateRound1PayloadReceived(aliceRound1Payload); - - JPAKERound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); - JPAKERound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); - - return new ExchangeAfterRound2Creation( - alice, - aliceRound2Payload, - bobRound2Payload); - } - - private JPAKEParticipant CreateAlice() - { - return CreateParticipant("alice", "password"); - } - - private JPAKEParticipant CreateBob() - { - return CreateParticipant("bob", "password"); - } - - private JPAKEParticipant CreateBobWithWrongPassword() - { - return CreateParticipant("bob", "wrong"); - } - - private JPAKEParticipant CreateParticipant(string participantId, string password) - { - return new JPAKEParticipant( - participantId, - password.ToCharArray(), - JPAKEPrimeOrderGroups.SUN_JCE_1024); - } - } -} diff --git a/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs b/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs deleted file mode 100644 index d9749cb53..000000000 --- a/crypto/test/src/crypto/test/JPAKEPrimeOrderGroupTest.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; - -using NUnit.Framework; - -using Org.BouncyCastle.Crypto.Agreement.Jpake; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Utilities.Test; - -namespace Org.BouncyCastle.Crypto.Tests -{ - [TestFixture] - public class JPAKEPrimeOrderGroupTest - : SimpleTest - { - public override void PerformTest() - { - TestConstruction(); - } - - public override string Name - { - get { return "JPAKEPrimeOrderGroup"; } - } - - public static void Main( - string[] args) - { - RunTest(new JPAKEPrimeOrderGroupTest()); - } - - [Test] - public void TestFunction() - { - string resultText = Perform().ToString(); - - Assert.AreEqual(Name + ": Okay", resultText); - } - - public void TestConstruction() - { - // p-1 not evenly divisible by q - try - { - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(5), BigInteger.ValueOf(6)); - - Fail("failed to throw exception on p-1 not evenly divisible by q"); - } - catch (ArgumentException) - { - // expected - } - - // g < 2 - try - { - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(1)); - - Fail("failed to throw exception on g < 2"); - } - catch (ArgumentException) - { - // expected - } - - // g > p - 1 - try - { - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(11)); - - Fail("failed to throw exception on g > p - 1"); - } - catch (ArgumentException) - { - // expected - } - - //g^q mod p not equal 1 - try - { - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(11), BigInteger.ValueOf(5), BigInteger.ValueOf(6)); - - Fail("failed to throw exception on g^q mod p not equal 1"); - } - catch (ArgumentException) - { - // expected - } - - // p not prime - try - { - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(15), BigInteger.ValueOf(2), BigInteger.ValueOf(4)); - - Fail("failed to throw exception on p not prime"); - } - catch (ArgumentException) - { - // expected - } - - // q not prime - try - { - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(6), BigInteger.ValueOf(3)); - - Fail("failed to throw exception on q not prime"); - } - catch (ArgumentException) - { - // expected - } - - // should succeed - new JPAKEPrimeOrderGroup(BigInteger.ValueOf(7), BigInteger.ValueOf(3), BigInteger.ValueOf(4)); - } - } -} diff --git a/crypto/test/src/crypto/test/JPAKEUtilTest.cs b/crypto/test/src/crypto/test/JPAKEUtilTest.cs deleted file mode 100644 index 7ac54aba0..000000000 --- a/crypto/test/src/crypto/test/JPAKEUtilTest.cs +++ /dev/null @@ -1,306 +0,0 @@ -using System; - -using NUnit.Framework; - -using Org.BouncyCastle.Crypto.Agreement.Jpake; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities.Test; - -namespace Org.BouncyCastle.Crypto.Tests -{ - [TestFixture] - public class JPAKEUtilTest - : SimpleTest - { - private static readonly BigInteger Ten = BigInteger.ValueOf(10); - - public override void PerformTest() - { - TestValidateGx4(); - TestValidateGa(); - TestValidateParticipantIdsDiffer(); - TestValidateParticipantsIdsEqual(); - TestValidateMacTag(); - TestValidateNotNull(); - TestValidateZeroKnowledgeProof(); - } - - public override string Name - { - get { return "JPAKEUtil"; } - } - - public static void Main( - string[] args) - { - RunTest(new JPAKEUtilTest()); - } - - [Test] - public void TestFunction() - { - string resultText = Perform().ToString(); - - Assert.AreEqual(Name + ": Okay", resultText); - } - - public void TestValidateGx4() - { - JPAKEUtil.ValidateGx4(Ten); - - try - { - JPAKEUtil.ValidateGx4(BigInteger.One); - - Fail("exception not thrown for g^x4 equal to 1"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateGa() - { - JPAKEUtil.ValidateGa(Ten); - - try - { - JPAKEUtil.ValidateGa(BigInteger.One); - - Fail("exception not thrown for g^a equal to 1"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateParticipantIdsDiffer() - { - JPAKEUtil.ValidateParticipantIdsDiffer("a", "b"); - JPAKEUtil.ValidateParticipantIdsDiffer("a", "A"); - - try - { - JPAKEUtil.ValidateParticipantIdsDiffer("a", "a"); - - Fail("validate participant ids differ not throwing exception for equal participant ids"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateParticipantsIdsEqual() - { - JPAKEUtil.ValidateParticipantIdsEqual("a", "a"); - - try - { - JPAKEUtil.ValidateParticipantIdsEqual("a", "b"); - - Fail("validate participant ids equal not throwing exception for different participant ids"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateMacTag() - { - JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024; - - SecureRandom random = new SecureRandom(); - IDigest digest = new Sha256Digest(); - - BigInteger x1 = JPAKEUtil.GenerateX1(pg1.Q, random); - BigInteger x2 = JPAKEUtil.GenerateX2(pg1.Q, random); - BigInteger x3 = JPAKEUtil.GenerateX1(pg1.Q, random); - BigInteger x4 = JPAKEUtil.GenerateX2(pg1.Q, random); - - BigInteger gx1 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x1); - BigInteger gx2 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x2); - BigInteger gx3 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x3); - BigInteger gx4 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x4); - - BigInteger gB = JPAKEUtil.CalculateGA(pg1.P, gx3, gx1, gx2); - - BigInteger s = JPAKEUtil.CalculateS("password".ToCharArray()); - - BigInteger xs = JPAKEUtil.CalculateX2s(pg1.Q, x4, s); - - BigInteger B = JPAKEUtil.CalculateA(pg1.P, pg1.Q, gB, xs); - - BigInteger keyingMaterial = JPAKEUtil.CalculateKeyingMaterial(pg1.P, pg1.Q, gx4, x2, s, B); - - BigInteger macTag = JPAKEUtil.CalculateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest); - - // should succeed - JPAKEUtil.ValidateMacTag("partnerParticipantId", "participantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag); - - // validating own macTag (as opposed to the other party's mactag) - try - { - JPAKEUtil.ValidateMacTag("participantId", "partnerParticipantId", gx1, gx2, gx3, gx4, keyingMaterial, digest, macTag); - - Fail("failed to throw exception on validating own macTag (calculated partner macTag)"); - } - catch (CryptoException) - { - // expected - } - - // participant ids switched - try - { - JPAKEUtil.ValidateMacTag("participantId", "partnerParticipantId", gx3, gx4, gx1, gx2, keyingMaterial, digest, macTag); - - Fail("failed to throw exception on validating own macTag (calculated partner macTag"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateNotNull() - { - JPAKEUtil.ValidateNotNull("a", "description"); - - try - { - JPAKEUtil.ValidateNotNull(null, "description"); - - Fail("failed to throw exception on null"); - } - catch (NullReferenceException) - { - // expected - } - } - - public void TestValidateZeroKnowledgeProof() - { - JPAKEPrimeOrderGroup pg1 = JPAKEPrimeOrderGroups.SUN_JCE_1024; - - SecureRandom random = new SecureRandom(); - IDigest digest1 = new Sha256Digest(); - - BigInteger x1 = JPAKEUtil.GenerateX1(pg1.Q, random); - BigInteger gx1 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x1); - string participantId1 = "participant1"; - - BigInteger[] zkp1 = JPAKEUtil.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, x1, participantId1, digest1, random); - - // should succeed - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest1); - - // wrong group - JPAKEPrimeOrderGroup pg2 = JPAKEPrimeOrderGroups.NIST_3072; - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg2.P, pg2.Q, pg2.G, gx1, zkp1, participantId1, digest1); - - Fail("failed to throw exception on wrong prime order group"); - } - catch (CryptoException) - { - // expected - } - - // wrong digest - IDigest digest2 = new Sha1Digest(); - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId1, digest2); - - Fail("failed to throw exception on wrong digest"); - } - catch (CryptoException) - { - // expected - } - - // wrong participant - string participantId2 = "participant2"; - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp1, participantId2, digest1); - - Fail("failed to throw exception on wrong participant"); - } - catch (CryptoException) - { - // expected - } - - // wrong gx - BigInteger x2 = JPAKEUtil.GenerateX2(pg1.Q, random); - BigInteger gx2 = JPAKEUtil.CalculateGx(pg1.P, pg1.G, x2); - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, zkp1, participantId1, digest1); - - Fail("failed to throw exception on wrong gx"); - } - catch (CryptoException) - { - // expected - } - - // wrong zkp - BigInteger[] zkp2 = JPAKEUtil.CalculateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx2, x2, participantId1, digest1, random); - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, gx1, zkp2, participantId1, digest1); - - Fail("failed to throw exception on wrong zero knowledge proof"); - } - catch (CryptoException) - { - // expected - } - - // gx <= 0 - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, BigInteger.Zero, zkp1, participantId1, digest1); - - Fail("failed to throw exception on g^x <= 0"); - } - catch (CryptoException) - { - // expected - } - - // gx >= p - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.P, zkp1, participantId1, digest1); - - Fail("failed to throw exception on g^x >= p"); - } - catch (CryptoException) - { - // expected - } - - // gx mod q == 1 - try - { - JPAKEUtil.ValidateZeroKnowledgeProof(pg1.P, pg1.Q, pg1.G, pg1.Q.Add(BigInteger.One), zkp1, participantId1, digest1); - - Fail("failed to throw exception on g^x mod q == 1"); - } - catch (CryptoException) - { - // expected - } - } - } -} -- cgit 1.5.1 From 4973028f24e97e1b13fdd8dc73574a7d416e8779 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 17 Oct 2015 14:40:11 +0700 Subject: Can't use var keyword in .NET 1.1 --- crypto/src/openpgp/PgpKeyRingGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crypto') diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs index 92ea394a2..efeea9d5b 100644 --- a/crypto/src/openpgp/PgpKeyRingGenerator.cs +++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs @@ -202,7 +202,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { try { - var sGen = new PgpSignatureGenerator(masterKey.PublicKey.Algorithm, hashAlgorithm); + PgpSignatureGenerator sGen = new PgpSignatureGenerator(masterKey.PublicKey.Algorithm, hashAlgorithm); // // Generate the certification -- cgit 1.5.1 From 02a9861925a3077835fa80f7f3bd055b62940801 Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 17 Oct 2015 15:07:04 +0700 Subject: Fix case of JPAKE filenames --- .../src/crypto/agreement/jpake/JPAKEParticipant.cs | 455 ----------------- .../crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs | 103 ---- .../agreement/jpake/JPAKEPrimeOrderGroups.cs | 108 ---- .../crypto/agreement/jpake/JPAKERound1Payload.cs | 101 ---- .../crypto/agreement/jpake/JPAKERound2Payload.cs | 72 --- .../crypto/agreement/jpake/JPAKERound3Payload.cs | 51 -- .../src/crypto/agreement/jpake/JPakeParticipant.cs | 455 +++++++++++++++++ .../crypto/agreement/jpake/JPakePrimeOrderGroup.cs | 103 ++++ .../agreement/jpake/JPakePrimeOrderGroups.cs | 108 ++++ .../crypto/agreement/jpake/JPakeRound1Payload.cs | 101 ++++ .../crypto/agreement/jpake/JPakeRound2Payload.cs | 72 +++ .../crypto/agreement/jpake/JPakeRound3Payload.cs | 51 ++ .../crypto/agreement/test/JPAKEParticipantTest.cs | 566 --------------------- .../crypto/agreement/test/JPakeParticipantTest.cs | 566 +++++++++++++++++++++ 14 files changed, 1456 insertions(+), 1456 deletions(-) delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs delete mode 100755 crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPakeParticipant.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs create mode 100755 crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs delete mode 100644 crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs create mode 100644 crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs (limited to 'crypto') diff --git a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs b/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs deleted file mode 100755 index f8ca2cd41..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKEParticipant.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System; - -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; - -namespace Org.BouncyCastle.Crypto.Agreement.JPake -{ - /// - /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. - /// - /// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper - /// - /// "Password Authenticated Key Exchange by Juggling, 2008." - /// - /// The J-PAKE protocol is symmetric. - /// There is no notion of a client or server, but rather just two participants. - /// An instance of JPakeParticipant represents one participant, and - /// is the primary interface for executing the exchange. - /// - /// To execute an exchange, construct a JPakeParticipant on each end, - /// and call the following 7 methods - /// (once and only once, in the given order, for each participant, sending messages between them as described): - /// - /// CreateRound1PayloadToSend() - and send the payload to the other participant - /// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant - /// CreateRound2PayloadToSend() - and send the payload to the other participant - /// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant - /// CalculateKeyingMaterial() - /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant - /// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant - /// - /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial(). - /// The caller is responsible for deriving the session key using a secure key derivation function (KDF). - /// - /// Round 3 is an optional key confirmation process. - /// If you do not execute round 3, then there is no assurance that both participants are using the same key. - /// (i.e. if the participants used different passwords, then their session keys will differ.) - /// - /// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. - /// - /// The symmetric design can easily support the asymmetric cases when one party initiates the communication. - /// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. - /// Also, in some cases, the key confirmation payload can be sent together with the round2 payload. - /// These are the trivial techniques to optimize the communication. - /// - /// The key confirmation process is implemented as specified in - /// NIST SP 800-56A Revision 1, - /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. - /// - /// This class is stateful and NOT threadsafe. - /// Each instance should only be used for ONE complete J-PAKE exchange - /// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange). - /// - public class JPakeParticipant - { - // Possible internal states. Used for state checking. - public static readonly int STATE_INITIALIZED = 0; - public static readonly int STATE_ROUND_1_CREATED = 10; - public static readonly int STATE_ROUND_1_VALIDATED = 20; - public static readonly int STATE_ROUND_2_CREATED = 30; - public static readonly int STATE_ROUND_2_VALIDATED = 40; - public static readonly int STATE_KEY_CALCULATED = 50; - public static readonly int STATE_ROUND_3_CREATED = 60; - public static readonly int STATE_ROUND_3_VALIDATED = 70; - - // Unique identifier of this participant. - // The two participants in the exchange must NOT share the same id. - private string participantId; - - // Shared secret. This only contains the secret between construction - // and the call to CalculateKeyingMaterial(). - // - // i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's, - // and the field is set to null. - private char[] password; - - // Digest to use during calculations. - private IDigest digest; - - // Source of secure random data. - private readonly SecureRandom random; - - private readonly BigInteger p; - private readonly BigInteger q; - private readonly BigInteger g; - - // The participantId of the other participant in this exchange. - private string partnerParticipantId; - - // Alice's x1 or Bob's x3. - private BigInteger x1; - // Alice's x2 or Bob's x4. - private BigInteger x2; - // Alice's g^x1 or Bob's g^x3. - private BigInteger gx1; - // Alice's g^x2 or Bob's g^x4. - private BigInteger gx2; - // Alice's g^x3 or Bob's g^x1. - private BigInteger gx3; - // Alice's g^x4 or Bob's g^x2. - private BigInteger gx4; - // Alice's B or Bob's A. - private BigInteger b; - - // The current state. - // See the STATE_* constants for possible values. - private int state; - - /// - /// Convenience constructor for a new JPakeParticipant that uses - /// the JPakePrimeOrderGroups#NIST_3072 prime order group, - /// a SHA-256 digest, and a default SecureRandom implementation. - /// - /// After construction, the State state will be STATE_INITIALIZED. - /// - /// Throws NullReferenceException if any argument is null. Throws - /// ArgumentException if password is empty. - /// - /// Unique identifier of this participant. - /// The two participants in the exchange must NOT share the same id. - /// Shared secret. - /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). - /// Caller should clear the input password as soon as possible. - public JPakeParticipant(string participantId, char[] password) - : this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { } - - /// - /// Convenience constructor for a new JPakeParticipant that uses - /// a SHA-256 digest, and a default SecureRandom implementation. - /// - /// After construction, the State state will be STATE_INITIALIZED. - /// - /// Throws NullReferenceException if any argument is null. Throws - /// ArgumentException if password is empty. - /// - /// Unique identifier of this participant. - /// The two participants in the exchange must NOT share the same id. - /// Shared secret. - /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). - /// Caller should clear the input password as soon as possible. - /// Prime order group. See JPakePrimeOrderGroups for standard groups. - public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group) - : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } - - - /// - /// Constructor for a new JPakeParticipant. - /// - /// After construction, the State state will be STATE_INITIALIZED. - /// - /// Throws NullReferenceException if any argument is null. Throws - /// ArgumentException if password is empty. - /// - /// Unique identifier of this participant. - /// The two participants in the exchange must NOT share the same id. - /// Shared secret. - /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). - /// Caller should clear the input password as soon as possible. - /// Prime order group. See JPakePrimeOrderGroups for standard groups. - /// Digest to use during zero knowledge proofs and key confirmation - /// (SHA-256 or stronger preferred). - /// Source of secure random data for x1 and x2, and for the zero knowledge proofs. - public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random) - { - JPakeUtilities.ValidateNotNull(participantId, "participantId"); - JPakeUtilities.ValidateNotNull(password, "password"); - JPakeUtilities.ValidateNotNull(group, "p"); - JPakeUtilities.ValidateNotNull(digest, "digest"); - JPakeUtilities.ValidateNotNull(random, "random"); - - if (password.Length == 0) - { - throw new ArgumentException("Password must not be empty."); - } - - this.participantId = participantId; - - // Create a defensive copy so as to fully encapsulate the password. - // - // This array will contain the password for the lifetime of this - // participant BEFORE CalculateKeyingMaterial() is called. - // - // i.e. When CalculateKeyingMaterial() is called, the array will be cleared - // in order to remove the password from memory. - // - // The caller is responsible for clearing the original password array - // given as input to this constructor. - this.password = new char[password.Length]; - Array.Copy(password, this.password, password.Length); - - this.p = group.P; - this.q = group.Q; - this.g = group.G; - - this.digest = digest; - this.random = random; - - this.state = STATE_INITIALIZED; - } - - /// - /// Gets the current state of this participant. - /// See the STATE_* constants for possible values. - /// - public virtual int State - { - get { return state; } - } - - - /// - /// Creates and returns the payload to send to the other participant during round 1. - /// - /// After execution, the State state} will be STATE_ROUND_1_CREATED}. - /// - public virtual JPakeRound1Payload CreateRound1PayloadToSend() - { - if (this.state >= STATE_ROUND_1_CREATED) - throw new InvalidOperationException("Round 1 payload already created for " + this.participantId); - - this.x1 = JPakeUtilities.GenerateX1(q, random); - this.x2 = JPakeUtilities.GenerateX2(q, random); - - this.gx1 = JPakeUtilities.CalculateGx(p, g, x1); - this.gx2 = JPakeUtilities.CalculateGx(p, g, x2); - BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); - BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); - - this.state = STATE_ROUND_1_CREATED; - - return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); - } - - /// - /// Validates the payload received from the other participant during round 1. - /// - /// Must be called prior to CreateRound2PayloadToSend(). - /// - /// After execution, the State state will be STATE_ROUND_1_VALIDATED. - /// - /// Throws CryptoException if validation fails. Throws InvalidOperationException - /// if called multiple times. - /// - public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived) - { - if (this.state >= STATE_ROUND_1_VALIDATED) - throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId); - - this.partnerParticipantId = round1PayloadReceived.ParticipantId; - this.gx3 = round1PayloadReceived.Gx1; - this.gx4 = round1PayloadReceived.Gx2; - - BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1; - BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2; - - JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); - JPakeUtilities.ValidateGx4(gx4); - JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); - JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); - this.state = STATE_ROUND_1_VALIDATED; - } - - /// - /// Creates and returns the payload to send to the other participant during round 2. - /// - /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method. - /// - /// After execution, the State state will be STATE_ROUND_2_CREATED. - /// - /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times - /// - public virtual JPakeRound2Payload CreateRound2PayloadToSend() - { - if (this.state >= STATE_ROUND_2_CREATED) - throw new InvalidOperationException("Round 2 payload already created for " + this.participantId); - if (this.state < STATE_ROUND_1_VALIDATED) - throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId); - - BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4); - BigInteger s = JPakeUtilities.CalculateS(password); - BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s); - BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s); - BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); - - this.state = STATE_ROUND_2_CREATED; - - return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s); - } - - /// - /// Validates the payload received from the other participant during round 2. - /// Note that this DOES NOT detect a non-common password. - /// The only indication of a non-common password is through derivation - /// of different keys (which can be detected explicitly by executing round 3 and round 4) - /// - /// Must be called prior to CalculateKeyingMaterial(). - /// - /// After execution, the State state will be STATE_ROUND_2_VALIDATED. - /// - /// Throws CryptoException if validation fails. Throws - /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times - /// - public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived) - { - if (this.state >= STATE_ROUND_2_VALIDATED) - throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId); - if (this.state < STATE_ROUND_1_VALIDATED) - throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId); - - BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2); - this.b = round2PayloadReceived.A; - BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s; - - JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId); - JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId); - JPakeUtilities.ValidateGa(gB); - JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest); - - this.state = STATE_ROUND_2_VALIDATED; - } - - /// - /// Calculates and returns the key material. - /// A session key must be derived from this key material using a secure key derivation function (KDF). - /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant). - /// - /// The keying material will be identical for each participant if and only if - /// each participant's password is the same. i.e. If the participants do not - /// share the same password, then each participant will derive a different key. - /// Therefore, if you immediately start using a key derived from - /// the keying material, then you must handle detection of incorrect keys. - /// If you want to handle this detection explicitly, you can optionally perform - /// rounds 3 and 4. See JPakeParticipant for details on how to execute - /// rounds 3 and 4. - /// - /// The keying material will be in the range [0, p-1]. - /// - /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method. - /// - /// As a side effect, the internal password array is cleared, since it is no longer needed. - /// - /// After execution, the State state will be STATE_KEY_CALCULATED. - /// - /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload), - /// or if called multiple times. - /// - public virtual BigInteger CalculateKeyingMaterial() - { - if (this.state >= STATE_KEY_CALCULATED) - throw new InvalidOperationException("Key already calculated for " + participantId); - if (this.state < STATE_ROUND_2_VALIDATED) - throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId); - - BigInteger s = JPakeUtilities.CalculateS(password); - - // Clear the password array from memory, since we don't need it anymore. - // Also set the field to null as a flag to indicate that the key has already been calculated. - Array.Clear(password, 0, password.Length); - this.password = null; - - BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b); - - // Clear the ephemeral private key fields as well. - // Note that we're relying on the garbage collector to do its job to clean these up. - // The old objects will hang around in memory until the garbage collector destroys them. - // - // If the ephemeral private keys x1 and x2 are leaked, - // the attacker might be able to brute-force the password. - this.x1 = null; - this.x2 = null; - this.b = null; - - // Do not clear gx* yet, since those are needed by round 3. - - this.state = STATE_KEY_CALCULATED; - - return keyingMaterial; - } - - /// - /// Creates and returns the payload to send to the other participant during round 3. - /// - /// See JPakeParticipant for more details on round 3. - /// - /// After execution, the State state} will be STATE_ROUND_3_CREATED. - /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple - /// times. - /// - /// The keying material as returned from CalculateKeyingMaterial(). - public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) - { - if (this.state >= STATE_ROUND_3_CREATED) - throw new InvalidOperationException("Round 3 payload already created for " + this.participantId); - if (this.state < STATE_KEY_CALCULATED) - throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId); - - BigInteger macTag = JPakeUtilities.CalculateMacTag( - this.participantId, - this.partnerParticipantId, - this.gx1, - this.gx2, - this.gx3, - this.gx4, - keyingMaterial, - this.digest); - - this.state = STATE_ROUND_3_CREATED; - - return new JPakeRound3Payload(participantId, macTag); - } - - /// - /// Validates the payload received from the other participant during round 3. - /// - /// See JPakeParticipant for more details on round 3. - /// - /// After execution, the State state will be STATE_ROUND_3_VALIDATED. - /// - /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to - /// CalculateKeyingMaterial or multiple times - /// - /// The keying material as returned from CalculateKeyingMaterial(). - public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial) - { - if (this.state >= STATE_ROUND_3_VALIDATED) - throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId); - if (this.state < STATE_KEY_CALCULATED) - throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId); - - JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId); - JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId); - - JPakeUtilities.ValidateMacTag( - this.participantId, - this.partnerParticipantId, - this.gx1, - this.gx2, - this.gx3, - this.gx4, - keyingMaterial, - this.digest, - round3PayloadReceived.MacTag); - - // Clear the rest of the fields. - this.gx1 = null; - this.gx2 = null; - this.gx3 = null; - this.gx4 = null; - - this.state = STATE_ROUND_3_VALIDATED; - } - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs deleted file mode 100755 index 08ffe1a55..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroup.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; - -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Crypto.Agreement.JPake -{ - /// - /// A pre-computed prime order group for use during a J-PAKE exchange. - /// - /// Typically a Schnorr group is used. In general, J-PAKE can use any prime order group - /// that is suitable for public key cryptography, including elliptic curve cryptography. - /// - /// See JPakePrimeOrderGroups for convenient standard groups. - /// - /// NIST publishes - /// many groups that can be used for the desired level of security. - /// - public class JPakePrimeOrderGroup - { - private readonly BigInteger p; - private readonly BigInteger q; - private readonly BigInteger g; - - /// - /// Constructs a new JPakePrimeOrderGroup. - /// - /// In general, you should use one of the pre-approved groups from - /// JPakePrimeOrderGroups, rather than manually constructing one. - /// - /// The following basic checks are performed: - /// - /// p-1 must be evenly divisible by q - /// g must be in [2, p-1] - /// g^q mod p must equal 1 - /// p must be prime (within reasonably certainty) - /// q must be prime (within reasonably certainty) - /// - /// The prime checks are performed using BigInteger#isProbablePrime(int), - /// and are therefore subject to the same probability guarantees. - /// - /// These checks prevent trivial mistakes. - /// However, due to the small uncertainties if p and q are not prime, - /// advanced attacks are not prevented. - /// Use it at your own risk. - /// - /// Throws NullReferenceException if any argument is null. Throws - /// InvalidOperationException is any of the above validations fail. - /// - public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) - : this(p, q, g, false) - { - // Don't skip the checks on user-specified groups. - } - - /// - /// Constructor used by the pre-approved groups in JPakePrimeOrderGroups. - /// These pre-approved groups can avoid the expensive checks. - /// User-specified groups should not use this constructor. - /// - public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) - { - JPakeUtilities.ValidateNotNull(p, "p"); - JPakeUtilities.ValidateNotNull(q, "q"); - JPakeUtilities.ValidateNotNull(g, "g"); - - if (!skipChecks) - { - if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero)) - throw new ArgumentException("p-1 must be evenly divisible by q"); - if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1) - throw new ArgumentException("g must be in [2, p-1]"); - if (!g.ModPow(q, p).Equals(JPakeUtilities.One)) - throw new ArgumentException("g^q mod p must equal 1"); - - // Note these checks do not guarantee that p and q are prime. - // We just have reasonable certainty that they are prime. - if (!p.IsProbablePrime(20)) - throw new ArgumentException("p must be prime"); - if (!q.IsProbablePrime(20)) - throw new ArgumentException("q must be prime"); - } - - this.p = p; - this.q = q; - this.g = g; - } - - public virtual BigInteger P - { - get { return p; } - } - - public virtual BigInteger Q - { - get { return q; } - } - - public virtual BigInteger G - { - get { return g; } - } - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs b/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs deleted file mode 100755 index 33640845a..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKEPrimeOrderGroups.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Crypto.Agreement.JPake -{ - /// - /// Standard pre-computed prime order groups for use by J-PAKE. - /// (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) - ///

- /// This class contains some convenient constants for use as input for - /// constructing {@link JPAKEParticipant}s. - ///

- /// The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), - /// and from the prime order groups - /// published by NIST. - ///

- public class JPakePrimeOrderGroups - { - /// - /// From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) - /// 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. - /// - public static readonly JPakePrimeOrderGroup SUN_JCE_1024 = new JPakePrimeOrderGroup( - // p - new BigInteger( - "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + - "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" + - "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" + - "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16), - // q - new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16), - // g - new BigInteger( - "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" + - "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" + - "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" + - "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16), - true - ); - - /// - /// From NIST. - /// 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. - /// - public static readonly JPakePrimeOrderGroup NIST_2048 = new JPakePrimeOrderGroup( - // p - new BigInteger( - "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" + - "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" + - "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" + - "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" + - "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" + - "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" + - "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" + - "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16), - // q - new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16), - // g - new BigInteger( - "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" + - "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" + - "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" + - "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" + - "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" + - "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" + - "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" + - "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16), - true - ); - - /// - /// From NIST. - /// 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. - /// - public static readonly JPakePrimeOrderGroup NIST_3072 = new JPakePrimeOrderGroup( - // p - new BigInteger( - "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + - "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" + - "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" + - "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" + - "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" + - "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" + - "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" + - "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" + - "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" + - "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" + - "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" + - "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16), - // q - new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16), - // g - new BigInteger( - "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" + - "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" + - "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" + - "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" + - "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" + - "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" + - "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" + - "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" + - "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" + - "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" + - "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" + - "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16), - true - ); - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs deleted file mode 100755 index 9e4ab7a5f..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKERound1Payload.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; - -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Crypto.Agreement.JPake -{ - /// - /// The payload sent/received during the first round of a J-PAKE exchange. - /// - /// Each JPAKEParticipant creates and sends an instance of this payload to - /// the other. The payload to send should be created via - /// JPAKEParticipant.CreateRound1PayloadToSend(). - /// - /// Each participant must also validate the payload received from the other. - /// The received payload should be validated via - /// JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload). - /// - public class JPakeRound1Payload - { - /// - /// The id of the JPAKEParticipant who created/sent this payload. - /// - private readonly string participantId; - - /// - /// The value of g^x1 - /// - private readonly BigInteger gx1; - - /// - /// The value of g^x2 - /// - private readonly BigInteger gx2; - - /// - /// The zero knowledge proof for x1. - /// - /// This is a two element array, containing {g^v, r} for x1. - /// - private readonly BigInteger[] knowledgeProofForX1; - - /// - /// The zero knowledge proof for x2. - /// - /// This is a two element array, containing {g^v, r} for x2. - /// - private readonly BigInteger[] knowledgeProofForX2; - - public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2) - { - JPakeUtilities.ValidateNotNull(participantId, "participantId"); - JPakeUtilities.ValidateNotNull(gx1, "gx1"); - JPakeUtilities.ValidateNotNull(gx2, "gx2"); - JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); - JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); - - this.participantId = participantId; - this.gx1 = gx1; - this.gx2 = gx2; - this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length]; - Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length); - this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length]; - Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length); - } - - public virtual string ParticipantId - { - get { return participantId; } - } - - public virtual BigInteger Gx1 - { - get { return gx1; } - } - - public virtual BigInteger Gx2 - { - get { return gx2; } - } - - public virtual BigInteger[] KnowledgeProofForX1 - { - get - { - BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length]; - Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length); - return kp; - } - } - - public virtual BigInteger[] KnowledgeProofForX2 - { - get - { - BigInteger[] kp = new BigInteger[knowledgeProofForX2.Length]; - Array.Copy(knowledgeProofForX2, kp, knowledgeProofForX2.Length); - return kp; - } - } - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs deleted file mode 100755 index 47962cb3f..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKERound2Payload.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; - -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Utilities; - -namespace Org.BouncyCastle.Crypto.Agreement.JPake -{ - /// - /// The payload sent/received during the second round of a J-PAKE exchange. - /// - /// Each JPAKEParticipant creates and sends an instance - /// of this payload to the other JPAKEParticipant. - /// The payload to send should be created via - /// JPAKEParticipant#createRound2PayloadToSend() - /// - /// Each JPAKEParticipant must also validate the payload - /// received from the other JPAKEParticipant. - /// The received payload should be validated via - /// JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload) - /// - public class JPakeRound2Payload - { - /// - /// The id of the JPAKEParticipant who created/sent this payload. - /// - private readonly string participantId; - - /// - /// The value of A, as computed during round 2. - /// - private readonly BigInteger a; - - /// - /// The zero knowledge proof for x2 * s. - /// - /// This is a two element array, containing {g^v, r} for x2 * s. - /// - private readonly BigInteger[] knowledgeProofForX2s; - - public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s) - { - JPakeUtilities.ValidateNotNull(participantId, "participantId"); - JPakeUtilities.ValidateNotNull(a, "a"); - JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); - - this.participantId = participantId; - this.a = a; - this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length]; - knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0); - } - - public virtual string ParticipantId - { - get { return participantId; } - } - - public virtual BigInteger A - { - get { return a; } - } - - public virtual BigInteger[] KnowledgeProofForX2s - { - get - { - BigInteger[] kp = new BigInteger[knowledgeProofForX2s.Length]; - Array.Copy(knowledgeProofForX2s, kp, knowledgeProofForX2s.Length); - return kp; - } - } - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs b/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs deleted file mode 100755 index 767702f23..000000000 --- a/crypto/src/crypto/agreement/jpake/JPAKERound3Payload.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; - -using Org.BouncyCastle.Math; - -namespace Org.BouncyCastle.Crypto.Agreement.JPake -{ - /// - /// The payload sent/received during the optional third round of a J-PAKE exchange, - /// which is for explicit key confirmation. - /// - /// Each JPAKEParticipant creates and sends an instance - /// of this payload to the other JPAKEParticipant. - /// The payload to send should be created via - /// JPAKEParticipant#createRound3PayloadToSend(BigInteger) - /// - /// Eeach JPAKEParticipant must also validate the payload - /// received from the other JPAKEParticipant. - /// The received payload should be validated via - /// JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - /// - public class JPakeRound3Payload - { - /// - /// The id of the {@link JPAKEParticipant} who created/sent this payload. - /// - private readonly string participantId; - - /// - /// The value of MacTag, as computed by round 3. - /// - /// See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest) - /// - private readonly BigInteger macTag; - - public JPakeRound3Payload(string participantId, BigInteger magTag) - { - this.participantId = participantId; - this.macTag = magTag; - } - - public virtual string ParticipantId - { - get { return participantId; } - } - - public virtual BigInteger MacTag - { - get { return macTag; } - } - } -} diff --git a/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs new file mode 100755 index 000000000..f8ca2cd41 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs @@ -0,0 +1,455 @@ +using System; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange. + /// + /// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper + /// + /// "Password Authenticated Key Exchange by Juggling, 2008." + /// + /// The J-PAKE protocol is symmetric. + /// There is no notion of a client or server, but rather just two participants. + /// An instance of JPakeParticipant represents one participant, and + /// is the primary interface for executing the exchange. + /// + /// To execute an exchange, construct a JPakeParticipant on each end, + /// and call the following 7 methods + /// (once and only once, in the given order, for each participant, sending messages between them as described): + /// + /// CreateRound1PayloadToSend() - and send the payload to the other participant + /// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant + /// CreateRound2PayloadToSend() - and send the payload to the other participant + /// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant + /// CalculateKeyingMaterial() + /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant + /// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant + /// + /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial(). + /// The caller is responsible for deriving the session key using a secure key derivation function (KDF). + /// + /// Round 3 is an optional key confirmation process. + /// If you do not execute round 3, then there is no assurance that both participants are using the same key. + /// (i.e. if the participants used different passwords, then their session keys will differ.) + /// + /// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides. + /// + /// The symmetric design can easily support the asymmetric cases when one party initiates the communication. + /// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass. + /// Also, in some cases, the key confirmation payload can be sent together with the round2 payload. + /// These are the trivial techniques to optimize the communication. + /// + /// The key confirmation process is implemented as specified in + /// NIST SP 800-56A Revision 1, + /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes. + /// + /// This class is stateful and NOT threadsafe. + /// Each instance should only be used for ONE complete J-PAKE exchange + /// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange). + /// + public class JPakeParticipant + { + // Possible internal states. Used for state checking. + public static readonly int STATE_INITIALIZED = 0; + public static readonly int STATE_ROUND_1_CREATED = 10; + public static readonly int STATE_ROUND_1_VALIDATED = 20; + public static readonly int STATE_ROUND_2_CREATED = 30; + public static readonly int STATE_ROUND_2_VALIDATED = 40; + public static readonly int STATE_KEY_CALCULATED = 50; + public static readonly int STATE_ROUND_3_CREATED = 60; + public static readonly int STATE_ROUND_3_VALIDATED = 70; + + // Unique identifier of this participant. + // The two participants in the exchange must NOT share the same id. + private string participantId; + + // Shared secret. This only contains the secret between construction + // and the call to CalculateKeyingMaterial(). + // + // i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's, + // and the field is set to null. + private char[] password; + + // Digest to use during calculations. + private IDigest digest; + + // Source of secure random data. + private readonly SecureRandom random; + + private readonly BigInteger p; + private readonly BigInteger q; + private readonly BigInteger g; + + // The participantId of the other participant in this exchange. + private string partnerParticipantId; + + // Alice's x1 or Bob's x3. + private BigInteger x1; + // Alice's x2 or Bob's x4. + private BigInteger x2; + // Alice's g^x1 or Bob's g^x3. + private BigInteger gx1; + // Alice's g^x2 or Bob's g^x4. + private BigInteger gx2; + // Alice's g^x3 or Bob's g^x1. + private BigInteger gx3; + // Alice's g^x4 or Bob's g^x2. + private BigInteger gx4; + // Alice's B or Bob's A. + private BigInteger b; + + // The current state. + // See the STATE_* constants for possible values. + private int state; + + /// + /// Convenience constructor for a new JPakeParticipant that uses + /// the JPakePrimeOrderGroups#NIST_3072 prime order group, + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + public JPakeParticipant(string participantId, char[] password) + : this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { } + + /// + /// Convenience constructor for a new JPakeParticipant that uses + /// a SHA-256 digest, and a default SecureRandom implementation. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + /// Prime order group. See JPakePrimeOrderGroups for standard groups. + public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group) + : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { } + + + /// + /// Constructor for a new JPakeParticipant. + /// + /// After construction, the State state will be STATE_INITIALIZED. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// ArgumentException if password is empty. + /// + /// Unique identifier of this participant. + /// The two participants in the exchange must NOT share the same id. + /// Shared secret. + /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called). + /// Caller should clear the input password as soon as possible. + /// Prime order group. See JPakePrimeOrderGroups for standard groups. + /// Digest to use during zero knowledge proofs and key confirmation + /// (SHA-256 or stronger preferred). + /// Source of secure random data for x1 and x2, and for the zero knowledge proofs. + public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random) + { + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(password, "password"); + JPakeUtilities.ValidateNotNull(group, "p"); + JPakeUtilities.ValidateNotNull(digest, "digest"); + JPakeUtilities.ValidateNotNull(random, "random"); + + if (password.Length == 0) + { + throw new ArgumentException("Password must not be empty."); + } + + this.participantId = participantId; + + // Create a defensive copy so as to fully encapsulate the password. + // + // This array will contain the password for the lifetime of this + // participant BEFORE CalculateKeyingMaterial() is called. + // + // i.e. When CalculateKeyingMaterial() is called, the array will be cleared + // in order to remove the password from memory. + // + // The caller is responsible for clearing the original password array + // given as input to this constructor. + this.password = new char[password.Length]; + Array.Copy(password, this.password, password.Length); + + this.p = group.P; + this.q = group.Q; + this.g = group.G; + + this.digest = digest; + this.random = random; + + this.state = STATE_INITIALIZED; + } + + /// + /// Gets the current state of this participant. + /// See the STATE_* constants for possible values. + /// + public virtual int State + { + get { return state; } + } + + + /// + /// Creates and returns the payload to send to the other participant during round 1. + /// + /// After execution, the State state} will be STATE_ROUND_1_CREATED}. + /// + public virtual JPakeRound1Payload CreateRound1PayloadToSend() + { + if (this.state >= STATE_ROUND_1_CREATED) + throw new InvalidOperationException("Round 1 payload already created for " + this.participantId); + + this.x1 = JPakeUtilities.GenerateX1(q, random); + this.x2 = JPakeUtilities.GenerateX2(q, random); + + this.gx1 = JPakeUtilities.CalculateGx(p, g, x1); + this.gx2 = JPakeUtilities.CalculateGx(p, g, x2); + BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random); + BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random); + + this.state = STATE_ROUND_1_CREATED; + + return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + } + + /// + /// Validates the payload received from the other participant during round 1. + /// + /// Must be called prior to CreateRound2PayloadToSend(). + /// + /// After execution, the State state will be STATE_ROUND_1_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws InvalidOperationException + /// if called multiple times. + /// + public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived) + { + if (this.state >= STATE_ROUND_1_VALIDATED) + throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId); + + this.partnerParticipantId = round1PayloadReceived.ParticipantId; + this.gx3 = round1PayloadReceived.Gx1; + this.gx4 = round1PayloadReceived.Gx2; + + BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1; + BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2; + + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId); + JPakeUtilities.ValidateGx4(gx4); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest); + this.state = STATE_ROUND_1_VALIDATED; + } + + /// + /// Creates and returns the payload to send to the other participant during round 2. + /// + /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method. + /// + /// After execution, the State state will be STATE_ROUND_2_CREATED. + /// + /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times + /// + public virtual JPakeRound2Payload CreateRound2PayloadToSend() + { + if (this.state >= STATE_ROUND_2_CREATED) + throw new InvalidOperationException("Round 2 payload already created for " + this.participantId); + if (this.state < STATE_ROUND_1_VALIDATED) + throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId); + + BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4); + BigInteger s = JPakeUtilities.CalculateS(password); + BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s); + BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s); + BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random); + + this.state = STATE_ROUND_2_CREATED; + + return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s); + } + + /// + /// Validates the payload received from the other participant during round 2. + /// Note that this DOES NOT detect a non-common password. + /// The only indication of a non-common password is through derivation + /// of different keys (which can be detected explicitly by executing round 3 and round 4) + /// + /// Must be called prior to CalculateKeyingMaterial(). + /// + /// After execution, the State state will be STATE_ROUND_2_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws + /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times + /// + public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived) + { + if (this.state >= STATE_ROUND_2_VALIDATED) + throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId); + if (this.state < STATE_ROUND_1_VALIDATED) + throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId); + + BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2); + this.b = round2PayloadReceived.A; + BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s; + + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId); + JPakeUtilities.ValidateGa(gB); + JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest); + + this.state = STATE_ROUND_2_VALIDATED; + } + + /// + /// Calculates and returns the key material. + /// A session key must be derived from this key material using a secure key derivation function (KDF). + /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant). + /// + /// The keying material will be identical for each participant if and only if + /// each participant's password is the same. i.e. If the participants do not + /// share the same password, then each participant will derive a different key. + /// Therefore, if you immediately start using a key derived from + /// the keying material, then you must handle detection of incorrect keys. + /// If you want to handle this detection explicitly, you can optionally perform + /// rounds 3 and 4. See JPakeParticipant for details on how to execute + /// rounds 3 and 4. + /// + /// The keying material will be in the range [0, p-1]. + /// + /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method. + /// + /// As a side effect, the internal password array is cleared, since it is no longer needed. + /// + /// After execution, the State state will be STATE_KEY_CALCULATED. + /// + /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload), + /// or if called multiple times. + /// + public virtual BigInteger CalculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + throw new InvalidOperationException("Key already calculated for " + participantId); + if (this.state < STATE_ROUND_2_VALIDATED) + throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId); + + BigInteger s = JPakeUtilities.CalculateS(password); + + // Clear the password array from memory, since we don't need it anymore. + // Also set the field to null as a flag to indicate that the key has already been calculated. + Array.Clear(password, 0, password.Length); + this.password = null; + + BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b); + + // Clear the ephemeral private key fields as well. + // Note that we're relying on the garbage collector to do its job to clean these up. + // The old objects will hang around in memory until the garbage collector destroys them. + // + // If the ephemeral private keys x1 and x2 are leaked, + // the attacker might be able to brute-force the password. + this.x1 = null; + this.x2 = null; + this.b = null; + + // Do not clear gx* yet, since those are needed by round 3. + + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /// + /// Creates and returns the payload to send to the other participant during round 3. + /// + /// See JPakeParticipant for more details on round 3. + /// + /// After execution, the State state} will be STATE_ROUND_3_CREATED. + /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple + /// times. + /// + /// The keying material as returned from CalculateKeyingMaterial(). + public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_CREATED) + throw new InvalidOperationException("Round 3 payload already created for " + this.participantId); + if (this.state < STATE_KEY_CALCULATED) + throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId); + + BigInteger macTag = JPakeUtilities.CalculateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest); + + this.state = STATE_ROUND_3_CREATED; + + return new JPakeRound3Payload(participantId, macTag); + } + + /// + /// Validates the payload received from the other participant during round 3. + /// + /// See JPakeParticipant for more details on round 3. + /// + /// After execution, the State state will be STATE_ROUND_3_VALIDATED. + /// + /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to + /// CalculateKeyingMaterial or multiple times + /// + /// The keying material as returned from CalculateKeyingMaterial(). + public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial) + { + if (this.state >= STATE_ROUND_3_VALIDATED) + throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId); + if (this.state < STATE_KEY_CALCULATED) + throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId); + + JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId); + JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId); + + JPakeUtilities.ValidateMacTag( + this.participantId, + this.partnerParticipantId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest, + round3PayloadReceived.MacTag); + + // Clear the rest of the fields. + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_ROUND_3_VALIDATED; + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs new file mode 100755 index 000000000..08ffe1a55 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroup.cs @@ -0,0 +1,103 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// A pre-computed prime order group for use during a J-PAKE exchange. + /// + /// Typically a Schnorr group is used. In general, J-PAKE can use any prime order group + /// that is suitable for public key cryptography, including elliptic curve cryptography. + /// + /// See JPakePrimeOrderGroups for convenient standard groups. + /// + /// NIST publishes + /// many groups that can be used for the desired level of security. + /// + public class JPakePrimeOrderGroup + { + private readonly BigInteger p; + private readonly BigInteger q; + private readonly BigInteger g; + + /// + /// Constructs a new JPakePrimeOrderGroup. + /// + /// In general, you should use one of the pre-approved groups from + /// JPakePrimeOrderGroups, rather than manually constructing one. + /// + /// The following basic checks are performed: + /// + /// p-1 must be evenly divisible by q + /// g must be in [2, p-1] + /// g^q mod p must equal 1 + /// p must be prime (within reasonably certainty) + /// q must be prime (within reasonably certainty) + /// + /// The prime checks are performed using BigInteger#isProbablePrime(int), + /// and are therefore subject to the same probability guarantees. + /// + /// These checks prevent trivial mistakes. + /// However, due to the small uncertainties if p and q are not prime, + /// advanced attacks are not prevented. + /// Use it at your own risk. + /// + /// Throws NullReferenceException if any argument is null. Throws + /// InvalidOperationException is any of the above validations fail. + /// + public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g) + : this(p, q, g, false) + { + // Don't skip the checks on user-specified groups. + } + + /// + /// Constructor used by the pre-approved groups in JPakePrimeOrderGroups. + /// These pre-approved groups can avoid the expensive checks. + /// User-specified groups should not use this constructor. + /// + public JPakePrimeOrderGroup(BigInteger p, BigInteger q, BigInteger g, bool skipChecks) + { + JPakeUtilities.ValidateNotNull(p, "p"); + JPakeUtilities.ValidateNotNull(q, "q"); + JPakeUtilities.ValidateNotNull(g, "g"); + + if (!skipChecks) + { + if (!p.Subtract(JPakeUtilities.One).Mod(q).Equals(JPakeUtilities.Zero)) + throw new ArgumentException("p-1 must be evenly divisible by q"); + if (g.CompareTo(BigInteger.Two) == -1 || g.CompareTo(p.Subtract(JPakeUtilities.One)) == 1) + throw new ArgumentException("g must be in [2, p-1]"); + if (!g.ModPow(q, p).Equals(JPakeUtilities.One)) + throw new ArgumentException("g^q mod p must equal 1"); + + // Note these checks do not guarantee that p and q are prime. + // We just have reasonable certainty that they are prime. + if (!p.IsProbablePrime(20)) + throw new ArgumentException("p must be prime"); + if (!q.IsProbablePrime(20)) + throw new ArgumentException("q must be prime"); + } + + this.p = p; + this.q = q; + this.g = g; + } + + public virtual BigInteger P + { + get { return p; } + } + + public virtual BigInteger Q + { + get { return q; } + } + + public virtual BigInteger G + { + get { return g; } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs new file mode 100755 index 000000000..33640845a --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs @@ -0,0 +1,108 @@ +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// Standard pre-computed prime order groups for use by J-PAKE. + /// (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) + ///

+ /// This class contains some convenient constants for use as input for + /// constructing {@link JPAKEParticipant}s. + ///

+ /// The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), + /// and from the prime order groups + /// published by NIST. + ///

+ public class JPakePrimeOrderGroups + { + /// + /// From Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB) + /// 1024-bit p, 160-bit q and 1024-bit g for 80-bit security. + /// + public static readonly JPakePrimeOrderGroup SUN_JCE_1024 = new JPakePrimeOrderGroup( + // p + new BigInteger( + "fd7f53811d75122952df4a9c2eece4e7f611b7523cef4400c31e3f80b6512669" + + "455d402251fb593d8d58fabfc5f5ba30f6cb9b556cd7813b801d346ff26660b7" + + "6b9950a5a49f9fe8047b1022c24fbba9d7feb7c61bf83b57e7c6a8a6150f04fb" + + "83f6d3c51ec3023554135a169132f675f3ae2b61d72aeff22203199dd14801c7", 16), + // q + new BigInteger("9760508f15230bccb292b982a2eb840bf0581cf5", 16), + // g + new BigInteger( + "f7e1a085d69b3ddecbbcab5c36b857b97994afbbfa3aea82f9574c0b3d078267" + + "5159578ebad4594fe67107108180b449167123e84c281613b7cf09328cc8a6e1" + + "3c167a8b547c8d28e0a3ae1e2bb3a675916ea37f0bfa213562f1fb627a01243b" + + "cca4f1bea8519089a883dfe15ae59f06928b665e807b552564014c3bfecf492a", 16), + true + ); + + /// + /// From NIST. + /// 2048-bit p, 224-bit q and 2048-bit g for 112-bit security. + /// + public static readonly JPakePrimeOrderGroup NIST_2048 = new JPakePrimeOrderGroup( + // p + new BigInteger( + "C196BA05AC29E1F9C3C72D56DFFC6154A033F1477AC88EC37F09BE6C5BB95F51" + + "C296DD20D1A28A067CCC4D4316A4BD1DCA55ED1066D438C35AEBAABF57E7DAE4" + + "28782A95ECA1C143DB701FD48533A3C18F0FE23557EA7AE619ECACC7E0B51652" + + "A8776D02A425567DED36EABD90CA33A1E8D988F0BBB92D02D1D20290113BB562" + + "CE1FC856EEB7CDD92D33EEA6F410859B179E7E789A8F75F645FAE2E136D252BF" + + "FAFF89528945C1ABE705A38DBC2D364AADE99BE0D0AAD82E5320121496DC65B3" + + "930E38047294FF877831A16D5228418DE8AB275D7D75651CEFED65F78AFC3EA7" + + "FE4D79B35F62A0402A1117599ADAC7B269A59F353CF450E6982D3B1702D9CA83", 16), + // q + new BigInteger("90EAF4D1AF0708B1B612FF35E0A2997EB9E9D263C9CE659528945C0D", 16), + // g + new BigInteger( + "A59A749A11242C58C894E9E5A91804E8FA0AC64B56288F8D47D51B1EDC4D6544" + + "4FECA0111D78F35FC9FDD4CB1F1B79A3BA9CBEE83A3F811012503C8117F98E50" + + "48B089E387AF6949BF8784EBD9EF45876F2E6A5A495BE64B6E770409494B7FEE" + + "1DBB1E4B2BC2A53D4F893D418B7159592E4FFFDF6969E91D770DAEBD0B5CB14C" + + "00AD68EC7DC1E5745EA55C706C4A1C5C88964E34D09DEB753AD418C1AD0F4FDF" + + "D049A955E5D78491C0B7A2F1575A008CCD727AB376DB6E695515B05BD412F5B8" + + "C2F4C77EE10DA48ABD53F5DD498927EE7B692BBBCDA2FB23A516C5B4533D7398" + + "0B2A3B60E384ED200AE21B40D273651AD6060C13D97FD69AA13C5611A51B9085", 16), + true + ); + + /// + /// From NIST. + /// 3072-bit p, 256-bit q and 3072-bit g for 128-bit security. + /// + public static readonly JPakePrimeOrderGroup NIST_3072 = new JPakePrimeOrderGroup( + // p + new BigInteger( + "90066455B5CFC38F9CAA4A48B4281F292C260FEEF01FD61037E56258A7795A1C" + + "7AD46076982CE6BB956936C6AB4DCFE05E6784586940CA544B9B2140E1EB523F" + + "009D20A7E7880E4E5BFA690F1B9004A27811CD9904AF70420EEFD6EA11EF7DA1" + + "29F58835FF56B89FAA637BC9AC2EFAAB903402229F491D8D3485261CD068699B" + + "6BA58A1DDBBEF6DB51E8FE34E8A78E542D7BA351C21EA8D8F1D29F5D5D159394" + + "87E27F4416B0CA632C59EFD1B1EB66511A5A0FBF615B766C5862D0BD8A3FE7A0" + + "E0DA0FB2FE1FCB19E8F9996A8EA0FCCDE538175238FC8B0EE6F29AF7F642773E" + + "BE8CD5402415A01451A840476B2FCEB0E388D30D4B376C37FE401C2A2C2F941D" + + "AD179C540C1C8CE030D460C4D983BE9AB0B20F69144C1AE13F9383EA1C08504F" + + "B0BF321503EFE43488310DD8DC77EC5B8349B8BFE97C2C560EA878DE87C11E3D" + + "597F1FEA742D73EEC7F37BE43949EF1A0D15C3F3E3FC0A8335617055AC91328E" + + "C22B50FC15B941D3D1624CD88BC25F3E941FDDC6200689581BFEC416B4B2CB73", 16), + // q + new BigInteger("CFA0478A54717B08CE64805B76E5B14249A77A4838469DF7F7DC987EFCCFB11D", 16), + // g + new BigInteger( + "5E5CBA992E0A680D885EB903AEA78E4A45A469103D448EDE3B7ACCC54D521E37" + + "F84A4BDD5B06B0970CC2D2BBB715F7B82846F9A0C393914C792E6A923E2117AB" + + "805276A975AADB5261D91673EA9AAFFEECBFA6183DFCB5D3B7332AA19275AFA1" + + "F8EC0B60FB6F66CC23AE4870791D5982AAD1AA9485FD8F4A60126FEB2CF05DB8" + + "A7F0F09B3397F3937F2E90B9E5B9C9B6EFEF642BC48351C46FB171B9BFA9EF17" + + "A961CE96C7E7A7CC3D3D03DFAD1078BA21DA425198F07D2481622BCE45969D9C" + + "4D6063D72AB7A0F08B2F49A7CC6AF335E08C4720E31476B67299E231F8BD90B3" + + "9AC3AE3BE0C6B6CACEF8289A2E2873D58E51E029CAFBD55E6841489AB66B5B4B" + + "9BA6E2F784660896AFF387D92844CCB8B69475496DE19DA2E58259B090489AC8" + + "E62363CDF82CFD8EF2A427ABCD65750B506F56DDE3B988567A88126B914D7828" + + "E2B63A6D7ED0747EC59E0E0A23CE7D8A74C1D2C2A7AFB6A29799620F00E11C33" + + "787F7DED3B30E1A22D09F1FBDA1ABBBFBF25CAE05A13F812E34563F99410E73B", 16), + true + ); + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs b/crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs new file mode 100755 index 000000000..9e4ab7a5f --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakeRound1Payload.cs @@ -0,0 +1,101 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// The payload sent/received during the first round of a J-PAKE exchange. + /// + /// Each JPAKEParticipant creates and sends an instance of this payload to + /// the other. The payload to send should be created via + /// JPAKEParticipant.CreateRound1PayloadToSend(). + /// + /// Each participant must also validate the payload received from the other. + /// The received payload should be validated via + /// JPAKEParticipant.ValidateRound1PayloadReceived(JPakeRound1Payload). + /// + public class JPakeRound1Payload + { + /// + /// The id of the JPAKEParticipant who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of g^x1 + /// + private readonly BigInteger gx1; + + /// + /// The value of g^x2 + /// + private readonly BigInteger gx2; + + /// + /// The zero knowledge proof for x1. + /// + /// This is a two element array, containing {g^v, r} for x1. + /// + private readonly BigInteger[] knowledgeProofForX1; + + /// + /// The zero knowledge proof for x2. + /// + /// This is a two element array, containing {g^v, r} for x2. + /// + private readonly BigInteger[] knowledgeProofForX2; + + public JPakeRound1Payload(string participantId, BigInteger gx1, BigInteger gx2, BigInteger[] knowledgeProofForX1, BigInteger[] knowledgeProofForX2) + { + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(gx1, "gx1"); + JPakeUtilities.ValidateNotNull(gx2, "gx2"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + + this.participantId = participantId; + this.gx1 = gx1; + this.gx2 = gx2; + this.knowledgeProofForX1 = new BigInteger[knowledgeProofForX1.Length]; + Array.Copy(knowledgeProofForX1, this.knowledgeProofForX1, knowledgeProofForX1.Length); + this.knowledgeProofForX2 = new BigInteger[knowledgeProofForX2.Length]; + Array.Copy(knowledgeProofForX2, this.knowledgeProofForX2, knowledgeProofForX2.Length); + } + + public virtual string ParticipantId + { + get { return participantId; } + } + + public virtual BigInteger Gx1 + { + get { return gx1; } + } + + public virtual BigInteger Gx2 + { + get { return gx2; } + } + + public virtual BigInteger[] KnowledgeProofForX1 + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX1.Length]; + Array.Copy(knowledgeProofForX1, kp, knowledgeProofForX1.Length); + return kp; + } + } + + public virtual BigInteger[] KnowledgeProofForX2 + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX2.Length]; + Array.Copy(knowledgeProofForX2, kp, knowledgeProofForX2.Length); + return kp; + } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs b/crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs new file mode 100755 index 000000000..47962cb3f --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakeRound2Payload.cs @@ -0,0 +1,72 @@ +using System; + +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// The payload sent/received during the second round of a J-PAKE exchange. + /// + /// Each JPAKEParticipant creates and sends an instance + /// of this payload to the other JPAKEParticipant. + /// The payload to send should be created via + /// JPAKEParticipant#createRound2PayloadToSend() + /// + /// Each JPAKEParticipant must also validate the payload + /// received from the other JPAKEParticipant. + /// The received payload should be validated via + /// JPAKEParticipant#validateRound2PayloadReceived(JPakeRound2Payload) + /// + public class JPakeRound2Payload + { + /// + /// The id of the JPAKEParticipant who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of A, as computed during round 2. + /// + private readonly BigInteger a; + + /// + /// The zero knowledge proof for x2 * s. + /// + /// This is a two element array, containing {g^v, r} for x2 * s. + /// + private readonly BigInteger[] knowledgeProofForX2s; + + public JPakeRound2Payload(string participantId, BigInteger a, BigInteger[] knowledgeProofForX2s) + { + JPakeUtilities.ValidateNotNull(participantId, "participantId"); + JPakeUtilities.ValidateNotNull(a, "a"); + JPakeUtilities.ValidateNotNull(knowledgeProofForX2s, "knowledgeProofForX2s"); + + this.participantId = participantId; + this.a = a; + this.knowledgeProofForX2s = new BigInteger[knowledgeProofForX2s.Length]; + knowledgeProofForX2s.CopyTo(this.knowledgeProofForX2s, 0); + } + + public virtual string ParticipantId + { + get { return participantId; } + } + + public virtual BigInteger A + { + get { return a; } + } + + public virtual BigInteger[] KnowledgeProofForX2s + { + get + { + BigInteger[] kp = new BigInteger[knowledgeProofForX2s.Length]; + Array.Copy(knowledgeProofForX2s, kp, knowledgeProofForX2s.Length); + return kp; + } + } + } +} diff --git a/crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs b/crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs new file mode 100755 index 000000000..767702f23 --- /dev/null +++ b/crypto/src/crypto/agreement/jpake/JPakeRound3Payload.cs @@ -0,0 +1,51 @@ +using System; + +using Org.BouncyCastle.Math; + +namespace Org.BouncyCastle.Crypto.Agreement.JPake +{ + /// + /// The payload sent/received during the optional third round of a J-PAKE exchange, + /// which is for explicit key confirmation. + /// + /// Each JPAKEParticipant creates and sends an instance + /// of this payload to the other JPAKEParticipant. + /// The payload to send should be created via + /// JPAKEParticipant#createRound3PayloadToSend(BigInteger) + /// + /// Eeach JPAKEParticipant must also validate the payload + /// received from the other JPAKEParticipant. + /// The received payload should be validated via + /// JPAKEParticipant#validateRound3PayloadReceived(JPakeRound3Payload, BigInteger) + /// + public class JPakeRound3Payload + { + /// + /// The id of the {@link JPAKEParticipant} who created/sent this payload. + /// + private readonly string participantId; + + /// + /// The value of MacTag, as computed by round 3. + /// + /// See JPAKEUtil#calculateMacTag(string, string, BigInteger, BigInteger, BigInteger, BigInteger, BigInteger, org.bouncycastle.crypto.Digest) + /// + private readonly BigInteger macTag; + + public JPakeRound3Payload(string participantId, BigInteger magTag) + { + this.participantId = participantId; + this.macTag = magTag; + } + + public virtual string ParticipantId + { + get { return participantId; } + } + + public virtual BigInteger MacTag + { + get { return macTag; } + } + } +} diff --git a/crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs b/crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs deleted file mode 100644 index c84264aa5..000000000 --- a/crypto/test/src/crypto/agreement/test/JPAKEParticipantTest.cs +++ /dev/null @@ -1,566 +0,0 @@ -using System; - -using NUnit.Framework; - -using Org.BouncyCastle.Crypto.Agreement.JPake; -using Org.BouncyCastle.Crypto.Digests; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.Utilities.Test; - -namespace Org.BouncyCastle.Crypto.Agreement.Tests -{ - [TestFixture] - public class JPakeParticipantTest - : SimpleTest - { - public override void PerformTest() - { - TestConstruction(); - TestSuccessfulExchange(); - TestIncorrectPassword(); - TestStateValidation(); - TestValidateRound1PayloadReceived(); - TestValidateRound2PayloadReceived(); - } - - public override string Name - { - get { return "JPakeParticipant"; } - } - - public static void Main( - string[] args) - { - RunTest(new JPakeParticipantTest()); - } - - [Test] - public void TestFunction() - { - string resultText = Perform().ToString(); - - Assert.AreEqual(Name + ": Okay", resultText); - } - - public void TestConstruction() - { - JPakePrimeOrderGroup group = JPakePrimeOrderGroups.SUN_JCE_1024; - SecureRandom random = new SecureRandom(); - IDigest digest = new Sha256Digest(); - string participantId = "participantId"; - char[] password = "password".ToCharArray(); - - // should succeed - new JPakeParticipant(participantId, password, group, digest, random); - - // null participantId - try - { - new JPakeParticipant(null, password, group, digest, random); - - Fail("failed to throw exception on null participantId"); - } - catch (ArgumentNullException) - { - // expected - } - - // null password - try - { - new JPakeParticipant(participantId, null, group, digest, random); - - Fail("failed to throw exception on null password"); - } - catch (ArgumentNullException) - { - // expected - } - - // empty password - try - { - new JPakeParticipant(participantId, "".ToCharArray(), group, digest, random); - - Fail("failed to throw exception on empty password"); - } - catch (ArgumentException) - { - // expected - } - - // null group - try - { - new JPakeParticipant(participantId, password, null, digest, random); - - Fail("failed to throw exception on null group"); - } - catch (ArgumentNullException) - { - // expected - } - - // null digest - try - { - new JPakeParticipant(participantId, password, group, null, random); - - Fail("failed to throw exception on null digest"); - } - catch (ArgumentNullException) - { - // expected - } - - // null random - try - { - new JPakeParticipant(participantId, password, group, digest, null); - - Fail("failed to throw exception on null random"); - } - catch (ArgumentNullException) - { - // expected - } - } - - public void TestSuccessfulExchange() - { - JPakeParticipant alice = CreateAlice(); - JPakeParticipant bob = CreateBob(); - - ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); - - alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); - bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); - - BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); - BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); - - JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); - - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); - - Assert.AreEqual(aliceKeyingMaterial, bobKeyingMaterial); - } - - public void TestIncorrectPassword() - { - JPakeParticipant alice = CreateAlice(); - JPakeParticipant bob = CreateBobWithWrongPassword(); - - ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); - - alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); - bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); - - BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); - BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); - - JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); - - try - { - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - - Fail("failed to throw exception on incorrect password"); - } - catch (CryptoException) - { - // expected - } - - try - { - bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); - - Fail("failed to throw exception on incorrect password"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestStateValidation() - { - JPakeParticipant alice = CreateAlice(); - JPakeParticipant bob = CreateBob(); - - // We're testing alice here. Bob is just used for help. - - // START ROUND 1 CHECKS - - Assert.AreEqual(JPakeParticipant.STATE_INITIALIZED, alice.State); - - // create round 2 before round 1 - try - { - alice.CreateRound2PayloadToSend(); - - Fail("failed to throw on round 2 creation before 1"); - } - catch (InvalidOperationException) - { - // expected - } - - JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); - Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_CREATED, alice.State); - - // create round 1 twice - try - { - alice.CreateRound1PayloadToSend(); - - Fail("failed to throw on round 1 creation twice"); - } - catch (InvalidOperationException) - { - // expected - } - - // create round 2 before validation round 1 - try - { - alice.CreateRound2PayloadToSend(); - - Fail("failed to throw on round 2 creation before round 1 validation"); - } - catch (InvalidOperationException) - { - // expected - } - - // validate round 2 before validation round 1 - try - { - alice.ValidateRound2PayloadReceived(null); - - Fail("failed to throw on round 2 validation before round 1 validation"); - } - catch (InvalidOperationException) - { - // expected - } - - JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); - alice.ValidateRound1PayloadReceived(bobRound1Payload); - Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_VALIDATED, alice.State); - - // validate round 1 payload twice - try - { - alice.ValidateRound1PayloadReceived(bobRound1Payload); - - Fail("failed to throw on round 1 validation twice"); - } - catch (InvalidOperationException) - { - // expected - } - - bob.ValidateRound1PayloadReceived(aliceRound1Payload); - - // START ROUND 2 CHECKS - - JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); - Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_CREATED, alice.State); - - // create round 2 payload twice - try - { - alice.CreateRound2PayloadToSend(); - - Fail("failed to throw on round 2 creation twice"); - } - catch (InvalidOperationException) - { - // expected - } - - // create key before validation round 2 - try - { - alice.CalculateKeyingMaterial(); - - Fail("failed to throw on calculating keying material before round 2 validation"); - } - catch (InvalidOperationException) - { - // expected - } - - // validate round 3 before validating round 2 - try - { - alice.ValidateRound3PayloadReceived(null, null); - - Fail("failed to throw on validating round 3 before 2"); - } - catch (InvalidOperationException) - { - // expected - } - - JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); - alice.ValidateRound2PayloadReceived(bobRound2Payload); - Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_VALIDATED, alice.State); - - // validate round 2 payload twice - try - { - alice.ValidateRound2PayloadReceived(bobRound2Payload); - - Fail("failed to throw on validating round 2 twice"); - } - catch (InvalidOperationException) - { - // expected - } - - bob.ValidateRound2PayloadReceived(aliceRound2Payload); - - // create round 3 before calculating key - try - { - alice.CreateRound3PayloadToSend(BigInteger.One); - - Fail("failed to throw on creating round 3 before calculating key aterial"); - } - catch (InvalidOperationException) - { - // expected - } - - // START KEY CALCULATION CHECKS - - BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); - Assert.AreEqual(JPakeParticipant.STATE_KEY_CALCULATED, alice.State); - - // calculate key twice - try - { - alice.CalculateKeyingMaterial(); - - Fail("failed to throw on calculating key twice"); - } - catch (InvalidOperationException) - { - // expected - } - - BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); - - // START ROUND 3 CHECKS - - JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_CREATED, alice.State); - - // create round 3 payload twice - try - { - alice.CreateRound3PayloadToSend(aliceKeyingMaterial); - - Fail("failed to throw on creation round 3 twice"); - } - catch (InvalidOperationException) - { - // expected - } - - JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_VALIDATED, alice.State); - - // validate round 3 payload twice - try - { - alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); - - Fail("failed to throw on validation round 3 twice"); - } - catch (InvalidOperationException) - { - // expected - } - - bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); - } - - public void TestValidateRound1PayloadReceived() - { - // We're testing alice here. Bob is just used for help. - - JPakeRound1Payload bobRound1Payload = CreateBob().CreateRound1PayloadToSend(); - - // should succeed - CreateAlice().ValidateRound1PayloadReceived(bobRound1Payload); - - // alice verifies alice's payload - try - { - JPakeParticipant alice = CreateAlice(); - alice.ValidateRound1PayloadReceived(alice.CreateRound1PayloadToSend()); - - Fail("failed to throw on participant validating own payload"); - } - catch (CryptoException) - { - // expected - } - - // g^x4 == 1 - try - { - CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( - bobRound1Payload.ParticipantId, - bobRound1Payload.Gx1, - BigInteger.One, - bobRound1Payload.KnowledgeProofForX1, - bobRound1Payload.KnowledgeProofForX2)); - - Fail("failed to throw on g^x4 == 1"); - } - catch (CryptoException) - { - // expected - } - - // zero knowledge proof for x3 fails - try - { - JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); - CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( - bobRound1Payload.ParticipantId, - bobRound1Payload.Gx1, - bobRound1Payload.Gx2, - bobRound1Payload2.KnowledgeProofForX1, - bobRound1Payload.KnowledgeProofForX2)); - - Fail("failed to throw on incorrect zero knowledge proof for x3"); - } - catch (CryptoException) - { - // expected - } - - // zero knowledge proof for x4 fails - try - { - JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); - CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( - bobRound1Payload.ParticipantId, - bobRound1Payload.Gx1, - bobRound1Payload.Gx2, - bobRound1Payload.KnowledgeProofForX1, - bobRound1Payload2.KnowledgeProofForX2)); - - Fail("failed to throw on incorrect zero knowledge proof for x4"); - } - catch (CryptoException) - { - // expected - } - } - - public void TestValidateRound2PayloadReceived() - { - // We're testing alice here. Bob is just used for help. - - // should succeed - ExchangeAfterRound2Creation exchange1 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - exchange1.alice.ValidateRound2PayloadReceived(exchange1.bobRound2Payload); - - // alice verified alice's payload - ExchangeAfterRound2Creation exchange2 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - try - { - exchange2.alice.ValidateRound2PayloadReceived(exchange2.aliceRound2Payload); - - Fail("failed to throw on participant verifying own payload 2"); - } - catch (CryptoException) - { - // expected - } - - // wrong z - ExchangeAfterRound2Creation exchange3 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - ExchangeAfterRound2Creation exchange4 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); - try - { - exchange3.alice.ValidateRound2PayloadReceived(exchange4.bobRound2Payload); - - Fail("failed to throw on wrong z"); - } - catch (CryptoException) - { - // expected - } - } - - private class ExchangeAfterRound2Creation - { - public JPakeParticipant alice; - public JPakeRound2Payload aliceRound2Payload; - public JPakeRound2Payload bobRound2Payload; - - public ExchangeAfterRound2Creation( - JPakeParticipant alice, - JPakeRound2Payload aliceRound2Payload, - JPakeRound2Payload bobRound2Payload) - { - this.alice = alice; - this.aliceRound2Payload = aliceRound2Payload; - this.bobRound2Payload = bobRound2Payload; - } - } - - private ExchangeAfterRound2Creation RunExchangeUntilRound2Creation(JPakeParticipant alice, JPakeParticipant bob) - { - JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); - JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); - - alice.ValidateRound1PayloadReceived(bobRound1Payload); - bob.ValidateRound1PayloadReceived(aliceRound1Payload); - - JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); - JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); - - return new ExchangeAfterRound2Creation( - alice, - aliceRound2Payload, - bobRound2Payload); - } - - private JPakeParticipant CreateAlice() - { - return CreateParticipant("alice", "password"); - } - - private JPakeParticipant CreateBob() - { - return CreateParticipant("bob", "password"); - } - - private JPakeParticipant CreateBobWithWrongPassword() - { - return CreateParticipant("bob", "wrong"); - } - - private JPakeParticipant CreateParticipant(string participantId, string password) - { - return new JPakeParticipant( - participantId, - password.ToCharArray(), - JPakePrimeOrderGroups.SUN_JCE_1024); - } - } -} diff --git a/crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs b/crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs new file mode 100644 index 000000000..c84264aa5 --- /dev/null +++ b/crypto/test/src/crypto/agreement/test/JPakeParticipantTest.cs @@ -0,0 +1,566 @@ +using System; + +using NUnit.Framework; + +using Org.BouncyCastle.Crypto.Agreement.JPake; +using Org.BouncyCastle.Crypto.Digests; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Crypto.Agreement.Tests +{ + [TestFixture] + public class JPakeParticipantTest + : SimpleTest + { + public override void PerformTest() + { + TestConstruction(); + TestSuccessfulExchange(); + TestIncorrectPassword(); + TestStateValidation(); + TestValidateRound1PayloadReceived(); + TestValidateRound2PayloadReceived(); + } + + public override string Name + { + get { return "JPakeParticipant"; } + } + + public static void Main( + string[] args) + { + RunTest(new JPakeParticipantTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + + public void TestConstruction() + { + JPakePrimeOrderGroup group = JPakePrimeOrderGroups.SUN_JCE_1024; + SecureRandom random = new SecureRandom(); + IDigest digest = new Sha256Digest(); + string participantId = "participantId"; + char[] password = "password".ToCharArray(); + + // should succeed + new JPakeParticipant(participantId, password, group, digest, random); + + // null participantId + try + { + new JPakeParticipant(null, password, group, digest, random); + + Fail("failed to throw exception on null participantId"); + } + catch (ArgumentNullException) + { + // expected + } + + // null password + try + { + new JPakeParticipant(participantId, null, group, digest, random); + + Fail("failed to throw exception on null password"); + } + catch (ArgumentNullException) + { + // expected + } + + // empty password + try + { + new JPakeParticipant(participantId, "".ToCharArray(), group, digest, random); + + Fail("failed to throw exception on empty password"); + } + catch (ArgumentException) + { + // expected + } + + // null group + try + { + new JPakeParticipant(participantId, password, null, digest, random); + + Fail("failed to throw exception on null group"); + } + catch (ArgumentNullException) + { + // expected + } + + // null digest + try + { + new JPakeParticipant(participantId, password, group, null, random); + + Fail("failed to throw exception on null digest"); + } + catch (ArgumentNullException) + { + // expected + } + + // null random + try + { + new JPakeParticipant(participantId, password, group, digest, null); + + Fail("failed to throw exception on null random"); + } + catch (ArgumentNullException) + { + // expected + } + } + + public void TestSuccessfulExchange() + { + JPakeParticipant alice = CreateAlice(); + JPakeParticipant bob = CreateBob(); + + ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); + + alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); + bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + + Assert.AreEqual(aliceKeyingMaterial, bobKeyingMaterial); + } + + public void TestIncorrectPassword() + { + JPakeParticipant alice = CreateAlice(); + JPakeParticipant bob = CreateBobWithWrongPassword(); + + ExchangeAfterRound2Creation exchange = RunExchangeUntilRound2Creation(alice, bob); + + alice.ValidateRound2PayloadReceived(exchange.bobRound2Payload); + bob.ValidateRound2PayloadReceived(exchange.aliceRound2Payload); + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + + try + { + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + + Fail("failed to throw exception on incorrect password"); + } + catch (CryptoException) + { + // expected + } + + try + { + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + + Fail("failed to throw exception on incorrect password"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestStateValidation() + { + JPakeParticipant alice = CreateAlice(); + JPakeParticipant bob = CreateBob(); + + // We're testing alice here. Bob is just used for help. + + // START ROUND 1 CHECKS + + Assert.AreEqual(JPakeParticipant.STATE_INITIALIZED, alice.State); + + // create round 2 before round 1 + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation before 1"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_CREATED, alice.State); + + // create round 1 twice + try + { + alice.CreateRound1PayloadToSend(); + + Fail("failed to throw on round 1 creation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + // create round 2 before validation round 1 + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation before round 1 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + // validate round 2 before validation round 1 + try + { + alice.ValidateRound2PayloadReceived(null); + + Fail("failed to throw on round 2 validation before round 1 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); + alice.ValidateRound1PayloadReceived(bobRound1Payload); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_1_VALIDATED, alice.State); + + // validate round 1 payload twice + try + { + alice.ValidateRound1PayloadReceived(bobRound1Payload); + + Fail("failed to throw on round 1 validation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound1PayloadReceived(aliceRound1Payload); + + // START ROUND 2 CHECKS + + JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_CREATED, alice.State); + + // create round 2 payload twice + try + { + alice.CreateRound2PayloadToSend(); + + Fail("failed to throw on round 2 creation twice"); + } + catch (InvalidOperationException) + { + // expected + } + + // create key before validation round 2 + try + { + alice.CalculateKeyingMaterial(); + + Fail("failed to throw on calculating keying material before round 2 validation"); + } + catch (InvalidOperationException) + { + // expected + } + + // validate round 3 before validating round 2 + try + { + alice.ValidateRound3PayloadReceived(null, null); + + Fail("failed to throw on validating round 3 before 2"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); + alice.ValidateRound2PayloadReceived(bobRound2Payload); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_2_VALIDATED, alice.State); + + // validate round 2 payload twice + try + { + alice.ValidateRound2PayloadReceived(bobRound2Payload); + + Fail("failed to throw on validating round 2 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound2PayloadReceived(aliceRound2Payload); + + // create round 3 before calculating key + try + { + alice.CreateRound3PayloadToSend(BigInteger.One); + + Fail("failed to throw on creating round 3 before calculating key aterial"); + } + catch (InvalidOperationException) + { + // expected + } + + // START KEY CALCULATION CHECKS + + BigInteger aliceKeyingMaterial = alice.CalculateKeyingMaterial(); + Assert.AreEqual(JPakeParticipant.STATE_KEY_CALCULATED, alice.State); + + // calculate key twice + try + { + alice.CalculateKeyingMaterial(); + + Fail("failed to throw on calculating key twice"); + } + catch (InvalidOperationException) + { + // expected + } + + BigInteger bobKeyingMaterial = bob.CalculateKeyingMaterial(); + + // START ROUND 3 CHECKS + + JPakeRound3Payload aliceRound3Payload = alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_CREATED, alice.State); + + // create round 3 payload twice + try + { + alice.CreateRound3PayloadToSend(aliceKeyingMaterial); + + Fail("failed to throw on creation round 3 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + JPakeRound3Payload bobRound3Payload = bob.CreateRound3PayloadToSend(bobKeyingMaterial); + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + Assert.AreEqual(JPakeParticipant.STATE_ROUND_3_VALIDATED, alice.State); + + // validate round 3 payload twice + try + { + alice.ValidateRound3PayloadReceived(bobRound3Payload, aliceKeyingMaterial); + + Fail("failed to throw on validation round 3 twice"); + } + catch (InvalidOperationException) + { + // expected + } + + bob.ValidateRound3PayloadReceived(aliceRound3Payload, bobKeyingMaterial); + } + + public void TestValidateRound1PayloadReceived() + { + // We're testing alice here. Bob is just used for help. + + JPakeRound1Payload bobRound1Payload = CreateBob().CreateRound1PayloadToSend(); + + // should succeed + CreateAlice().ValidateRound1PayloadReceived(bobRound1Payload); + + // alice verifies alice's payload + try + { + JPakeParticipant alice = CreateAlice(); + alice.ValidateRound1PayloadReceived(alice.CreateRound1PayloadToSend()); + + Fail("failed to throw on participant validating own payload"); + } + catch (CryptoException) + { + // expected + } + + // g^x4 == 1 + try + { + CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + BigInteger.One, + bobRound1Payload.KnowledgeProofForX1, + bobRound1Payload.KnowledgeProofForX2)); + + Fail("failed to throw on g^x4 == 1"); + } + catch (CryptoException) + { + // expected + } + + // zero knowledge proof for x3 fails + try + { + JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); + CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + bobRound1Payload.Gx2, + bobRound1Payload2.KnowledgeProofForX1, + bobRound1Payload.KnowledgeProofForX2)); + + Fail("failed to throw on incorrect zero knowledge proof for x3"); + } + catch (CryptoException) + { + // expected + } + + // zero knowledge proof for x4 fails + try + { + JPakeRound1Payload bobRound1Payload2 = CreateBob().CreateRound1PayloadToSend(); + CreateAlice().ValidateRound1PayloadReceived(new JPakeRound1Payload( + bobRound1Payload.ParticipantId, + bobRound1Payload.Gx1, + bobRound1Payload.Gx2, + bobRound1Payload.KnowledgeProofForX1, + bobRound1Payload2.KnowledgeProofForX2)); + + Fail("failed to throw on incorrect zero knowledge proof for x4"); + } + catch (CryptoException) + { + // expected + } + } + + public void TestValidateRound2PayloadReceived() + { + // We're testing alice here. Bob is just used for help. + + // should succeed + ExchangeAfterRound2Creation exchange1 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + exchange1.alice.ValidateRound2PayloadReceived(exchange1.bobRound2Payload); + + // alice verified alice's payload + ExchangeAfterRound2Creation exchange2 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + try + { + exchange2.alice.ValidateRound2PayloadReceived(exchange2.aliceRound2Payload); + + Fail("failed to throw on participant verifying own payload 2"); + } + catch (CryptoException) + { + // expected + } + + // wrong z + ExchangeAfterRound2Creation exchange3 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + ExchangeAfterRound2Creation exchange4 = RunExchangeUntilRound2Creation(CreateAlice(), CreateBob()); + try + { + exchange3.alice.ValidateRound2PayloadReceived(exchange4.bobRound2Payload); + + Fail("failed to throw on wrong z"); + } + catch (CryptoException) + { + // expected + } + } + + private class ExchangeAfterRound2Creation + { + public JPakeParticipant alice; + public JPakeRound2Payload aliceRound2Payload; + public JPakeRound2Payload bobRound2Payload; + + public ExchangeAfterRound2Creation( + JPakeParticipant alice, + JPakeRound2Payload aliceRound2Payload, + JPakeRound2Payload bobRound2Payload) + { + this.alice = alice; + this.aliceRound2Payload = aliceRound2Payload; + this.bobRound2Payload = bobRound2Payload; + } + } + + private ExchangeAfterRound2Creation RunExchangeUntilRound2Creation(JPakeParticipant alice, JPakeParticipant bob) + { + JPakeRound1Payload aliceRound1Payload = alice.CreateRound1PayloadToSend(); + JPakeRound1Payload bobRound1Payload = bob.CreateRound1PayloadToSend(); + + alice.ValidateRound1PayloadReceived(bobRound1Payload); + bob.ValidateRound1PayloadReceived(aliceRound1Payload); + + JPakeRound2Payload aliceRound2Payload = alice.CreateRound2PayloadToSend(); + JPakeRound2Payload bobRound2Payload = bob.CreateRound2PayloadToSend(); + + return new ExchangeAfterRound2Creation( + alice, + aliceRound2Payload, + bobRound2Payload); + } + + private JPakeParticipant CreateAlice() + { + return CreateParticipant("alice", "password"); + } + + private JPakeParticipant CreateBob() + { + return CreateParticipant("bob", "password"); + } + + private JPakeParticipant CreateBobWithWrongPassword() + { + return CreateParticipant("bob", "wrong"); + } + + private JPakeParticipant CreateParticipant(string participantId, string password) + { + return new JPakeParticipant( + participantId, + password.ToCharArray(), + JPakePrimeOrderGroups.SUN_JCE_1024); + } + } +} -- cgit 1.5.1 From 9f176fcdc0580b408b734003c021fae32b644b4f Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 17 Oct 2015 15:22:44 +0700 Subject: Fix various warnings from recent commits --- .../src/crypto/agreement/jpake/JPakeParticipant.cs | 1 + .../agreement/jpake/JPakePrimeOrderGroups.cs | 4 +-- .../src/crypto/agreement/jpake/JPakeUtilities.cs | 1 - crypto/src/crypto/tls/TlsClientProtocol.cs | 12 ++++---- crypto/src/crypto/tls/TlsProtocol.cs | 36 +++++++++++----------- crypto/src/crypto/tls/TlsServerProtocol.cs | 12 ++++---- .../crypto/tls/test/TlsProtocolNonBlockingTest.cs | 4 +-- 7 files changed, 35 insertions(+), 35 deletions(-) (limited to 'crypto') diff --git a/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs index f8ca2cd41..794284866 100755 --- a/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs +++ b/crypto/src/crypto/agreement/jpake/JPakeParticipant.cs @@ -421,6 +421,7 @@ namespace Org.BouncyCastle.Crypto.Agreement.JPake /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to /// CalculateKeyingMaterial or multiple times /// + /// The round 3 payload received from the other participant. /// The keying material as returned from CalculateKeyingMaterial(). public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial) { diff --git a/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs index 33640845a..192cd2b51 100755 --- a/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs +++ b/crypto/src/crypto/agreement/jpake/JPakePrimeOrderGroups.cs @@ -5,10 +5,10 @@ namespace Org.BouncyCastle.Crypto.Agreement.JPake /// /// Standard pre-computed prime order groups for use by J-PAKE. /// (J-PAKE can use pre-computed prime order groups, same as DSA and Diffie-Hellman.) - ///

+ ///

/// This class contains some convenient constants for use as input for /// constructing {@link JPAKEParticipant}s. - ///

+ ///

/// The prime order groups below are taken from Sun's JDK JavaDoc (docs/guide/security/CryptoSpec.html#AppB), /// and from the prime order groups /// published by NIST. diff --git a/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs b/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs index eaf8af676..b23518a8c 100644 --- a/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs +++ b/crypto/src/crypto/agreement/jpake/JPakeUtilities.cs @@ -310,7 +310,6 @@ namespace Org.BouncyCastle.Crypto.Agreement.JPake /// /// throws CryptoException if the participantId strings are equal. ///

- /// the MacTag received from the partner public static void ValidateMacTag(string participantId, string partnerParticipantId, BigInteger gx1, BigInteger gx2, BigInteger gx3, BigInteger gx4, BigInteger keyingMaterial, IDigest digest, BigInteger partnerMacTag) diff --git a/crypto/src/crypto/tls/TlsClientProtocol.cs b/crypto/src/crypto/tls/TlsClientProtocol.cs index 14c1cf4a4..c2447e434 100644 --- a/crypto/src/crypto/tls/TlsClientProtocol.cs +++ b/crypto/src/crypto/tls/TlsClientProtocol.cs @@ -43,12 +43,12 @@ namespace Org.BouncyCastle.Crypto.Tls } /** - * Constructor for non-blocking mode.
- *
+ * Constructor for non-blocking mode.
+ *
* When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to * provide the received ciphertext, then use - * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
- *
+ * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
+ *
* Similarly, when data needs to be sent, use * {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use * {@link #readOutput(byte[], int, int)} to get the corresponding @@ -63,8 +63,8 @@ namespace Org.BouncyCastle.Crypto.Tls } /** - * Initiates a TLS handshake in the role of client.
- *
+ * Initiates a TLS handshake in the role of client.
+ *
* In blocking mode, this will not return until the handshake is complete. * In non-blocking mode, use {@link TlsPeer#NotifyHandshakeComplete()} to * receive a callback when the handshake is complete. diff --git a/crypto/src/crypto/tls/TlsProtocol.cs b/crypto/src/crypto/tls/TlsProtocol.cs index 7acc34d3c..fffde0b2b 100644 --- a/crypto/src/crypto/tls/TlsProtocol.cs +++ b/crypto/src/crypto/tls/TlsProtocol.cs @@ -604,15 +604,15 @@ namespace Org.BouncyCastle.Crypto.Tls } /** - * Offer input from an arbitrary source. Only allowed in non-blocking mode.
- *
+ * Offer input from an arbitrary source. Only allowed in non-blocking mode.
+ *
* After this method returns, the input buffer is "owned" by this object. Other code - * must not attempt to do anything with it.
- *
+ * must not attempt to do anything with it.
+ *
* This method will decrypt and process all records that are fully available. * If only part of a record is available, the buffer will be retained until the - * remainder of the record is offered.
- *
+ * remainder of the record is offered.
+ *
* If any records containing application data were processed, the decrypted data * can be obtained using {@link #readInput(byte[], int, int)}. If any records * containing protocol data were processed, a response may have been generated. @@ -649,8 +649,8 @@ namespace Org.BouncyCastle.Crypto.Tls /** * Gets the amount of received application data. A call to {@link #readInput(byte[], int, int)} - * is guaranteed to be able to return at least this much data.
- *
+ * is guaranteed to be able to return at least this much data.
+ *
* Only allowed in non-blocking mode. * @return The number of bytes of available application data */ @@ -666,8 +666,8 @@ namespace Org.BouncyCastle.Crypto.Tls * Retrieves received application data. Use {@link #getAvailableInputBytes()} to check * how much application data is currently available. This method functions similarly to * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data - * is available, nothing will be copied and zero will be returned.
- *
+ * is available, nothing will be copied and zero will be returned.
+ *
* Only allowed in non-blocking mode. * @param buffer The buffer to hold the application data * @param offset The start offset in the buffer at which the data is written @@ -684,12 +684,12 @@ namespace Org.BouncyCastle.Crypto.Tls } /** - * Offer output from an arbitrary source. Only allowed in non-blocking mode.
- *
+ * Offer output from an arbitrary source. Only allowed in non-blocking mode.
+ *
* After this method returns, the specified section of the buffer will have been * processed. Use {@link #readOutput(byte[], int, int)} to get the bytes to - * transmit to the other peer.
- *
+ * transmit to the other peer.
+ *
* This method must not be called until after the handshake is complete! Attempting * to call it before the handshake is complete will result in an exception. * @param buffer The buffer containing application data to encrypt @@ -710,8 +710,8 @@ namespace Org.BouncyCastle.Crypto.Tls /** * Gets the amount of encrypted data available to be sent. A call to * {@link #readOutput(byte[], int, int)} is guaranteed to be able to return at - * least this much data.
- *
+ * least this much data.
+ *
* Only allowed in non-blocking mode. * @return The number of bytes of available encrypted data */ @@ -727,8 +727,8 @@ namespace Org.BouncyCastle.Crypto.Tls * Retrieves encrypted data to be sent. Use {@link #getAvailableOutputBytes()} to check * how much encrypted data is currently available. This method functions similarly to * {@link InputStream#read(byte[], int, int)}, except that it never blocks. If no data - * is available, nothing will be copied and zero will be returned.
- *
+ * is available, nothing will be copied and zero will be returned.
+ *
* Only allowed in non-blocking mode. * @param buffer The buffer to hold the encrypted data * @param offset The start offset in the buffer at which the data is written diff --git a/crypto/src/crypto/tls/TlsServerProtocol.cs b/crypto/src/crypto/tls/TlsServerProtocol.cs index 27f7a1dfd..4ab628b13 100644 --- a/crypto/src/crypto/tls/TlsServerProtocol.cs +++ b/crypto/src/crypto/tls/TlsServerProtocol.cs @@ -45,12 +45,12 @@ namespace Org.BouncyCastle.Crypto.Tls } /** - * Constructor for non-blocking mode.
- *
+ * Constructor for non-blocking mode.
+ *
* When data is received, use {@link #offerInput(java.nio.ByteBuffer)} to * provide the received ciphertext, then use - * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
- *
+ * {@link #readInput(byte[], int, int)} to read the corresponding cleartext.
+ *
* Similarly, when data needs to be sent, use * {@link #offerOutput(byte[], int, int)} to provide the cleartext, then use * {@link #readOutput(byte[], int, int)} to get the corresponding @@ -65,8 +65,8 @@ namespace Org.BouncyCastle.Crypto.Tls } /** - * Receives a TLS handshake in the role of server.
- *
+ * Receives a TLS handshake in the role of server.
+ *
* In blocking mode, this will not return until the handshake is complete. * In non-blocking mode, use {@link TlsPeer#notifyHandshakeComplete()} to * receive a callback when the handshake is complete. diff --git a/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs b/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs index 5fe0f32ad..477e287f1 100644 --- a/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs +++ b/crypto/test/src/crypto/tls/test/TlsProtocolNonBlockingTest.cs @@ -104,7 +104,7 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests protocol.OfferInput(new byte[10]); Assert.Fail("Input was accepted after close"); } - catch (IOException e) + catch (IOException) { } @@ -113,7 +113,7 @@ namespace Org.BouncyCastle.Crypto.Tls.Tests protocol.OfferOutput(new byte[10], 0, 10); Assert.Fail("Output was accepted after close"); } - catch (IOException e) + catch (IOException) { } } -- cgit 1.5.1 From a8866af2bf98dd3be651ae853ddf463a313e972a Mon Sep 17 00:00:00 2001 From: Peter Dettman Date: Sat, 17 Oct 2015 23:21:07 +0700 Subject: https://github.com/bcgit/bc-csharp/issues/37 - Add alternative PGP methods involving passphrases to support UTF8 or caller-defined encodings --- crypto/src/openpgp/PgpEncryptedDataGenerator.cs | 49 ++- crypto/src/openpgp/PgpKeyRingGenerator.cs | 158 +++++++++- crypto/src/openpgp/PgpPbeEncryptedData.cs | 37 ++- crypto/src/openpgp/PgpSecretKey.cs | 346 ++++++++++++++++++--- crypto/src/openpgp/PgpUtilities.cs | 50 ++- .../test/src/openpgp/examples/ByteArrayHandler.cs | 2 +- .../test/src/openpgp/examples/PbeFileProcessor.cs | 2 +- crypto/test/src/openpgp/test/PGPPBETest.cs | 10 +- crypto/test/src/openpgp/test/PGPRSATest.cs | 2 +- crypto/test/src/openpgp/test/PgpKeyRingTest.cs | 6 +- crypto/test/src/openpgp/test/PgpUnicodeTest.cs | 15 +- 11 files changed, 588 insertions(+), 89 deletions(-) (limited to 'crypto') diff --git a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs index 2a2e63961..06868eab1 100644 --- a/crypto/src/openpgp/PgpEncryptedDataGenerator.cs +++ b/crypto/src/openpgp/PgpEncryptedDataGenerator.cs @@ -271,26 +271,55 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// /// Add a PBE encryption method to the encrypted object using the default algorithm (S2K_SHA1). /// - public void AddMethod( - char[] passPhrase) + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + [Obsolete("Use version that takes an explicit s2kDigest parameter")] + public void AddMethod(char[] passPhrase) { AddMethod(passPhrase, HashAlgorithmTag.Sha1); } - /// Add a PBE encryption method to the encrypted object. - public void AddMethod( - char[] passPhrase, - HashAlgorithmTag s2kDigest) + /// Add a PBE encryption method to the encrypted object. + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public void AddMethod(char[] passPhrase, HashAlgorithmTag s2kDigest) + { + DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, false), true, s2kDigest); + } + + /// Add a PBE encryption method to the encrypted object. + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public void AddMethodUtf8(char[] passPhrase, HashAlgorithmTag s2kDigest) + { + DoAddMethod(PgpUtilities.EncodePassPhrase(passPhrase, true), true, s2kDigest); + } + + /// Add a PBE encryption method to the encrypted object. + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public void AddMethodRaw(byte[] rawPassPhrase, HashAlgorithmTag s2kDigest) + { + DoAddMethod(rawPassPhrase, false, s2kDigest); + } + + internal void DoAddMethod(byte[] rawPassPhrase, bool clearPassPhrase, HashAlgorithmTag s2kDigest) { byte[] iv = new byte[8]; - rand.NextBytes(iv); + rand.NextBytes(iv); - S2k s2k = new S2k(s2kDigest, iv, 0x60); + S2k s2k = new S2k(s2kDigest, iv, 0x60); - methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.MakeKeyFromPassPhrase(defAlgorithm, s2k, passPhrase))); + methods.Add(new PbeMethod(defAlgorithm, s2k, PgpUtilities.DoMakeKeyFromPassPhrase(defAlgorithm, s2k, rawPassPhrase, clearPassPhrase))); } - /// Add a public key encrypted session key to the encrypted object. + /// Add a public key encrypted session key to the encrypted object. public void AddMethod( PgpPublicKey key) { diff --git a/crypto/src/openpgp/PgpKeyRingGenerator.cs b/crypto/src/openpgp/PgpKeyRingGenerator.cs index efeea9d5b..4f6a4b12f 100644 --- a/crypto/src/openpgp/PgpKeyRingGenerator.cs +++ b/crypto/src/openpgp/PgpKeyRingGenerator.cs @@ -17,7 +17,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp private SymmetricKeyAlgorithmTag encAlgorithm; private HashAlgorithmTag hashAlgorithm; private int certificationLevel; - private char[] passPhrase; + private byte[] rawPassPhrase; private bool useSha1; private PgpKeyPair masterKey; private PgpSignatureSubpacketVector hashedPacketVector; @@ -28,7 +28,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// Create a new key ring generator using old style checksumming. It is recommended to use /// SHA1 checksumming where possible. /// - /// The certification level for keys on this ring. + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + /// The certification level for keys on this ring. /// The master key pair. /// The id to be associated with the ring. /// The algorithm to be used to protect secret keys. @@ -36,6 +40,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// Packets to be included in the certification hash. /// Packets to be attached unhashed to the certification. /// input secured random. + [Obsolete("Use version taking an explicit 'useSha1' parameter instead")] public PgpKeyRingGenerator( int certificationLevel, PgpKeyPair masterKey, @@ -52,7 +57,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// /// Create a new key ring generator. /// - /// The certification level for keys on this ring. + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + /// The certification level for keys on this ring. /// The master key pair. /// The id to be associated with the ring. /// The algorithm to be used to protect secret keys. @@ -71,23 +80,86 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PgpSignatureSubpacketVector hashedPackets, PgpSignatureSubpacketVector unhashedPackets, SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// + /// If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + bool utf8PassPhrase, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) { this.certificationLevel = certificationLevel; this.masterKey = masterKey; this.id = id; this.encAlgorithm = encAlgorithm; - this.passPhrase = passPhrase; + this.rawPassPhrase = rawPassPhrase; this.useSha1 = useSha1; this.hashedPacketVector = hashedPackets; this.unhashedPacketVector = unhashedPackets; this.rand = rand; - keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)); + keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand)); } /// /// Create a new key ring generator. /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// /// The certification level for keys on this ring. /// The master key pair. /// The id to be associated with the ring. @@ -109,19 +181,85 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PgpSignatureSubpacketVector hashedPackets, PgpSignatureSubpacketVector unhashedPackets, SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The hash algorithm. + /// + /// If true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + bool utf8PassPhrase, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Create a new key ring generator. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + /// The certification level for keys on this ring. + /// The master key pair. + /// The id to be associated with the ring. + /// The algorithm to be used to protect secret keys. + /// The hash algorithm. + /// The passPhrase to be used to protect secret keys. + /// Checksum the secret keys with SHA1 rather than the older 16 bit checksum. + /// Packets to be included in the certification hash. + /// Packets to be attached unhashed to the certification. + /// input secured random. + public PgpKeyRingGenerator( + int certificationLevel, + PgpKeyPair masterKey, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) { this.certificationLevel = certificationLevel; this.masterKey = masterKey; this.id = id; this.encAlgorithm = encAlgorithm; - this.passPhrase = passPhrase; + this.rawPassPhrase = rawPassPhrase; this.useSha1 = useSha1; this.hashedPacketVector = hashedPackets; this.unhashedPacketVector = unhashedPackets; this.rand = rand; this.hashAlgorithm = hashAlgorithm; - keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, passPhrase, useSha1, hashedPackets, unhashedPackets, rand)); + keys.Add(new PgpSecretKey(certificationLevel, masterKey, id, encAlgorithm, hashAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand)); } /// Add a subkey to the key ring to be generated with default certification. @@ -172,7 +310,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey)); - keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, passPhrase, useSha1, rand)); + keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, + rawPassPhrase, false, useSha1, rand, false)); } catch (PgpException e) { @@ -215,7 +354,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp IList subSigs = Platform.CreateArrayList(); subSigs.Add(sGen.GenerateCertification(masterKey.PublicKey, keyPair.PublicKey)); - keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, passPhrase, useSha1, rand)); + keys.Add(new PgpSecretKey(keyPair.PrivateKey, new PgpPublicKey(keyPair.PublicKey, null, subSigs), encAlgorithm, + rawPassPhrase, false, useSha1, rand, false)); } catch (PgpException) { diff --git a/crypto/src/openpgp/PgpPbeEncryptedData.cs b/crypto/src/openpgp/PgpPbeEncryptedData.cs index c5fe89407..f43f2f512 100644 --- a/crypto/src/openpgp/PgpPbeEncryptedData.cs +++ b/crypto/src/openpgp/PgpPbeEncryptedData.cs @@ -30,18 +30,43 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } /// Return the decrypted input stream, using the passed in passphrase. - public Stream GetDataStream( - char[] passPhrase) + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public Stream GetDataStream(char[] passPhrase) + { + return DoGetDataStream(PgpUtilities.EncodePassPhrase(passPhrase, false), true); + } + + /// Return the decrypted input stream, using the passed in passphrase. + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public Stream GetDataStreamUtf8(char[] passPhrase) + { + return DoGetDataStream(PgpUtilities.EncodePassPhrase(passPhrase, true), true); + } + + /// Return the decrypted input stream, using the passed in passphrase. + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public Stream GetDataStreamRaw(byte[] rawPassPhrase) + { + return DoGetDataStream(rawPassPhrase, false); + } + + internal Stream DoGetDataStream(byte[] rawPassPhrase, bool clearPassPhrase) { try { SymmetricKeyAlgorithmTag keyAlgorithm = keyData.EncAlgorithm; - KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase( - keyAlgorithm, keyData.S2k, passPhrase); - + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase( + keyAlgorithm, keyData.S2k, rawPassPhrase, clearPassPhrase); - byte[] secKeyData = keyData.GetSecKeyData(); + byte[] secKeyData = keyData.GetSecKeyData(); if (secKeyData != null && secKeyData.Length > 0) { IBufferedCipher keyCipher = CipherUtilities.GetCipher( diff --git a/crypto/src/openpgp/PgpSecretKey.cs b/crypto/src/openpgp/PgpSecretKey.cs index 1027393ce..0f472c1a4 100644 --- a/crypto/src/openpgp/PgpSecretKey.cs +++ b/crypto/src/openpgp/PgpSecretKey.cs @@ -30,18 +30,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PgpPrivateKey privKey, PgpPublicKey pubKey, SymmetricKeyAlgorithmTag encAlgorithm, - char[] passPhrase, - bool useSha1, - SecureRandom rand) - : this(privKey, pubKey, encAlgorithm, passPhrase, useSha1, rand, false) - { - } - - internal PgpSecretKey( - PgpPrivateKey privKey, - PgpPublicKey pubKey, - SymmetricKeyAlgorithmTag encAlgorithm, - char[] passPhrase, + byte[] rawPassPhrase, + bool clearPassPhrase, bool useSha1, SecureRandom rand, bool isMasterKey) @@ -107,7 +97,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp byte[] encData; if (pub.Version >= 4) { - encData = EncryptKeyData(keyData, encAlgorithm, passPhrase, rand, out s2k, out iv); + encData = EncryptKeyData(keyData, encAlgorithm, rawPassPhrase, clearPassPhrase, rand, out s2k, out iv); } else { @@ -139,6 +129,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } } + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + [Obsolete("Use the constructor taking an explicit 'useSha1' parameter instead")] public PgpSecretKey( int certificationLevel, PgpKeyPair keyPair, @@ -152,32 +147,151 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp { } + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// public PgpSecretKey( int certificationLevel, PgpKeyPair keyPair, string id, SymmetricKeyAlgorithmTag encAlgorithm, + bool utf8PassPhrase, char[] passPhrase, bool useSha1, PgpSignatureSubpacketVector hashedPackets, PgpSignatureSubpacketVector unhashedPackets, SecureRandom rand) - : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), encAlgorithm, passPhrase, useSha1, rand, true) + : this(certificationLevel, keyPair, id, encAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), true, + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + internal PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets), + encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true) + { + } + + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + char[] passPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, false, passPhrase, useSha1, hashedPackets, unhashedPackets, rand) { } + /// + /// If utf8PassPhrase is true, conversion of the passphrase to bytes uses Encoding.UTF8.GetBytes(), otherwise the conversion + /// is performed using Convert.ToByte(), which is the historical behaviour of the library (1.7 and earlier). + /// public PgpSecretKey( int certificationLevel, PgpKeyPair keyPair, string id, SymmetricKeyAlgorithmTag encAlgorithm, HashAlgorithmTag hashAlgorithm, + bool utf8PassPhrase, char[] passPhrase, bool useSha1, PgpSignatureSubpacketVector hashedPackets, PgpSignatureSubpacketVector unhashedPackets, SecureRandom rand) - : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm), encAlgorithm, passPhrase, useSha1, rand, true) + : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, + PgpUtilities.EncodePassPhrase(passPhrase, utf8PassPhrase), true, + useSha1, hashedPackets, unhashedPackets, rand) + { + } + + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(certificationLevel, keyPair, id, encAlgorithm, hashAlgorithm, rawPassPhrase, false, useSha1, hashedPackets, unhashedPackets, rand) + { + } + + internal PgpSecretKey( + int certificationLevel, + PgpKeyPair keyPair, + string id, + SymmetricKeyAlgorithmTag encAlgorithm, + HashAlgorithmTag hashAlgorithm, + byte[] rawPassPhrase, + bool clearPassPhrase, + bool useSha1, + PgpSignatureSubpacketVector hashedPackets, + PgpSignatureSubpacketVector unhashedPackets, + SecureRandom rand) + : this(keyPair.PrivateKey, CertifiedPublicKey(certificationLevel, keyPair, id, hashedPackets, unhashedPackets, hashAlgorithm), + encAlgorithm, rawPassPhrase, clearPassPhrase, useSha1, rand, true) { } @@ -269,7 +383,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp SecureRandom rand) : this(certificationLevel, new PgpKeyPair(algorithm, pubKey, privKey, time), - id, encAlgorithm, passPhrase, hashedPackets, unhashedPackets, rand) + id, encAlgorithm, passPhrase, false, hashedPackets, unhashedPackets, rand) { } @@ -367,8 +481,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp get { return pub.GetUserAttributes(); } } - private byte[] ExtractKeyData( - char[] passPhrase) + private byte[] ExtractKeyData(byte[] rawPassPhrase, bool clearPassPhrase) { SymmetricKeyAlgorithmTag encAlgorithm = secret.EncAlgorithm; byte[] encData = secret.GetSecretKeyData(); @@ -380,7 +493,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp // TODO Factor this block out as 'decryptData' try { - KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, passPhrase); + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(secret.EncAlgorithm, secret.S2k, rawPassPhrase, clearPassPhrase); byte[] iv = secret.GetIV(); byte[] data; @@ -483,8 +596,34 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } /// Extract a PgpPrivateKey from this secret key's encrypted contents. - public PgpPrivateKey ExtractPrivateKey( - char[] passPhrase) + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public PgpPrivateKey ExtractPrivateKey(char[] passPhrase) + { + return DoExtractPrivateKey(PgpUtilities.EncodePassPhrase(passPhrase, false), true); + } + + /// Extract a PgpPrivateKey from this secret key's encrypted contents. + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public PgpPrivateKey ExtractPrivateKeyUtf8(char[] passPhrase) + { + return DoExtractPrivateKey(PgpUtilities.EncodePassPhrase(passPhrase, true), true); + } + + /// Extract a PgpPrivateKey from this secret key's encrypted contents. + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public PgpPrivateKey ExtractPrivateKeyRaw(byte[] rawPassPhrase) + { + return DoExtractPrivateKey(rawPassPhrase, false); + } + + internal PgpPrivateKey DoExtractPrivateKey(byte[] rawPassPhrase, bool clearPassPhrase) { if (IsPrivateKeyEmpty) return null; @@ -492,7 +631,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp PublicKeyPacket pubPk = secret.PublicKeyPacket; try { - byte[] data = ExtractKeyData(passPhrase); + byte[] data = ExtractKeyData(rawPassPhrase, clearPassPhrase); BcpgInputStream bcpgIn = BcpgInputStream.Wrap(new MemoryStream(data, false)); AsymmetricKeyParameter privateKey; switch (pubPk.Algorithm) @@ -652,6 +791,10 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp /// Return a copy of the passed in secret key, encrypted using a new password /// and the passed in algorithm. /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// /// The PgpSecretKey to be copied. /// The current password for the key. /// The new password for the key. @@ -664,11 +807,67 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp SymmetricKeyAlgorithmTag newEncAlgorithm, SecureRandom rand) { + return DoCopyWithNewPassword(key, PgpUtilities.EncodePassPhrase(oldPassPhrase, false), + PgpUtilities.EncodePassPhrase(newPassPhrase, false), true, newEncAlgorithm, rand); + } + + /// + /// Return a copy of the passed in secret key, encrypted using a new password + /// and the passed in algorithm. + /// + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + /// The PgpSecretKey to be copied. + /// The current password for the key. + /// The new password for the key. + /// The algorithm to be used for the encryption. + /// Source of randomness. + public static PgpSecretKey CopyWithNewPasswordUtf8( + PgpSecretKey key, + char[] oldPassPhrase, + char[] newPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + return DoCopyWithNewPassword(key, PgpUtilities.EncodePassPhrase(oldPassPhrase, true), + PgpUtilities.EncodePassPhrase(newPassPhrase, true), true, newEncAlgorithm, rand); + } + + /// + /// Return a copy of the passed in secret key, encrypted using a new password + /// and the passed in algorithm. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + /// The PgpSecretKey to be copied. + /// The current password for the key. + /// The new password for the key. + /// The algorithm to be used for the encryption. + /// Source of randomness. + public static PgpSecretKey CopyWithNewPasswordRaw( + PgpSecretKey key, + byte[] rawOldPassPhrase, + byte[] rawNewPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { + return DoCopyWithNewPassword(key, rawOldPassPhrase, rawNewPassPhrase, false, newEncAlgorithm, rand); + } + internal static PgpSecretKey DoCopyWithNewPassword( + PgpSecretKey key, + byte[] rawOldPassPhrase, + byte[] rawNewPassPhrase, + bool clearPassPhrase, + SymmetricKeyAlgorithmTag newEncAlgorithm, + SecureRandom rand) + { if (key.IsPrivateKeyEmpty) throw new PgpException("no private key in this SecretKey - public key present only."); - byte[] rawKeyData = key.ExtractKeyData(oldPassPhrase); + byte[] rawKeyData = key.ExtractKeyData(rawOldPassPhrase, clearPassPhrase); int s2kUsage = key.secret.S2kUsage; byte[] iv = null; S2k s2k = null; @@ -696,11 +895,16 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } else { + if (s2kUsage == SecretKeyPacket.UsageNone) + { + s2kUsage = SecretKeyPacket.UsageChecksum; + } + try { if (pubKeyPacket.Version >= 4) { - keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, newPassPhrase, rand, out s2k, out iv); + keyData = EncryptKeyData(rawKeyData, newEncAlgorithm, rawNewPassPhrase, clearPassPhrase, rand, out s2k, out iv); } else { @@ -749,7 +953,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp private static byte[] EncryptKeyData( byte[] rawKeyData, SymmetricKeyAlgorithmTag encAlgorithm, - char[] passPhrase, + byte[] rawPassPhrase, + bool clearPassPhrase, SecureRandom random, out S2k s2k, out byte[] iv) @@ -769,7 +974,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp random.NextBytes(s2kIV); s2k = new S2k(HashAlgorithmTag.Sha1, s2kIV, 0x60); - KeyParameter kp = PgpUtilities.MakeKeyFromPassPhrase(encAlgorithm, s2k, passPhrase); + KeyParameter kp = PgpUtilities.DoMakeKeyFromPassPhrase(encAlgorithm, s2k, rawPassPhrase, clearPassPhrase); iv = new byte[c.GetBlockSize()]; random.NextBytes(iv); @@ -779,12 +984,41 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return c.DoFinal(rawKeyData); } - /** - * Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. - * - * @return a secret key object. - */ + /// + /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true, pubKey); + } + + /// + /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + /// + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase, PgpPublicKey pubKey) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true, pubKey); + } + + /// + /// Parse a secret key from one of the GPG S expression keys associating it with the passed in public key. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase, PgpPublicKey pubKey) + { + return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false, pubKey); + } + + internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, PgpPublicKey pubKey) { SXprUtilities.SkipOpenParenthesis(inputStream); @@ -826,7 +1060,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp SXprUtilities.SkipCloseParenthesis(inputStream); - byte[] dValue = GetDValue(inputStream, passPhrase, curveName); + byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); // TODO: check SHA-1 hash. return new PgpSecretKey(new SecretKeyPacket(pubKey.PublicKeyPacket, SymmetricKeyAlgorithmTag.Null, null, null, @@ -836,12 +1070,44 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new PgpException("unknown key type found"); } - /** - * Parse a secret key from one of the GPG S expression keys. - * - * @return a secret key object. - */ + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// public static PgpSecretKey ParseSecretKeyFromSExpr(Stream inputStream, char[] passPhrase) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, false), true); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public static PgpSecretKey ParseSecretKeyFromSExprUtf8(Stream inputStream, char[] passPhrase) + { + return DoParseSecretKeyFromSExpr(inputStream, PgpUtilities.EncodePassPhrase(passPhrase, true), true); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public static PgpSecretKey ParseSecretKeyFromSExprRaw(Stream inputStream, byte[] rawPassPhrase) + { + return DoParseSecretKeyFromSExpr(inputStream, rawPassPhrase, false); + } + + /// + /// Parse a secret key from one of the GPG S expression keys. + /// + internal static PgpSecretKey DoParseSecretKeyFromSExpr(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase) { SXprUtilities.SkipOpenParenthesis(inputStream); @@ -891,7 +1157,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp SXprUtilities.SkipCloseParenthesis(inputStream); - byte[] dValue = GetDValue(inputStream, passPhrase, curveName); + byte[] dValue = GetDValue(inputStream, rawPassPhrase, clearPassPhrase, curveName); // TODO: check SHA-1 hash. return new PgpSecretKey(new SecretKeyPacket(pubPacket, SymmetricKeyAlgorithmTag.Null, null, null, @@ -901,7 +1167,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp throw new PgpException("unknown key type found"); } - private static byte[] GetDValue(Stream inputStream, char[] passPhrase, string curveName) + private static byte[] GetDValue(Stream inputStream, byte[] rawPassPhrase, bool clearPassPhrase, string curveName) { string type; SXprUtilities.SkipOpenParenthesis(inputStream); @@ -932,7 +1198,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp } // TODO: recognise other algorithms - KeyParameter key = PgpUtilities.MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, passPhrase); + KeyParameter key = PgpUtilities.DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag.Aes128, s2k, rawPassPhrase, clearPassPhrase); byte[] data = RecoverKeyData(SymmetricKeyAlgorithmTag.Aes128, "/CBC/NoPadding", key, iv, secKeyData, 0, secKeyData.Length); diff --git a/crypto/src/openpgp/PgpUtilities.cs b/crypto/src/openpgp/PgpUtilities.cs index e4551db07..65c07b2e2 100644 --- a/crypto/src/openpgp/PgpUtilities.cs +++ b/crypto/src/openpgp/PgpUtilities.cs @@ -193,13 +193,44 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp return MakeKey(algorithm, keyBytes); } - public static KeyParameter MakeKeyFromPassPhrase( - SymmetricKeyAlgorithmTag algorithm, - S2k s2k, - char[] passPhrase) + internal static byte[] EncodePassPhrase(char[] passPhrase, bool utf8) + { + return passPhrase == null + ? null + : utf8 + ? Encoding.UTF8.GetBytes(passPhrase) + : Strings.ToByteArray(passPhrase); + } + + /// + /// Conversion of the passphrase characters to bytes is performed using Convert.ToByte(), which is + /// the historical behaviour of the library (1.7 and earlier). + /// + public static KeyParameter MakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase) + { + return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, false), true); + } + + /// + /// The passphrase is encoded to bytes using UTF8 (Encoding.UTF8.GetBytes). + /// + public static KeyParameter MakeKeyFromPassPhraseUtf8(SymmetricKeyAlgorithmTag algorithm, S2k s2k, char[] passPhrase) + { + return DoMakeKeyFromPassPhrase(algorithm, s2k, EncodePassPhrase(passPhrase, true), true); + } + + /// + /// Allows the caller to handle the encoding of the passphrase to bytes. + /// + public static KeyParameter MakeKeyFromPassPhraseRaw(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase) + { + return DoMakeKeyFromPassPhrase(algorithm, s2k, rawPassPhrase, false); + } + + internal static KeyParameter DoMakeKeyFromPassPhrase(SymmetricKeyAlgorithmTag algorithm, S2k s2k, byte[] rawPassPhrase, bool clearPassPhrase) { int keySize = GetKeySize(algorithm); - byte[] pBytes = Encoding.UTF8.GetBytes(passPhrase); + byte[] pBytes = rawPassPhrase; byte[] keyBytes = new byte[(keySize + 7) / 8]; int generatedBytes = 0; @@ -308,12 +339,15 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp loopCount++; } - Array.Clear(pBytes, 0, pBytes.Length); + if (clearPassPhrase && rawPassPhrase != null) + { + Array.Clear(rawPassPhrase, 0, rawPassPhrase.Length); + } - return MakeKey(algorithm, keyBytes); + return MakeKey(algorithm, keyBytes); } - /// Write out the passed in file as a literal data packet. + /// Write out the passed in file as a literal data packet. public static void WriteFileToLiteralData( Stream output, char fileType, diff --git a/crypto/test/src/openpgp/examples/ByteArrayHandler.cs b/crypto/test/src/openpgp/examples/ByteArrayHandler.cs index 676db8766..b5098ff66 100644 --- a/crypto/test/src/openpgp/examples/ByteArrayHandler.cs +++ b/crypto/test/src/openpgp/examples/ByteArrayHandler.cs @@ -123,7 +123,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Examples } PgpEncryptedDataGenerator encGen = new PgpEncryptedDataGenerator(algorithm, new SecureRandom()); - encGen.AddMethod(passPhrase); + encGen.AddMethod(passPhrase, HashAlgorithmTag.Sha1); Stream encOut = encGen.Open(output, compressedData.Length); diff --git a/crypto/test/src/openpgp/examples/PbeFileProcessor.cs b/crypto/test/src/openpgp/examples/PbeFileProcessor.cs index 66b1cc4ed..961704407 100644 --- a/crypto/test/src/openpgp/examples/PbeFileProcessor.cs +++ b/crypto/test/src/openpgp/examples/PbeFileProcessor.cs @@ -127,7 +127,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Examples PgpEncryptedDataGenerator encGen = new PgpEncryptedDataGenerator( SymmetricKeyAlgorithmTag.Cast5, withIntegrityCheck, new SecureRandom()); - encGen.AddMethod(passPhrase); + encGen.AddMethod(passPhrase, HashAlgorithmTag.Sha1); Stream encOut = encGen.Open(outputStream, compressedData.Length); diff --git a/crypto/test/src/openpgp/test/PGPPBETest.cs b/crypto/test/src/openpgp/test/PGPPBETest.cs index 621cef684..29b786a83 100644 --- a/crypto/test/src/openpgp/test/PGPPBETest.cs +++ b/crypto/test/src/openpgp/test/PGPPBETest.cs @@ -168,7 +168,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator( SymmetricKeyAlgorithmTag.Cast5, new SecureRandom()); - cPk.AddMethod(pass); + cPk.AddMethod(pass, HashAlgorithmTag.Sha1); byte[] bOutData = bOut.ToArray(); Stream cOut = cPk.Open(new UncloseableStream(cbOut), bOutData.Length); @@ -188,7 +188,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests cPk = new PgpEncryptedDataGenerator( SymmetricKeyAlgorithmTag.Cast5, new SecureRandom()); - cPk.AddMethod(pass); + cPk.AddMethod(pass, HashAlgorithmTag.Sha1); bOutData = bOut.ToArray(); cOut = cPk.Open(new UncloseableStream(cbOut), bOutData.Length); @@ -233,7 +233,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests cPk = new PgpEncryptedDataGenerator( SymmetricKeyAlgorithmTag.Cast5, rand); - cPk.AddMethod(pass); + cPk.AddMethod(pass, HashAlgorithmTag.Sha1); cOut = cPk.Open(new UncloseableStream(cbOut), new byte[16]); { @@ -256,7 +256,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests cPk = new PgpEncryptedDataGenerator( SymmetricKeyAlgorithmTag.Cast5, true, rand); - cPk.AddMethod(pass); + cPk.AddMethod(pass, HashAlgorithmTag.Sha1); cOut = cPk.Open(new UncloseableStream(cbOut), new byte[16]); bOutData = bOut.ToArray(); @@ -328,7 +328,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests cbOut = new MemoryStream(); cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, true, rand); - cPk.AddMethod(pass); + cPk.AddMethod(pass, HashAlgorithmTag.Sha1); cOut = cPk.Open(new UncloseableStream(cbOut), new byte[16]); diff --git a/crypto/test/src/openpgp/test/PGPRSATest.cs b/crypto/test/src/openpgp/test/PGPRSATest.cs index 35f844483..82b569bbb 100644 --- a/crypto/test/src/openpgp/test/PGPRSATest.cs +++ b/crypto/test/src/openpgp/test/PGPRSATest.cs @@ -381,7 +381,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests encGen.AddMethod(pgpPubKey); - encGen.AddMethod("password".ToCharArray()); + encGen.AddMethod("password".ToCharArray(), HashAlgorithmTag.Sha1); Stream cOut = encGen.Open(bcOut, bytes.Length); diff --git a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs index 9896c1ef6..43aef5afa 100644 --- a/crypto/test/src/openpgp/test/PgpKeyRingTest.cs +++ b/crypto/test/src/openpgp/test/PgpKeyRingTest.cs @@ -1844,7 +1844,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests PgpKeyPair elgKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ElGamalEncrypt, elgKp, DateTime.UtcNow); PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, dsaKeyPair, - "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, null, null, new SecureRandom()); + "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, false, null, null, new SecureRandom()); keyRingGen.AddSubKey(elgKeyPair); @@ -1904,12 +1904,12 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests PgpKeyPair rsaKeyPair2 = new PgpKeyPair(PublicKeyAlgorithmTag.RsaGeneral, rsaKp, DateTime.UtcNow); PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, - rsaKeyPair1, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, null, null, random); + rsaKeyPair1, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, false, null, null, random); PgpSecretKeyRing secRing1 = keyRingGen.GenerateSecretKeyRing(); PgpPublicKeyRing pubRing1 = keyRingGen.GeneratePublicKeyRing(); keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, - rsaKeyPair2, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, null, null, random); + rsaKeyPair2, "test", SymmetricKeyAlgorithmTag.Aes256, passPhrase, false, null, null, random); PgpSecretKeyRing secRing2 = keyRingGen.GenerateSecretKeyRing(); PgpPublicKeyRing pubRing2 = keyRingGen.GeneratePublicKeyRing(); diff --git a/crypto/test/src/openpgp/test/PgpUnicodeTest.cs b/crypto/test/src/openpgp/test/PgpUnicodeTest.cs index ce1df8980..534e8a471 100644 --- a/crypto/test/src/openpgp/test/PgpUnicodeTest.cs +++ b/crypto/test/src/openpgp/test/PgpUnicodeTest.cs @@ -13,7 +13,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests [TestFixture] public class PgpUnicodeTest { - private void DoTestKey(BigInteger keyId, string passphrase) + private void DoTestKey(BigInteger keyId, string passphrase, bool utf8) { PgpSecretKeyRingBundle secretKeyRing = LoadSecretKeyCollection("secring.gpg"); @@ -25,7 +25,11 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests try { - PgpPrivateKey privateKey = key.ExtractPrivateKey(passphrase.ToCharArray()); + char[] pass = passphrase.ToCharArray(); + + PgpPrivateKey privateKey = utf8 + ? key.ExtractPrivateKeyUtf8(pass) + : key.ExtractPrivateKey(pass); Assert.IsTrue(privateKey.KeyId == keyId.LongValue); } @@ -53,7 +57,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests // passwordFile.close(); // String passphrase = new String(password); - DoTestKey(keyId, passphrase); + DoTestKey(keyId, passphrase, true); // all fine! @@ -75,7 +79,8 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests string passphrase = "Admin123"; - DoTestKey(keyId, passphrase); + DoTestKey(keyId, passphrase, false); + DoTestKey(keyId, passphrase, true); // all fine! } @@ -102,7 +107,7 @@ namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests string passphrase = reader.ReadLine(); passwordFile.Close(); - DoTestKey(keyId, passphrase); + DoTestKey(keyId, passphrase, true); // all fine! } -- cgit 1.5.1