From 3c916cf61a28a0d14c82cef31a2ce70689aabe6c Mon Sep 17 00:00:00 2001 From: Dawid <96344755+dawmit@users.noreply.github.com> Date: Fri, 26 Sep 2025 18:29:05 +0100 Subject: [PATCH 1/2] Added Owl implementation, without gradle changes --- .../crypto/agreement/owl/ECSchnorrZKP.java | 55 ++ .../owl/Owl_AuthenticationFinish.java | 103 +++ .../owl/Owl_AuthenticationInitiate.java | 123 +++ .../owl/Owl_AuthenticationServerResponse.java | 167 ++++ .../crypto/agreement/owl/Owl_Client.java | 586 +++++++++++++ .../agreement/owl/Owl_ClientRegistration.java | 266 ++++++ .../crypto/agreement/owl/Owl_Curve.java | 201 +++++ .../crypto/agreement/owl/Owl_Curves.java | 50 ++ .../agreement/owl/Owl_FinishRegistration.java | 121 +++ .../owl/Owl_InitialRegistration.java | 84 ++ .../agreement/owl/Owl_KeyConfirmation.java | 64 ++ .../crypto/agreement/owl/Owl_Server.java | 453 ++++++++++ .../agreement/owl/Owl_ServerRegistration.java | 188 +++++ .../crypto/agreement/owl/Owl_Util.java | 794 ++++++++++++++++++ .../crypto/examples/Owl_Example.java | 228 +++++ .../owl_test/Owl_ClientRegistrationTest.java | 98 +++ .../agreement/owl_test/Owl_ClientTest.java | 359 ++++++++ .../agreement/owl_test/Owl_CurveTest.java | 66 ++ .../owl_test/Owl_ServerRegistrationTest.java | 114 +++ .../agreement/owl_test/Owl_ServerTest.java | 389 +++++++++ .../agreement/owl_test/Owl_UtilTest.java | 284 +++++++ 21 files changed, 4793 insertions(+) create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/ECSchnorrZKP.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java create mode 100644 core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java create mode 100644 core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java create mode 100644 core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java create mode 100644 core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_CurveTest.java create mode 100644 core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ServerRegistrationTest.java create mode 100644 core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ServerTest.java create mode 100644 core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_UtilTest.java diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/ECSchnorrZKP.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/ECSchnorrZKP.java new file mode 100644 index 0000000000..fa85df54cc --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/ECSchnorrZKP.java @@ -0,0 +1,55 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * Package protected class containing zero knowledge proof, for an Owl key exchange. + *
+ * This class encapsulates the values involved in the Schnorr + * zero-knowledge proof used in the Owl protocol. + */ +public class ECSchnorrZKP +{ + + /** + * The value of V = G x [v]. + */ + private final ECPoint V; + + /** + * The value of r = v - d * c mod n + */ + private final BigInteger r; + + /** + * Constructor for ECSchnorrZKP + * + * @param V Prover's commitment V = G x [v] + * @param r Prover's response r to a challenge c, r = v - d * c mod n + */ + public ECSchnorrZKP(ECPoint V, BigInteger r) + { + this.V = V; + this.r = r; + } + + /** + * Get the prover's commitment V = G x [v] where G is a base point on the elliptic curve and v is an ephemeral secret + * @return The prover's commitment + */ + public ECPoint getV() + { + return V; + } + + /** + * Get the prover's response r to the challenge c, r = v - d * c mod n where d is the prover's private key + * @return The prover's response + */ + public BigInteger getr() + { + return r; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java new file mode 100644 index 0000000000..fb826a33d1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java @@ -0,0 +1,103 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by the client during the third pass of an Owl exchange. + *
+ * Each {@link Owl_Client} creates and sends an instance + * of this payload to the {@link Owl_Server} after validating the previous payload + * {@link Owl_AuthenticationServerResponse}. + * The payload to send should be created via + * {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)}. + *
+ * Each {@link Owl_Client} must also validate the payload + * received from the {@link Owl_Server}, which is done by the same function + * {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)}. + */ +public class Owl_AuthenticationFinish +{ + /** + * Client's unique Id + */ + private final String clientId; + /** + * The value alpha = (x2 x pi) * [X1 + X3 + X4]. + */ + private final ECPoint alpha; + + /** + * The zero Knowledge proof for alpha. + *
+ * This is a class {@link ECSchnorrZKP} with two fields, containing {v * [G], r} for x2pi. + *
+ */ + private final ECSchnorrZKP knowledgeProofForAlpha; + + /** + * The value of r = x1 - t.h mod n + */ + private final BigInteger r; + + /** + * Constructor of Owl_AuthenticationFinish + * @param clientId Client's identity + * @param alpha The public key alpha sent by the client in the third pass + * @param knowledgeProofForAlpha The zero-knowledge proof for the knowledge of the private key for alpha + * @param r The response r for proving the knowledge of t=H(usrname||password) mod n. + */ + public Owl_AuthenticationFinish( + String clientId, + ECPoint alpha, + ECSchnorrZKP knowledgeProofForAlpha, + BigInteger r) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(alpha, "alpha"); + Owl_Util.validateNotNull(r, "r"); + Owl_Util.validateNotNull(knowledgeProofForAlpha, "knowledgeProofForAlpha"); + + this.clientId = clientId; + this.knowledgeProofForAlpha = knowledgeProofForAlpha; + this.alpha = alpha; + this.r = r; + } + + /** + * Get the client's identity (also known as username) + * @return The client's identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get the public key alpha = (x2 x pi) * [X1 + X3 + X4]. sent by the client in the third pass + * @return The public key alpha + */ + public ECPoint getAlpha() + { + return alpha; + } + + /** + * Get the response r as part of the zero-knowledge proof for proving the knowledge of t, r = x1 - t.h mod n where x1 is the ephemeral private key for the public key X1 sent in the first pass of Owl + * @return The response r sent by the client in the third pass + */ + public BigInteger getR() + { + return r; + } + + /** + * Get the Schnorr zero-knowledge proof for the knowledge of the private key (x2 x pi) for the public key alpha + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForAlpha() + { + return knowledgeProofForAlpha; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java new file mode 100644 index 0000000000..1b67e2d860 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java @@ -0,0 +1,123 @@ +package org.example; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by the client in the first pass of an Owl exchange. + *+ * Each {@link Owl_Client} creates and sends an instance + * of this payload to the {@link Owl_Server}. + * The payload to send should be created via + * {@link Owl_Client#authenticationInitiate()}. + */ +public class Owl_AuthenticationInitiate +{ + + /** + * Unique identifier for the client (this is the username) + *
+ * ClientId must not be the same as the server unique identifier, + *
+ */ + private final String clientId; + + /** + * The value of g^x1 + */ + private final ECPoint gx1; + + /** + * The value of g^x2 + */ + private final ECPoint gx2; + + /** + * The zero knowledge proof for x1. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x1. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX1; + + /** + * The zero knowledge proof for x2. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x2. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX2; + + /** + * Constructor of Owl_AuthenticationInitiate + * @param clientId the client's identity (or username) + * @param gx1 The public key X1 = x1 * [G] + * @param gx2 The public key X2 = x2 * [G] + * @param knowledgeProofForX1 The zero-knowledge proof for proving the knowledge of x1 + * @param knowledgeProofForX2 The zero-knowledge proof for proving the knowledge of x2 + */ + public Owl_AuthenticationInitiate( + String clientId, + ECPoint gx1, + ECPoint gx2, + ECSchnorrZKP knowledgeProofForX1, + ECSchnorrZKP knowledgeProofForX2) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(gx1, "gx1"); + Owl_Util.validateNotNull(gx2, "gx2"); + Owl_Util.validateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + Owl_Util.validateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + + this.clientId = clientId; + this.gx1 = gx1; + this.gx2 = gx2; + this.knowledgeProofForX1 = knowledgeProofForX1; + this.knowledgeProofForX2 = knowledgeProofForX2; + } + + /** + * Get the client's identity (or username) + * @return The client's identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get the client's public key X1 = x1 * [G] in the first pass of Owl + * @return The client's public key X1 + */ + public ECPoint getGx1() + { + return gx1; + } + + /** + * Get the client's public key X2 = x2 * [G] in the first pass of Owl + * @return The client's public key X2 + */ + public ECPoint getGx2() + { + return gx2; + } + + /** + * Get the zero-knowledge proof for the knowledge of x1 + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX1() + { + return knowledgeProofForX1; + } + + /** + * Get the zero-knowledge proof for the knowledge of x2 + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX2() + { + return knowledgeProofForX2; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java new file mode 100644 index 0000000000..f0e69ce515 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java @@ -0,0 +1,167 @@ +package org.example; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by the server during the second pass of an Owl exchange. + *+ * Each {@link Owl_Server} creates and sends an instance + * of this payload to the {@link Owl_Client}. + * The payload to send should be created via + * {@link Owl_Server#authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)}. + *
+ * Each {@link Owl_Server} must also validate the payload + * received from the {@link Owl_Client} which comes in the form of {@link Owl_AuthenticationInitiate}. + * The {@link Owl_Server} must retrieve the {@link Owl_FinishRegistration} + * from wherever the server securely stored the initial login information. + * The received payload should be validated via the same function (in the same call). + */ +public class Owl_AuthenticationServerResponse +{ + /** + * Unique identifier for the server. + *
+ * Must not be the same as the unique identifier for the client (client username). + *
+ */ + private final String serverId; + + /** + * The value of g^x3 + */ + private final ECPoint gx3; + + /** + * The value of g^x4 + */ + private final ECPoint gx4; + + /** + * The zero knowledge proof for x3. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x3. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX3; + + /** + * The zero knowledge proof for x4. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x4. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX4; + + /** + * The value for beta = (X1 + X2 + X3)^x4pi + */ + private final ECPoint beta; + + /** + * The zero knowledge proof for beta. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x4pi. + *
+ */ + private final ECSchnorrZKP knowledgeProofForBeta; + + /** + * Constructor for Owl_AuthenticationServerResponse + * @param serverId The server's identity + * @param gx3 The public key X3 = x3 * [G] + * @param gx4 The public key X4 = x4 * [G] + * @param knowledgeProofForX3 The zero-knowledge proof for the knowledge of x3 + * @param knowledgeProofForX4 The zero-knowledge proof for the knowledge of x4 + * @param beta The public key beta = (x4 x pi) * [X1 + X2 + X3] + * @param knowledgeProofForBeta The zero-knowledge proof for the knowledge of (x4 x pi) for beta + */ + public Owl_AuthenticationServerResponse( + String serverId, + ECPoint gx3, + ECPoint gx4, + ECSchnorrZKP knowledgeProofForX3, + ECSchnorrZKP knowledgeProofForX4, + ECPoint beta, + ECSchnorrZKP knowledgeProofForBeta) + { + Owl_Util.validateNotNull(serverId, "serverId"); + Owl_Util.validateNotNull(gx3, "gx3"); + Owl_Util.validateNotNull(gx4, "gx4"); + Owl_Util.validateNotNull(knowledgeProofForX3, "knowledgeProofForX3"); + Owl_Util.validateNotNull(knowledgeProofForX4, "knowledgeProofForX4"); + Owl_Util.validateNotNull(beta, "beta"); + Owl_Util.validateNotNull(knowledgeProofForBeta, "knowledgeProofForBeta"); + + this.serverId = serverId; + this.gx3 = gx3; + this.gx4 = gx4; + this.knowledgeProofForX3 = knowledgeProofForX3; + this.knowledgeProofForX4 = knowledgeProofForX4; + this.beta = beta; + this.knowledgeProofForBeta = knowledgeProofForBeta; + } + + /** + * Get the server's identity + * @return The server's identity + */ + public String getServerId() + { + return serverId; + } + + /** + * Get the public key X3 = x3 * [G] + * @return The public key X3 + */ + public ECPoint getGx3() + { + return gx3; + } + + /** + * Get the public key X4 = x4 * [G] + * @return The public key X4 + */ + public ECPoint getGx4() + { + return gx4; + } + + /** + * Get the zero-knowledge proof for the knowledge of x3 for X3 = x3 * [G] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX3() + { + return knowledgeProofForX3; + } + + /** + * Get the zero-knowledge proof for the knowledge of x4 for X4 = x4 * [G] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX4() + { + return knowledgeProofForX4; + } + + /** + * Get the public key beta = (x4 x pi) * [X1 + X2 + X3] + * @return The public key beta + */ + public ECPoint getBeta() + { + return beta; + } + + /** + * Get the zero-knowledge proof for the knowledge of (x4 x pi) for the public key beta = (x4 x pi) * [X1 + X2 + X3] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForBeta() + { + return knowledgeProofForBeta; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java new file mode 100644 index 0000000000..0dbc2b26cf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java @@ -0,0 +1,586 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; + +/** + * A client in the Elliptic Curve Owl key exchange protocol. + *+ * Owl is an augmented password-authenticated key exchange (PAKE) protocol, + * defined by Feng Hao, Samiran Bag, Liqun Chen, and Paul C. van Oorschot + * in the paper "Owl: An Augmented Password-Authenticated Key Exchange Scheme". + * Owl builds on the same idea as J-PAKE (using Schnorr zero-knowledge proofs to enforce participants to follow the protocol specification honestly), + * but it is augmented to to provide the additional protection against server compromise. + * While J-PAKE is symmetric, Owl is asymmetric. Like J-PAKE, Owl can be implemented in either elliptic curve (EC) or finite field (FF) settings. + * This implementation is done in the elliptic curve setting. + * Any elliptic curve that is suitable for cryptography can be used for Owl (same for J-PAKE). + *
+ * In Owl, there is one client and one server communicating between each other. + * An instance of {@link Owl_Server} represents one server, and + * an instance of {@link Owl_Client} represents one client. + * These together make up the main machine through which the protocol is facilitated. + *
+ * There are two distinct phases that can be taken in Owl: user registration - where the client registers + * as a new user on the server; login - where an existing user (client) attempts to log in and establish a shared session key with the server + * based on password authentication. Using the session key, the user (client) can perform further actions (e.g., password update) over a secure channel, + * but these actions are outside the scope of this key exchange program. + *
+ * The user registration phase involves only one pass of communication from the client to the server. It's assumed that the following communication is done over a secure channel (e.g., using an out-of-band method). + *
After the third pass, the client has completed client-to-server authentication. At this point, both the client and the server can compute a shared key. They call the following methods to compute a raw keying material and do key confirmation. + *
+ * 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). + *
+ * The explicit key confirmation process is optional but highly recommended. It does not affect the round efficiency and adds a negligible computational cost. The client-to-server key confirmation string + * can be piggybacked in the third pass along with {@link Owl_AuthenticationFinish}. The server-to-client key confirmation string can be sent in the next pass together with encrypted data. + * If you do not execute key confirmation, then there is no assurance that both client and server have actually derived the same key, and the ensuing secure communication may fail. + * If the key confirmation succeeds, then the keys are guaranteed to be the same on both sides. + *
+ * The key confirmation process is implemented as specified in + * NIST SP 800-56A Revision 3, + * Section 5.9.1 Unilateral Key Confirmation for Key Agreement Schemes. + *
+ * This class is stateful and NOT threadsafe. + * Each instance should only be used for ONE complete Owl key exchange + * (i.e. a new {@link Owl_Server} and {@link Owl_Client} should be constructed for each new Owl key exchange). + */ +public class Owl_Client +{ + /* + * Possible internal states. Used for state checking. + */ + public static final int STATE_INITIALISED = 0; + public static final int STATE_LOGIN_INITIALISED = 10; + public static final int STATE_LOGIN_FINISHED = 20; + public static final int STATE_KEY_CALCULATED = 30; + public static final int STATE_KC_INITIALISED = 40; + public static final int STATE_KC_VALIDATED = 50; + + /** + * Unique identifier of this client. + * The client and server in the exchange must NOT share the same id. + * clientId is the same as the username in this PAKE + */ + private final String clientId; + + /** + * 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 final Digest digest; + + /** + * Source of secure random data. + */ + private final SecureRandom random; + + /** + * The serverId of the server in this exchange. + */ + private String serverId; + + private ECCurve.AbstractFp ecCurve; + private BigInteger q; + private BigInteger h; + private BigInteger n; + private ECPoint g; + /** + * Client's x1 + */ + private BigInteger x1; + /** + * Client's x2. + */ + private BigInteger x2; + /** + Client's gx1. + */ + private ECPoint gx1; + /** + * Client's gx2. + */ + private ECPoint gx2; + /** + * Server's gx3. + */ + private ECPoint gx3; + /** + * Server's gx4. + */ + private ECPoint gx4; + /** + * Client's user specified secret t = H(username||password) mod n + */ + private BigInteger t; + /** + * Shared secret used for authentication pi = H(t) mod n + */ + private BigInteger pi; + /** + * The current state. + * See theSTATE_* constants for possible values.
+ */
+ private int state;
+ /**
+ * The raw key K used to calculate a session key.
+ */
+ private ECPoint rawKey;
+ /**
+ * Client's alpha
+ */
+ private ECPoint alpha;
+ /**
+ * ECSchnorrZKP knowledge proof for x1, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX1;
+ /**
+ * ECSchnorrZKP knowledge proof for x2, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX2;
+ /**
+ * ECSchnorrZKP knowledge proof for x3, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX3;
+ /**
+ * ECSchnorrZKP knowledge proof for x4, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX4;
+
+ /**
+ * Convenience constructor for a new {@link Owl_Client} that uses
+ * the {@link Owl_Curves#NIST_P256} elliptic curve,
+ * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+ * + * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_Client( + String clientId, + char[] password) + { + this( + clientId, + password, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_Client} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_Client( + String clientId, + char[] password, + Owl_Curve curve) + { + this( + clientId, + password, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_Client}. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}.
+ *
+ * @param clientId unique identifier of this client.
+ * The server and client 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 curve elliptic curve.
+ * See {@link Owl_Curves} for standard curves
+ * @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 NullPointerException if any argument is null
+ * @throws IllegalArgumentException if password is empty
+ */
+ public Owl_Client(
+ String clientId,
+ char[] password,
+ Owl_Curve curve,
+ Digest digest,
+ SecureRandom random)
+ {
+ Owl_Util.validateNotNull(clientId, "clientId");
+ Owl_Util.validateNotNull(password, "password");
+ Owl_Util.validateNotNull(curve, "curve params");
+ Owl_Util.validateNotNull(digest, "digest");
+ Owl_Util.validateNotNull(random, "random");
+ if (password.length == 0)
+ {
+ throw new IllegalArgumentException("Password must not be empty.");
+ }
+
+ this.clientId = clientId;
+
+ /*
+ * Create a defensive copy so as to fully encapsulate the password.
+ *
+ * This array will contain the password for the lifetime of this
+ * client 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 = Arrays.copyOf(password, password.length);
+
+ this.ecCurve = curve.getCurve();
+ this.g = curve.getG();
+ this.h = curve.getH();
+ this.n = curve.getN();
+ this.q = curve.getQ();
+
+ this.digest = digest;
+ this.random = random;
+
+ this.state = STATE_INITIALISED;
+ }
+
+ /**
+ * Gets the current state of this client.
+ * See the STATE_* constants for possible values.
+ *
+ * @return the state of the client
+ */
+ public int getState()
+ {
+ return this.state;
+ }
+ /**
+ * Creates and returns the payload to send to the server as part of the first pass of the protocol.
+ *
+ * Must be called prior to {@link #authenticationFinish(Owl_AuthenticationServerResponse)} + * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_INITIALISED}. + * + * @return {@link Owl_AuthenticationInitiate} + * @throws IllegalStateException if called multiple times. + */ + public Owl_AuthenticationInitiate authenticationInitiate() + { + if (this.state >= STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Login already initiated by " + clientId); + } + this.t = calculateT(); + + this.pi = calculatePi(); + + this.x1 = Owl_Util.generateX1(n, random); + this.x2 = Owl_Util.generateX1(n, random); + + this.gx1 = Owl_Util.calculateGx(g, x1); + this.gx2 = Owl_Util.calculateGx(g, x2); + + this.knowledgeProofForX1 = Owl_Util.calculateZeroknowledgeProof(g, n, x1, gx1, digest, clientId, random); + this.knowledgeProofForX2 = Owl_Util.calculateZeroknowledgeProof(g, n, x2, gx2, digest, clientId, random); + + this.state = STATE_LOGIN_INITIALISED; + + return new Owl_AuthenticationInitiate(clientId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + } + + /** + * Finalises the login authentication protocol by creating and sending the final payload to the server. + * Validates the payload sent by the {@link Owl_Server#authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)} after login initilisation. + *
+ * Must be called prior to {@link #calculateKeyingMaterial()}. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_FINISHED}. + * + * @param authenticationServerResponse The payload sent by {@link Owl_Server#authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)} and to be validated. + * + * @return {@link Owl_AuthenticationFinish} + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #authenticationInitiate()} or called multiple times. + */ + public Owl_AuthenticationFinish authenticationFinish(Owl_AuthenticationServerResponse authenticationServerResponse) + throws CryptoException + { + if (this.state >= STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Login authentication already finished by: " + clientId); + } + if (this.state < STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Must initialise login authentication before calling authentication finish for: " + clientId); + } + this.serverId = authenticationServerResponse.getServerId(); + this.gx3 = authenticationServerResponse.getGx3(); + this.gx4 = authenticationServerResponse.getGx4(); + ECPoint beta = authenticationServerResponse.getBeta(); + ECSchnorrZKP knowledgeProofForX3 = authenticationServerResponse.getKnowledgeProofForX3(); + ECSchnorrZKP knowledgeProofForX4 = authenticationServerResponse.getKnowledgeProofForX4(); + ECSchnorrZKP knowledgeProofForBeta = authenticationServerResponse.getKnowledgeProofForBeta(); + + ECPoint betaG = Owl_Util.calculateGA(gx1, gx2, gx3); + + Owl_Util.validateParticipantIdsDiffer(clientId, authenticationServerResponse.getServerId()); + Owl_Util.validateZeroknowledgeProof(g, gx3, knowledgeProofForX3, q, n, ecCurve, h, authenticationServerResponse.getServerId(), digest); + Owl_Util.validateZeroknowledgeProof(g, gx4, knowledgeProofForX4, q, n, ecCurve, h, authenticationServerResponse.getServerId(), digest); + Owl_Util.validateZeroknowledgeProof(betaG, beta, knowledgeProofForBeta, q, n, ecCurve, h, authenticationServerResponse.getServerId(), digest); + + ECPoint alphaG = Owl_Util.calculateGA(gx1, gx3, gx4); + BigInteger x2pi = Owl_Util.calculateX2s(n, x2, pi); + ECPoint alpha = Owl_Util.calculateA(alphaG, x2pi); + + ECSchnorrZKP knowledgeProofForAlpha = Owl_Util.calculateZeroknowledgeProof(alphaG, n, x2pi, alpha, digest, clientId, random); + + this.rawKey = Owl_Util.calculateKeyingMaterial(gx4, x2, x2pi, beta); + + BigInteger hTranscript = Owl_Util.calculateTranscript(rawKey, clientId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2, serverId, gx3, gx4, + knowledgeProofForX3, knowledgeProofForX4, beta, knowledgeProofForBeta, alpha, knowledgeProofForAlpha, digest); + + BigInteger r = Owl_Util.calculateR(x1, t, hTranscript, n); + + this.state = STATE_LOGIN_FINISHED; + + return new Owl_AuthenticationFinish(clientId, alpha, knowledgeProofForAlpha, r); + } + + + /** + * 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 Owl_Client}). + *
+ * The keying material will be identical for client and server if and only if + * the login password is the same as the password stored by the server. i.e. If the client and + * server do not share the same password, then each 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 perform explicit + * key confirmation. See {@link Owl_Client} for details on how to execute + * key confirmation. + *
+ * If the passwords used for registration and login are different then this will be caught + * when validating r during {@link Owl_Server#authenticationServerEnd(Owl_AuthenticationFinish)}. + *
+ * {@link #authenticationFinish(Owl_AuthenticationServerResponse)} 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}. + * + * @return raw key material + * @throws IllegalStateException if called prior to {@link #authenticationFinish(Owl_AuthenticationServerResponse)}, + * or if called multiple times. + */ + public BigInteger calculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Key already calculated for " + clientId); + } + if (this.state < STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Login authentication must be finished prior to creating key for " + clientId); + } + + /* + * 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. + */ + Arrays.fill(password, (char)0); + this.password = null; + + BigInteger keyingMaterial = rawKey.normalize().getAffineXCoord().toBigInteger(); + /* + * 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.t = null; + this.pi = null; + this.rawKey = null; + + /* + * Do not clear gx* yet, since those are needed by key confirmation. + */ + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /** + * Creates and returns the payload to send to the server as part of Key Confirmation. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_INITIALISED}. + * + * @return {@link Owl_KeyConfirmation} + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public Owl_KeyConfirmation initiateKeyConfirmation(BigInteger keyingMaterial) + { + if (this.state >= STATE_KC_INITIALISED) + { + throw new IllegalStateException("Key Confirmation already initiated for " + this.clientId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to initialising key confirmation for " + this.clientId); + } + + BigInteger macTag = Owl_Util.calculateMacTag( + this.clientId, + this.serverId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest); + + this.state = STATE_KC_INITIALISED; + + return new Owl_KeyConfirmation(clientId, macTag); + } + + /** + * Validates the key confirmation payload received by the server. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_VALIDATED}. + * + * @param keyConfirmationPayload The key confirmation payload received from the other client. + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public void validateKeyConfirmation(Owl_KeyConfirmation keyConfirmationPayload, BigInteger keyingMaterial) + throws CryptoException + { + if (this.state >= STATE_KC_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for this payload for" + clientId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to validating this payload for " + this.clientId); + } + Owl_Util.validateParticipantIdsDiffer(clientId, keyConfirmationPayload.getId()); + Owl_Util.validateParticipantIdsEqual(this.serverId, keyConfirmationPayload.getId()); + + Owl_Util.validateMacTag( + this.clientId, + this.serverId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest, + keyConfirmationPayload.getMacTag()); + + + /* + * Clear the rest of the fields. + */ + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_KC_VALIDATED; + } + + private BigInteger calculateT() + { + try + { + // t = H(username||password). Prepend each item with its byte length (int) to set clear boundary + return Owl_Util.calculateT(n, + String.valueOf(clientId.getBytes().length) + clientId + + String.valueOf(password.length) + new String(password), digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } + + private BigInteger calculatePi() + { + try + { + return Owl_Util.calculatePi(n, t, digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java new file mode 100644 index 0000000000..02377e75ba --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java @@ -0,0 +1,266 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; +/** + * A client in the Owl key exchange protocol specifically for the user registration phase. + *
+ * There is one client and one server communicating between each other. + * An instance of {@link Owl_ServerRegistration} represents one server, and + * an instance of {@link Owl_ClientRegistration} represents one client. + * These together make up the main machine through which user registration is facilitated. + *
+ * To execute the registration, construct an {@link Owl_ServerRegistration} on the server end, + * and construct an {@link Owl_ClientRegistration} on the client end. + * Each Owl registration will need a new and distinct {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration}. + * You cannot use the same {@link Owl_ServerRegistration} or {@link Owl_ClientRegistration} for multiple exchanges. + *
+ * For user login go to {@link Owl_Client} and {@link Owl_Server}. + * To execute the user registration phase, both + * {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration} must be constructed. + *
+ * The following communication between {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration}, must be + * facilitated over a secure communications channel as the leakage of the payload sent, + * would allow an attacker to reconstruct the secret password. + *
+ * Call the following methods in this order, the client initiates every exchange. + *
+ * This class is stateful and NOT threadsafe. + * Each instance should only be used for ONE complete Owl exchange + * (i.e. a new {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration} should be constructed for each new Owl exchange). + */ +public class Owl_ClientRegistration{ + /* + * Possible state for user registration. + */ + public static final boolean REGISTRATION_NOT_CALLED = false; + public static final boolean REGISTRATION_CALLED = true; + /** + * Unique identifier of this client. + *
+ * The client and server in the exchange must NOT share the same id. + *
+ */ + private final String clientId; + /** + * Shared secret. This only contains the secret between construction + * and the call to {@link #initiateUserRegistration()}. + *+ * i.e. When {@link #initiateUserRegistration()} is called, this buffer is overwritten with 0's, + * and the field is set to null. + *
+ */ + private char[] password; + /** + * Digest to use during calculations. + */ + private final Digest digest; + /** + * Source of secure random data. + */ + private final SecureRandom random; + /** + * Client's user specified secret t = H(username||password) mod n + */ + private BigInteger t; + + private BigInteger n; + private ECPoint g; + /** + * Checks if user registration is called more than once. + */ + private boolean registrationState; + /** + * Get the status of the user registration. + * I.E. whether or not this server has registered a user already. + * See theREGSITRATION_* constants for possible values.
+ * @return True if the user has been registered or false otherwise
+ */
+ public boolean getRegistrationState()
+ {
+ return this.registrationState;
+ }
+
+ /**
+ * Convenience constructor for a new {@link Owl_ClientRegistration} that uses
+ * the {@link Owl_Curves#NIST_P256} elliptic curve,
+ * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+ * + * After construction, the {@link #getRegistrationState() state} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 #initiateUserRegistration()} is called). + * Caller should clear the input password as soon as possible. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_ClientRegistration( + String clientId, + char[] password) + { + this( + clientId, + password, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_ClientRegistration} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getRegistrationState() state} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param clientId unique identifier of this client.. + * The server and client 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 #initiateUserRegistration()} is called). + * Caller should clear the input password as soon as possible. + * @param curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_ClientRegistration( + String clientId, + char[] password, + Owl_Curve curve) + { + this( + clientId, + password, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_ClientRegistration}. + *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 #initiateUserRegistration()} is called). + * Caller should clear the input password as soon as possible. + * @param curve elliptic curve. + * See {@link Owl_Curves} for standard curves + * @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 NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_ClientRegistration( + String clientId, + char[] password, + Owl_Curve curve, + Digest digest, + SecureRandom random) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(password, "password"); + Owl_Util.validateNotNull(curve, "curve params"); + Owl_Util.validateNotNull(digest, "digest"); + Owl_Util.validateNotNull(random, "random"); + if (password.length == 0) + { + throw new IllegalArgumentException("Password must not be empty."); + } + + this.clientId = clientId; + + /* + * Create a defensive copy so as to fully encapsulate the password. + * + * This array will contain the password for the lifetime of this + * client BEFORE {@link #initiateUserRegistration()} is called. + * + * i.e. When {@link #initiateUserRegistration()} 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 = Arrays.copyOf(password, password.length); + this.g = curve.getG(); + this.n = curve.getN(); + + this.digest = digest; + this.random = random; + + this.registrationState = REGISTRATION_NOT_CALLED; + } + /** + * Initiates user registration with the server. Creates the registration payload {@link Owl_InitialRegistration} and sends it to the server. + * MUST be sent over a secure channel. + *
+ * Must be called prior to {@link Owl_ServerRegistration#registerUseronServer(Owl_InitialRegistration)} + * @return {@link Owl_InitialRegistration} + * @throws IllegalStateException if this function is called more than once + */ + public Owl_InitialRegistration initiateUserRegistration() + { + if(this.registrationState) + { + throw new IllegalStateException("User login registration already begun by "+ clientId); + } + this.t = calculateT(); + + BigInteger pi = calculatePi(); + + ECPoint gt = Owl_Util.calculateGx(g, t); + + /* + * 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. + */ + Arrays.fill(password, (char)0); + this.password = null; + this.t = null; + this.registrationState = REGISTRATION_CALLED; + + return new Owl_InitialRegistration(clientId, pi, gt); + } + + private BigInteger calculateT() + { + try + { + // t = H(username||password). Prepend each item with its byte length (int) to set clear boundary + return Owl_Util.calculateT(n, + String.valueOf(clientId.getBytes().length) + clientId + + String.valueOf(password.length) + new String(password), digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } + private BigInteger calculatePi() + { + try + { + return Owl_Util.calculatePi(n, t, digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java new file mode 100644 index 0000000000..6282abe784 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java @@ -0,0 +1,201 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; + +/** + * A pre-computed elliptic curve over a prime field, in short-Weierstrass form for use during an Owl exchange. + *
+ * In general, Owl can use any elliptic curve or prime order group + * that is suitable for public key cryptography. + *
+ * See {@link Owl_Curves} for convenient standard curves. + *
+ * NIST publishes + * many curves with different forms and levels of security. + */ +public class Owl_Curve +{ + private final ECCurve.AbstractFp curve; + private final ECPoint g; + + /** + * Constructs a new {@link Owl_Curve}. + *
+ * In general, you should use one of the pre-approved curves from + * {@link Owl_Curves}, 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. + * + * @param q The prime field modulus + * @param a The curve coefficient a + * @param b The curve coefficient b + * @param n The order of the base point G + * @param h The co-factor + * @param g_x The x coordinate of the base point G + * @param g_y The y coordinate of the base point G + * + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if any of the above validations fail + */ + public Owl_Curve(BigInteger q, BigInteger a, BigInteger b, BigInteger n, BigInteger h, BigInteger g_x, BigInteger g_y) + { + Owl_Util.validateNotNull(a, "a"); + Owl_Util.validateNotNull(b, "b"); + Owl_Util.validateNotNull(q, "q"); + Owl_Util.validateNotNull(n, "n"); + Owl_Util.validateNotNull(h, "h"); + Owl_Util.validateNotNull(g_x, "g_x"); + Owl_Util.validateNotNull(g_y, "g_y"); + + /* + * Don't skip the checks on user-specified groups. + */ + + /* + * Note that these checks do not guarantee that n and q are prime. + * We just have reasonable certainty that they are prime. + */ + if (!q.isProbablePrime(20)) + { + throw new IllegalArgumentException("Field size q must be prime"); + } + + if (a.compareTo(BigInteger.ZERO) < 0 || a.compareTo(q) >= 0) + { + throw new IllegalArgumentException("The parameter 'a' is not in the field [0, q-1]"); + } + + if (b.compareTo(BigInteger.ZERO) < 0 || b.compareTo(q) >= 0) + { + throw new IllegalArgumentException("The parameter 'b' is not in the field [0, q-1]"); + } + + BigInteger d = calculateDeterminant(q, a, b); + if (d.equals(BigInteger.ZERO)) + { + throw new IllegalArgumentException("The curve is singular, i.e the discriminant is equal to 0 mod q."); + } + + if (!n.isProbablePrime(20)) + { + throw new IllegalArgumentException("The order n must be prime"); + } + + ECCurve.Fp curve = new ECCurve.Fp(q, a, b, n, h); + ECPoint g = curve.createPoint(g_x, g_y); + + if (!g.isValid()) + { + throw new IllegalArgumentException("The base point G does not lie on the curve."); + } + + this.curve = curve; + this.g = g; + } + + /** + * Internal package-private constructor used by the pre-approved + * groups in {@link Owl_Curves}. + * These pre-approved curves can avoid the expensive checks. + */ + Owl_Curve(ECCurve.AbstractFp curve, ECPoint g) + { + Owl_Util.validateNotNull(curve, "curve"); + Owl_Util.validateNotNull(g, "g"); + Owl_Util.validateNotNull(curve.getOrder(), "n"); + Owl_Util.validateNotNull(curve.getCofactor(), "h"); + + this.curve = curve; + this.g = g; + } + + /** + * Get the elliptic curve + * @return The curve + */ + public ECCurve.AbstractFp getCurve() + { + return curve; + } + + /** + * Get the base point G + * @return G + */ + public ECPoint getG() + { + return g; + } + + /** + * Get the curve coefficient a + * @return a + */ + public BigInteger getA() + { + return curve.getA().toBigInteger(); + } + + /** + * Get the curve coefficient b + * @return b + */ + public BigInteger getB() + { + return curve.getB().toBigInteger(); + } + + /** + * Get n, the order of the base point + * @return n + */ + public BigInteger getN() + { + return curve.getOrder(); + } + + /** + * Get the co-factor h of the curve + * @return h + */ + public BigInteger getH() + { + return curve.getCofactor(); + } + + /** + * Get the prime field modulus q of the curve + * @return q + */ + public BigInteger getQ() + { + return curve.getQ(); + } + + private static BigInteger calculateDeterminant(BigInteger q, BigInteger a, BigInteger b) + { + BigInteger a3x4 = a.multiply(a).mod(q).multiply(a).mod(q).shiftLeft(2); + BigInteger b2x27 = b.multiply(b).mod(q).multiply(BigInteger.valueOf(27)); + return a3x4.add(b2x27).mod(q); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java new file mode 100644 index 0000000000..41c43f181e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java @@ -0,0 +1,50 @@ +package org.example; + +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.math.ec.ECCurve; + +/** + * Standard pre-computed elliptic curves for use by Owl. + * (Owl can use pre-computed elliptic curves or prime order groups, same as DSA and Diffie-Hellman.) + *
+ * This class contains some convenient constants for use as input for + * constructing {@link Owl_Client}s and {@link Owl_Server}. + *
+ * The prime order groups below are taken from NIST SP 800-186, + * "Recommendations for Discrete Logarithm-based Cryptography: Elliptic Curve Domain Parameters", + * published by NIST. + */ +public class Owl_Curves +{ + /** + * From NIST. + * 128-bit security. + */ + public static final Owl_Curve NIST_P256; + + /** + * From NIST. + * 192-bit security. + */ + public static final Owl_Curve NIST_P384; + + /** + * From NIST. + * 256-bit security. + */ + public static final Owl_Curve NIST_P521; + + static + { + NIST_P256 = getCurve("P-256"); + NIST_P384 = getCurve("P-384"); + NIST_P521 = getCurve("P-521"); + } + + private static Owl_Curve getCurve(String curveName) + { + X9ECParameters x9 = CustomNamedCurves.getByName(curveName); + return new Owl_Curve((ECCurve.AbstractFp)x9.getCurve(), x9.getG()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java new file mode 100644 index 0000000000..513b27471c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java @@ -0,0 +1,121 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The final payload generated by the {@link Owl_ServerRegistration} during the user registration of a Owl exchange. + * This payload is to be stored securely by the server. + *
+ * Each {@link Owl_ServerRegistration} creates and sends an instance + * of this payload to be stored securely. + * The payload to send should be created via + * {@link Owl_ServerRegistration#registerUseronServer(Owl_InitialRegistration)}. + */ +public class Owl_FinishRegistration +{ + /** + * Unique identifier for the client in this registration phase. + *
+ * Must be different to the server's unique identifier. + *
+ */ + private final String clientId; + + /** + * The value x3 * [G]. + */ + private final ECPoint gx3; + + /** + * The zero knowledge proof for x3. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {v*[G], r} for x3. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX3; + + /** + * The value of pi = H(t), where t = H(Username||password) mod(n) + */ + private final BigInteger pi; + + /** + * The value of T = t * [G] + */ + private final ECPoint gt; + + /** + * Constructor of Owl_FinishRegistration + * @param clientId The client identity (or username) + * @param knowledgeProofForX3 The zero-knowledge proof for the knowledge of x3 for X3 + * @param gx3 The public key X3= [G] * x3 + * @param pi pi = H(t) where t=H(username || password) mod n + * @param gt T = t * [G] + */ + public Owl_FinishRegistration( + String clientId, + ECSchnorrZKP knowledgeProofForX3, + ECPoint gx3, + BigInteger pi, + ECPoint gt) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(pi, "pi"); + Owl_Util.validateNotNull(gt, "gt"); + Owl_Util.validateNotNull(gx3, "gx3"); + Owl_Util.validateNotNull(knowledgeProofForX3, "knowledgeProofForX3"); + + this.clientId = clientId; + this.knowledgeProofForX3 = knowledgeProofForX3; + this. gx3 = gx3; + this.pi = pi; + this.gt = gt; + } + + /** + * Get the client identity + * @return The client identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get pi = H(t), where t = H(Username||password) mod(n) + * @return pi + */ + public BigInteger getPi() + { + return pi; + } + + /** + * Get T = t * [G] + * @return T + */ + public ECPoint getGt() + { + return gt; + } + + /** + * Get X3 = x3 * [G] + * @return X3 + */ + public ECPoint getGx3() + { + return gx3; + } + + /** + * Get the zero-knowledge proof for the knowledge of x3 for X3 = x3 * [G] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX3() + { + return knowledgeProofForX3; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java new file mode 100644 index 0000000000..bfa87751df --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java @@ -0,0 +1,84 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by {@link Owl_ClientRegistration}, during the user registration phase of an Owl exchange. + *+ * The {@link Owl_ClientRegistration} creates and sends an instance + * of this payload to the {@link Owl_ServerRegistration}. + * The payload to send should be created via + * {@link Owl_ClientRegistration#initiateUserRegistration()}. + *
+ * Each {@link Owl_ServerRegistration} must also validate the payload + * received from the {@link Owl_ClientRegistration}. + * The received payload should be validated via + * {@link Owl_ServerRegistration#registerUseronServer(Owl_InitialRegistration)}. + */ +public class Owl_InitialRegistration +{ + /** + * Unique identifier for the client (same as username). + *
+ * Must not be the same as the unique identifier for the server. + *
+ */ + private final String clientId; + /** + * The value of pi = H(t), where t = H(Username||password) mod n + */ + private final BigInteger pi; + /** + * The value of T = t * [G] + */ + private final ECPoint gt; + + /** + * Constructor of Owl_InitialRegistration + * @param clientId Client identity (or username) + * @param pi pi = H(t), where t = H(Username||password) mod(n) + * @param gt T = t * [G] + */ + public Owl_InitialRegistration( + String clientId, + BigInteger pi, + ECPoint gt) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(pi, "pi"); + Owl_Util.validateNotNull(gt, "gt"); + + this.clientId = clientId; + this.pi = pi; + this.gt = gt; + } + + /** + * Get the client identity (or username) + * @return The client identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get pi = H(t), where t = H(Username||password) mod(n) + * @return pi + */ + public BigInteger getPi() + { + return pi; + } + + /** + * Get T = t * [G] + * @return T + */ + public ECPoint getGt() + { + return gt; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java new file mode 100644 index 0000000000..99f7986d9b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java @@ -0,0 +1,64 @@ +package org.example; + +import java.math.BigInteger; + +/** + * The payload sent/received during the explicit key confirmation stage of the protocol, + *+ * Both {@link Owl_Client} and {@link Owl_Server} create and send an instance + * of this payload to the other. + * The payload to send should be created via + * {@link Owl_Client#initiateKeyConfirmation(BigInteger)} + * or {@link Owl_Server#initiateKeyConfirmation(BigInteger)}. + *
+ * Both {@link Owl_Client} and {@link Owl_Server} must also validate the payload + * received from the other. + * The received payload should be validated via + * {@link Owl_Client#validateKeyConfirmation(Owl_KeyConfirmation, BigInteger)} + * {@link Owl_Server#validateKeyConfirmation(Owl_KeyConfirmation, BigInteger)} + */ +public class Owl_KeyConfirmation +{ + + /** + * The id of either {@link Owl_Client} or {@link Owl_Server} who created/sent this payload. + */ + private final String id; + + /** + * The value of MacTag, as computed by the key confirmation round. + * + * @see Owl_Util#calculateMacTag + */ + private final BigInteger macTag; + + /** + * Constructor of Owl_KeyConfirmation + * @param id The identity of the sender + * @param magTag The key confirmation string + */ + public Owl_KeyConfirmation(String id, BigInteger magTag) + { + this.id = id; + this.macTag = magTag; + } + + /** + * Get the identity of the sender + * @return The identity of the sender + */ + public String getId() + { + return id; + } + + /** + * Get the MAC tag which serves as a key confirmation string + * @return The MAC tag + */ + public BigInteger getMacTag() + { + return macTag; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java new file mode 100644 index 0000000000..6fa4a4c892 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java @@ -0,0 +1,453 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; + +/** + * A server in the Elliptic Curve Owl key exchange protocol. + *
+ * See {@link Owl_Client} for more details about Owl. + */ +public class Owl_Server +{ + + /* + * Possible internal states. Used for state checking. + */ + public static final int STATE_INITIALISED = 0; + public static final int STATE_LOGIN_INITIALISED = 10; + public static final int STATE_LOGIN_FINISHED = 20; + public static final int STATE_KEY_CALCULATED = 30; + public static final int STATE_KC_INITIALISED = 40; + public static final int STATE_KC_VALIDATED = 50; + + /** + * Unique identifier of this server. + * The client and server in the exchange must NOT share the same id. + */ + private final String serverId; + + /** + * Unique identifier for the client in the exchange. + */ + private String clientId; + + /** + * Digest to use during calculations. + */ + private final Digest digest; + + /** + * Source of secure random data. + */ + private final SecureRandom random; + + private ECCurve.AbstractFp ecCurve; + private BigInteger q; + private BigInteger h; + private BigInteger n; + private ECPoint g; + + /** + * Server's x4. + */ + private BigInteger x4; + /** + * Client's gx1. + */ + private ECPoint gx1; + /** + * Client's gx2. + */ + private ECPoint gx2; + /** + * Server's gx3. + */ + private ECPoint gx3; + /** + * Server's gx4. + */ + private ECPoint gx4; + /** + * Shared secret used for authentication pi = H(t) mod n + */ + private BigInteger pi; + /** + * Client's T, Password verifier stored on server + */ + private ECPoint gt; + /** + * Server's beta value used in login authentication + */ + private ECPoint beta; + /** + * ECSchnorrZKP knowledge proof for x1, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX1; + /** + * ECSchnorrZKP knowledge proof for x2, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX2; + /** + * ECSchnorrZKP knowledge proof for x3, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX3; + /** + * ECSchnorrZKP knowledge proof for x4, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX4; + /** + * ECSchnorrZKP knowledge proof for beta, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForBeta; + /** + * The raw key K used to calculate a session key. + */ + private ECPoint rawKey; + /** + * The current state. + * See the STATE_* constants for possible values. + */ + private int state; + + /** + * Convenience constructor for a new {@link Owl_Server} that uses + * the {@link Owl_Curves#NIST_P256} elliptic curve, + * a SHA-256 digest, and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @throws NullPointerException if any argument is null + */ + public Owl_Server( + String serverId) + { + this( + serverId, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_Server} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @param curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + */ + public Owl_Server( + String serverId, + Owl_Curve curve) + { + this( + serverId, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_Server}. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}.
+ *
+ * @param serverId unique identifier of this server.
+ * The client and server in the exchange must NOT share the same id.
+ * @param curve elliptic curve
+ * See {@link Owl_Curves} for standard curves
+ * @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 x3 and x4, and for the zero knowledge proofs
+ * @throws NullPointerException if any argument is null
+ */
+ public Owl_Server(
+ String serverId,
+ Owl_Curve curve,
+ Digest digest,
+ SecureRandom random)
+ {
+ Owl_Util.validateNotNull(serverId, "serverId");
+ Owl_Util.validateNotNull(curve, "curve params");
+ Owl_Util.validateNotNull(digest, "digest");
+ Owl_Util.validateNotNull(random, "random");
+
+ this.serverId = serverId;
+
+ this.ecCurve = curve.getCurve();
+ this.g = curve.getG();
+ this.h = curve.getH();
+ this.n = curve.getN();
+ this.q = curve.getQ();
+
+ this.digest = digest;
+ this.random = random;
+
+ this.state = STATE_INITIALISED;
+ }
+
+ /**
+ * Gets the current state of this server.
+ * See the STATE_* constants for possible values.
+ *
+ * @return The state of the server
+ */
+ public int getState()
+ {
+ return this.state;
+ }
+
+ /**
+ * Validates the payload sent by {@link Owl_Client#authenticationInitiate()} by {@link Owl_Client}, and then creates a new {@link Owl_AuthenticationServerResponse} payload and sends it to the {@link Owl_Client}.
+ *
+ * Must be called prior to {@link #authenticationServerEnd(Owl_AuthenticationFinish)}. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_INITIALISED}. + * + * @param authenticationInitiate payload sent by {@link Owl_Client#authenticationInitiate()} to be validated and used for further calculation. + * @param userLoginCredentials comes from the server where it stored the user login credentials as part of the user login registration. + * @return {@link Owl_AuthenticationServerResponse} + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called multiple times. + */ + public Owl_AuthenticationServerResponse authenticationServerResponse( + Owl_AuthenticationInitiate authenticationInitiate, + Owl_FinishRegistration userLoginCredentials) + throws CryptoException + { + if (this.state >= STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Response to client authentication initiation already created by " + serverId); + } + this.clientId = authenticationInitiate.getClientId(); + this.gx1 = authenticationInitiate.getGx1(); + this.gx2 = authenticationInitiate.getGx2(); + + this.knowledgeProofForX1 = authenticationInitiate.getKnowledgeProofForX1(); + this.knowledgeProofForX2 = authenticationInitiate.getKnowledgeProofForX2(); + + Owl_Util.validateParticipantIdsDiffer(serverId, authenticationInitiate.getClientId()); + Owl_Util.validateZeroknowledgeProof(g, gx1, knowledgeProofForX1, q, n, ecCurve, h, authenticationInitiate.getClientId(), digest); + Owl_Util.validateZeroknowledgeProof(g, gx2, knowledgeProofForX2, q, n, ecCurve, h, authenticationInitiate.getClientId(), digest); + + this.x4 = Owl_Util.generateX1(n, random); + + this.gx4 = Owl_Util.calculateGx(g, x4); + + this.knowledgeProofForX4 = Owl_Util.calculateZeroknowledgeProof(g, n, x4, gx4, digest, serverId, random); + + this.gx3 = userLoginCredentials.getGx3(); + this.pi = userLoginCredentials.getPi(); + this.knowledgeProofForX3 = userLoginCredentials.getKnowledgeProofForX3(); + this.gt = userLoginCredentials.getGt(); + + Owl_Util.validateParticipantIdsEqual(this.clientId, userLoginCredentials.getClientId()); + + ECPoint betaG = Owl_Util.calculateGA(gx1, gx2, gx3); + BigInteger x4pi = Owl_Util.calculateX2s(n, x4, pi); + this.beta = Owl_Util.calculateA(betaG, x4pi); + this.knowledgeProofForBeta = Owl_Util.calculateZeroknowledgeProof(betaG, n, x4pi, beta, digest, serverId, random); + + this.state = STATE_LOGIN_INITIALISED; + + return new Owl_AuthenticationServerResponse(serverId, gx3, gx4, knowledgeProofForX3, knowledgeProofForX4, beta, knowledgeProofForBeta); + } + + /** + * Validates the payload received from the client during the third pass of the Owl protocol. + * Must be called prior to {@link #calculateKeyingMaterial()}. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_FINISHED}. + * + * @param authenticationFinish payload sent by {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)} to be validated. + * + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)}, or multiple times + */ + public void authenticationServerEnd(Owl_AuthenticationFinish authenticationFinish) + throws CryptoException + { + if (this.state >= STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Server's authentication ending already called by " + serverId); + } + if (this.state < STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Authentication server response required before authentication finish by " + this.serverId); + } + + ECPoint alpha = authenticationFinish.getAlpha(); + ECPoint alphaG = Owl_Util.calculateGA(gx1, gx3, gx4); + ECSchnorrZKP knowledgeProofForAlpha = authenticationFinish.getKnowledgeProofForAlpha(); + + Owl_Util.validateZeroknowledgeProof(alphaG, alpha, knowledgeProofForAlpha, q, n, ecCurve, h, clientId, digest); + + BigInteger x4pi = Owl_Util.calculateX2s(n, x4, pi); + this.rawKey = Owl_Util.calculateKeyingMaterial(gx2, x4, x4pi, alpha); + + BigInteger hTranscript = Owl_Util.calculateTranscript(rawKey, clientId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2, serverId, gx3, gx4, + knowledgeProofForX3, knowledgeProofForX4, beta, knowledgeProofForBeta, alpha, knowledgeProofForAlpha, digest); + + Owl_Util.validateR(authenticationFinish.getR(), gx1, hTranscript, gt, g, n); + Owl_Util.validateParticipantIdsDiffer(serverId, authenticationFinish.getClientId()); + Owl_Util.validateParticipantIdsEqual(this.clientId, authenticationFinish.getClientId()); + + this.state = STATE_LOGIN_FINISHED; + } + + /** + * 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 Owl_Server}). + *
+ * The keying material will be identical for client and server if and only if + * the login password is the same as the password stored by the server. i.e. If the client and + * server do not share the same password, then each will derive a different key. + * Rememeber, the server does not explicitly hold the password, but a secret value derived from the password + * sent to the server by the client during user registration. + * Therefore, if you immediately start using a key derived from + * the keying material, then you must handle detection of incorrect keys. + * Validation of the r value also detects if passwords are different between user registration and user login. + * If you want to check the equality of the key materials derived at the two sides explicitly, you can perform explicit + * key confirmation. See {@link Owl_Server} for details on how to execute + * key confirmation. + *
+ * {@link #authenticationServerEnd(Owl_AuthenticationFinish)} must be called prior to this method. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KEY_CALCULATED}. + * + * @return The raw key material produced by the Owl key exchange process + * @throws IllegalStateException if called prior to {@link #authenticationServerEnd(Owl_AuthenticationFinish)}, + * or if called multiple times. + */ + public BigInteger calculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Key already calculated for " + serverId); + } + if (this.state < STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Server must validate client's final payload prior to creating key for " + serverId); + } + + BigInteger keyingMaterial = rawKey.normalize().getAffineXCoord().toBigInteger(); + /* + * 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 key x4 are leaked, + * the attacker might be able to brute-force the password. + */ + this.x4 = null; + this.beta = null; + this.gt = null; + this.rawKey = null; + + /* + * Do not clear gx* yet, since those are needed by key confirmation. + */ + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /** + * Creates and returns the payload to send to the client as part of Key Confirmation. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_INITIALISED}. + * + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @return {@link Owl_KeyConfirmation} + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public Owl_KeyConfirmation initiateKeyConfirmation(BigInteger keyingMaterial) + { + if (this.state >= STATE_KC_INITIALISED) + { + throw new IllegalStateException("Key confirmation payload already created for " + this.serverId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to creating key confirmation payload for " + this.serverId); + } + + BigInteger macTag = Owl_Util.calculateMacTag( + this.serverId, + this.clientId, + this.gx3, + this.gx4, + this.gx1, + this.gx2, + keyingMaterial, + this.digest); + + this.state = STATE_KC_INITIALISED; + + return new Owl_KeyConfirmation(serverId, macTag); + } + + /** + * Validates the payload received from the client as part of Key Confirmation. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_VALIDATED}. + * + * @param keyConfirmationPayload The key confirmation payload received from the client.. + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public void validateKeyConfirmation(Owl_KeyConfirmation keyConfirmationPayload, BigInteger keyingMaterial) + throws CryptoException + { + if (this.state >= STATE_KC_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for key confirmation payload by " + serverId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated validated prior to validating key confirmation payload for " + this.serverId); + } + Owl_Util.validateParticipantIdsDiffer(serverId, keyConfirmationPayload.getId()); + Owl_Util.validateParticipantIdsEqual(this.clientId, keyConfirmationPayload.getId()); + + Owl_Util.validateMacTag( + this.serverId, + this.clientId, + this.gx3, + this.gx4, + this.gx1, + this.gx2, + keyingMaterial, + this.digest, + keyConfirmationPayload.getMacTag()); + + /* + * Clear the rest of the fields. + */ + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_KC_VALIDATED; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java new file mode 100644 index 0000000000..4e165f6765 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java @@ -0,0 +1,188 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; + +/** + * A server in the Owl key exchange protocol specifically for the user registration phase. + *
+ * See {@link Owl_ClientRegistration} for more details on the user registration in Owl. + *
+ * This class is stateful and NOT threadsafe.
+ * Each instance should only be used for ONE complete Owl registration phase
+ * (i.e. a new {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration} should be constructed for each new Owl exchange).
+ */
+public class Owl_ServerRegistration
+{
+ /*
+ * Possible state for user registration.
+ */
+ public static final boolean REGISTRATION_NOT_CALLED = false;
+ public static final boolean REGISTRATION_CALLED = true;
+ /**
+ * Unique identifier of this server.
+ * The client and server in the exchange must NOT share the same id.
+ */
+ private final String serverId;
+ /**
+ * Digest to use during calculations.
+ */
+ private final Digest digest;
+
+ /**
+ * Source of secure random data.
+ */
+ private final SecureRandom random;
+
+ private ECCurve.AbstractFp ecCurve;
+ private BigInteger h;
+ private BigInteger q;
+ private BigInteger n;
+ private ECPoint g;
+ /**
+ * Checks if user registration is called more than once.
+ */
+ private boolean registrationState;
+
+ /**
+ * Check's the status of the user registration
+ * I.E. whether or not this server has registered a user already.
+ * See the REGSITRATION_* constants for possible values.
+ * @return true if the user has been registered or false otherwise
+ */
+ public boolean getRegistrationState()
+ {
+ return this.registrationState;
+ }
+ /**
+ * Convenience constructor for a new {@link Owl_ServerRegistration} that uses
+ * the {@link Owl_Curves#NIST_P256} elliptic curve,
+ * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+ *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @throws NullPointerException if any argument is null + */ + public Owl_ServerRegistration( + String serverId) + { + this( + serverId, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_ServerRegistration} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @param curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + */ + public Owl_ServerRegistration( + String serverId, + Owl_Curve curve) + { + this( + serverId, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_ServerRegistration}. + *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param serverId unique identifier of this server. + * The client and server in the exchange must NOT share the same id. + * @param curve elliptic curve; see {@link Owl_Curves} for standard curves + * @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 x3 and x4, and for the zero knowledge proofs + * @throws NullPointerException if any argument is null + */ + public Owl_ServerRegistration( + String serverId, + Owl_Curve curve, + Digest digest, + SecureRandom random) + { + Owl_Util.validateNotNull(serverId, "serverId"); + Owl_Util.validateNotNull(curve, "curve params"); + Owl_Util.validateNotNull(digest, "digest"); + Owl_Util.validateNotNull(random, "random"); + + this.serverId = serverId; + this.ecCurve = curve.getCurve(); + this.q = curve.getQ(); + this.h = curve.getH(); + this.g = curve.getG(); + this.n = curve.getN(); + + this.digest = digest; + this.random = random; + + this.registrationState = REGISTRATION_NOT_CALLED; + } + /** + * Initiates user registration with the server. Creates the registration payload {@link Owl_InitialRegistration} and sends it to the server. + * MUST be sent over a secure channel. + *
+ * Must be called prior to {@link #registerUseronServer(Owl_InitialRegistration)} + * @throws IllegalStateException if this function is called more than once + */ + + /** + * Receives the payload sent by the client as part of user registration, and stores necessary values in the server. + *
+ * Must be called after {@link Owl_ClientRegistration#initiateUserRegistration()} by the {@link Owl_Client}. + * @param userLoginRegistrationReceived {@link Owl_InitialRegistration} + * @return {@link Owl_FinishRegistration} + * @throws IllegalStateException if this functions is called more than once. + * @throws CryptoException if validation of the payload fails + */ + public Owl_FinishRegistration registerUseronServer( + Owl_InitialRegistration userLoginRegistrationReceived + ) + throws CryptoException + { + if(this.registrationState) + { + throw new IllegalStateException("Server has already registrered this payload, by "+ serverId); + } + BigInteger x3 = Owl_Util.generateX1(n, random); + + ECPoint gx3 = Owl_Util.calculateGx(g, x3); + + ECSchnorrZKP knowledgeProofForX3 = Owl_Util.calculateZeroknowledgeProof(g, n, x3, gx3, digest, serverId, random); + + String clientId = userLoginRegistrationReceived.getClientId(); + BigInteger pi = userLoginRegistrationReceived.getPi(); + ECPoint gt = userLoginRegistrationReceived.getGt(); + + Owl_Util.validateParticipantIdsDiffer(clientId, serverId); + if (pi.compareTo(BigInteger.ONE)==-1 || pi.compareTo(n.subtract(BigInteger.ONE)) == 1) { + throw new CryptoException("pi is not in the range of [1, n-1]. for " + serverId); + } + Owl_Util.validatePublicKey(gt, ecCurve, q, h); + this.registrationState = REGISTRATION_CALLED; + + return new Owl_FinishRegistration(clientId, knowledgeProofForX3, gx3, pi, gt); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java new file mode 100644 index 0000000000..f9c5195e5d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java @@ -0,0 +1,794 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.Strings; + +/** + * Primitives needed for an Owl exchange. + *
+ * The recommended way to perform an Owl exchange is by using + * an {@link Owl_Server} and {@link Owl_Client} + * (and for user registration {@link Owl_ClientRegistration} and {@link Owl_ServerRegistration}). + * Internally, those two classes call these primitive + * operations in {@link Owl_Util}. + *
+ * The primitives, however, can be used without a {@link Owl_Server} + * or {@link Owl_Client} if needed. + */ +public class Owl_Util +{ + static final BigInteger ZERO = BigInteger.valueOf(0); + static final BigInteger ONE = BigInteger.valueOf(1); + + /** + * Return a value that can be used as x1, x2, x3 or x4 during round 1. + *
+ * The returned value is a random value in the range [1, n-1].
+ * @param n The order of the base point
+ * @param random SecureRandom
+ * @return A random value within [1, n-1]
+ */
+ public static BigInteger generateX1(
+ BigInteger n,
+ SecureRandom random)
+ {
+ BigInteger min = ONE;
+ BigInteger max = n.subtract(ONE);
+ return BigIntegers.createRandomInRange(min, max, random);
+ }
+
+ /**
+ * Calculate X = [G] * x as done in round 1.
+ * Also used to calculate T = [G] * t as seen in the initial user registration
+ *
+ * @param g Base point G
+ * @param x Scalar x
+ * @return [G] * x
+ */
+ public static ECPoint calculateGx(
+ ECPoint g,
+ BigInteger x)
+ {
+ return g.multiply(x);
+ }
+
+ /**
+ * Calculate the combined public key GA = X1 * X3 * X4 from three public keys X1, X3 and X4.
+ *
+ * @param gx1 Public key X1
+ * @param gx3 Public key X3
+ * @param gx4 Public key X4
+ * @return The combined public key
+ * @throws CryptoException if any of the parameters are the infinity point on the elliptic curve.
+ */
+ public static ECPoint calculateGA(
+ ECPoint gx1,
+ ECPoint gx3,
+ ECPoint gx4) throws CryptoException {
+
+ if (gx1.isInfinity()) {
+ throw new CryptoException("Public key X1 cannot be infinity");
+ }
+ if (gx3.isInfinity()) {
+ throw new CryptoException("Public key X3 cannot be infinity");
+ }
+ if (gx4.isInfinity()) {
+ throw new CryptoException("Public key X4 cannot be infinity");
+ }
+
+ ECPoint Gx = gx1.add(gx3).add(gx4);
+
+ if (Gx.isInfinity()) {
+ throw new CryptoException("The combined public key cannot be infinity");
+ }
+
+ return Gx;
+ }
+
+
+ /**
+ * Calculate x2 * pi mod n as done in second pass of Owl.
+ *
+ * @param n The order of the base point
+ * @param x2 The private key x2
+ * @param pi pi = H(t) mod n
+ * @return x2 * pi mod n
+ */
+ public static BigInteger calculateX2s(
+ BigInteger n,
+ BigInteger x2,
+ BigInteger pi)
+ {
+ return x2.multiply(pi).mod(n);
+ }
+
+
+ /**
+ * Calculate alpha or beta as done in the second pass.
+ */
+ /**
+ * Calculate the public key from a base point and a scalar
+ * @param gA Base point
+ * @param x2pi Scalar
+ * @return [gA] * x2pi
+ */
+ public static ECPoint calculateA(
+ ECPoint gA,
+ BigInteger x2pi)
+ {
+ // alpha = Galpha^(x*pi)
+ return gA.multiply(x2pi);
+ }
+
+ /**
+ * Calculate t for the user's initial registration onto the server.
+ * t is calculated by H(username||password) mod n
+ *
+ * @param n The order of the base point
+ * @param string xxx
+ * @param digest Instance of Digest
+ * @return t = H(username||password) mod n
+ * @throws CryptoException if t = 0 mod n
+ */
+ public static BigInteger calculateT(
+ BigInteger n,
+ String string,
+ Digest digest)
+ throws CryptoException
+ {
+ BigInteger t = calculateHash(string, digest).mod(n);
+ if (t.signum() == 0)
+ {
+ throw new CryptoException("MUST ensure t is not equal to 0 modulo n");
+ }
+ return t;
+ }
+
+ /**
+ * Calculate pi = H(t) mod(n) for initial registration.
+ *
+ * @param n The order of the base point
+ * @param t t = H(username||password) mod n
+ * @param digest Instance of a one-way hash
+ * @return pi
+ * @throws CryptoException When pi = 0 mod n
+ */
+ public static BigInteger calculatePi(
+ BigInteger n,
+ BigInteger t,
+ Digest digest)
+ throws CryptoException
+ {
+ BigInteger pi = calculateHash(t, digest).mod(n);
+ if(pi.compareTo(BigInteger.ZERO)==0){
+ throw new CryptoException("MUST ensure that pi is not equal to 0 modulo n");
+ }
+ return pi;
+ }
+ /**
+ * Calculate a zero-knowledge proof of x using Schnorr's signature.
+ * The returned object has two fields {V = [G] * v, r = v-x*h} for x.
+ *
+ * @param generator The base point on the curve
+ * @param n The order of the base point
+ * @param x The private key
+ * @param X The public key
+ * @param digest Instance of a one-way hash
+ * @param userID The identity of the prover
+ * @param random Instance of a secure random number generator
+ * @return {@link ECSchnorrZKP}
+ */
+ public static ECSchnorrZKP calculateZeroknowledgeProof(
+ ECPoint generator,
+ BigInteger n,
+ BigInteger x,
+ ECPoint X,
+ Digest digest,
+ String userID,
+ SecureRandom random)
+ {
+
+ /* Generate a random v from [1, n-1], and compute V = G*v */
+ BigInteger v = BigIntegers.createRandomInRange(BigInteger.ONE, n.subtract(BigInteger.ONE), random);
+ ECPoint V = generator.multiply(v);
+ BigInteger h = calculateHashForZeroknowledgeProof(generator, V, X, userID, digest); // h
+ // r = v-x*h mod n
+
+ return new ECSchnorrZKP(V, v.subtract(x.multiply(h)).mod(n));
+ }
+
+
+ private static BigInteger calculateHash(
+ String string,
+ Digest digest)
+ {
+ digest.reset();
+
+ byte[] byteArray = Strings.toUTF8ByteArray(string);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+
+ return new BigInteger(output);
+ }
+
+ private static BigInteger calculateHash(
+ BigInteger bigint,
+ Digest digest)
+ {
+ digest.reset();
+
+ byte[] byteArray = bigint.toByteArray();
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+
+ return new BigInteger(output);
+ }
+
+
+ private static BigInteger calculateHashForZeroknowledgeProof(
+ ECPoint g,
+ ECPoint v,
+ ECPoint x,
+ String participantId,
+ Digest digest)
+ {
+ digest.reset();
+
+ updateDigestIncludingSize(digest, g);
+
+ updateDigestIncludingSize(digest, v);
+
+ updateDigestIncludingSize(digest, x);
+
+ updateDigestIncludingSize(digest, participantId);
+
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+
+ return new BigInteger(output);
+ }
+
+
+ private static void updateDigestIncludingSize(
+ Digest digest,
+ ECPoint ecPoint)
+ {
+ byte[] byteArray = ecPoint.getEncoded(true);
+ digest.update(intToByteArray(byteArray.length), 0, 4);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ }
+
+ private static void updateDigestIncludingSize(
+ Digest digest,
+ String string)
+ {
+ byte[] byteArray = Strings.toUTF8ByteArray(string);
+ digest.update(intToByteArray(byteArray.length), 0, 4);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ }
+
+ private static void updateDigestIncludingSize(
+ Digest digest,
+ BigInteger bigInteger)
+ {
+ byte[] byteArray = bigInteger.toByteArray();
+ digest.update(intToByteArray(byteArray.length), 0, 4);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ }
+
+ /**
+ * Validates the zero-knowledge proof (generated by
+ * {@link #calculateZeroknowledgeProof(ECPoint, BigInteger, BigInteger, ECPoint, Digest, String, SecureRandom)})
+ * is correct.
+ *
+ * @param generator The base point on the curve
+ * @param X The public key
+ * @param zkp The zero-knowledge proof {@link ECSchnorrZKP}
+ * @param q The prime modulus for the coordinates
+ * @param n The oder of the base point
+ * @param curve The elliptic curve
+ * @param coFactor The co-factor
+ * @param userID The identity of the prover
+ * @param digest Instance of a one-way hash
+ * @throws CryptoException If the zero-knowledge proof is not correct
+ */
+
+ public static void validateZeroknowledgeProof(
+ ECPoint generator,
+ ECPoint X,
+ ECSchnorrZKP zkp,
+ BigInteger q,
+ BigInteger n,
+ ECCurve curve,
+ BigInteger coFactor,
+ String userID,
+ Digest digest)
+ throws CryptoException
+ {
+ ECPoint V = zkp.getV();
+ BigInteger r = zkp.getr();
+ /* ZKP: {V=G*v, r} */
+ BigInteger h = calculateHashForZeroknowledgeProof(generator, V, X, userID, digest);
+
+ // First, ensure X is a valid public key
+ validatePublicKey(X, curve, q, coFactor);
+
+ // Now check if V = G*r + X*h.
+ // Given that {G, X} are valid points on the curve, the equality implies that V is also a point on the curve.
+ if (!V.equals(generator.multiply(r).add(X.multiply(h.mod(n)))))
+ {
+ throw new CryptoException("Zero-knowledge proof validation failed: V = G*r + X*h must hold");
+ }
+
+ }
+
+ /**
+ * Validates that the given participant ids are not equal.
+ * (For the Owl exchange, each participant must use a unique id.)
+ *
+ * @param participantId1 The identity of the first participant
+ * @param participantId2 The identity of the second participate
+ * @throws CryptoException if the participantId strings are equal.
+ */
+ public static void validateParticipantIdsDiffer(
+ String participantId1,
+ String participantId2)
+ throws CryptoException
+ {
+ 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 (client/server).
+ *
+ * @param expectedParticipantId The expected participant's identity
+ * @param actualParticipantId The actual participate's identity
+
+ * @throws CryptoException if the participantId strings are equal.
+ */
+ public static void validateParticipantIdsEqual(
+ String expectedParticipantId,
+ String actualParticipantId)
+ throws CryptoException
+ {
+ 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.
+ *
+ * @param object object in question
+ * @param description name of the object (to be used in exception message)
+ * @throws NullPointerException if the object is null.
+ */
+ public static void validateNotNull(
+ Object object,
+ String description)
+ {
+ if (object == null)
+ {
+ throw new NullPointerException(description + " must not be null");
+ }
+ }
+
+ /**
+ * Calculates the keying material, which can be done after {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)} has completed
+ * for client and when {@link Owl_Server#authenticationServerEnd(Owl_AuthenticationFinish)} has completed for server.
+ * 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 Owl_Server} or {@link Owl_Client}).
+ *
+ * KeyingMaterial = [Beta - [X4] * x2pi] * x2 + *+ * + * @param gx4 Public key X4 = x4 * [G] + * @param x2 Private key x2 + * @param x2pi Private key x2*pi + * @param B The public key Beta + * @return Raw key material K = [Beta - [X4] * x2pi] * x2) + */ + public static ECPoint calculateKeyingMaterial( + ECPoint gx4, + BigInteger x2, + BigInteger x2pi, + ECPoint B) + { + return B.subtract(gx4.multiply(x2pi)).multiply(x2); + } + + /** + * Calculates the 'transcript' which is, in order, everything communicated between the two parties + * in the login authentication protocol used to compute a common session key. + * Transcript is required for the calculation of r, as a compiler is used + * to prove the knowledge of t in an asymmetric setting. + * For more information: Owl: An Augmented Password-Authenticated + * Key Exchange Scheme + * + * @param rawKey Raw key material + * @param clientId Client identity + * @param gx1 Public key X1 + * @param gx2 Public key X2 + * @param knowledgeProofX1 Zero-knowledge proof for X1 + * @param knowledgeProofX2 Zero-knowledge proof for X2 + * @param serverId Server identity + * @param gx3 Public key X3 + * @param gx4 Public key X4 + * @param knowledgeProofX3 Zero-knowledge proof for X3 + * @param knowledgeProofX4 Zero-knowledege proof for X4 + * @param beta Public key Beta + * @param knowledgeProofBeta Zero-knowledge proof for Beta + * @param alpha Public key Alpha + * @param knowledgeProofAlpha Zero-knowledge proof for Alpha + * @param digest Instance of MAC + * @return Transcript + */ + public static BigInteger calculateTranscript( + ECPoint rawKey, + String clientId, + ECPoint gx1, + ECPoint gx2, + ECSchnorrZKP knowledgeProofX1, + ECSchnorrZKP knowledgeProofX2, + String serverId, + ECPoint gx3, + ECPoint gx4, + ECSchnorrZKP knowledgeProofX3, + ECSchnorrZKP knowledgeProofX4, + ECPoint beta, + ECSchnorrZKP knowledgeProofBeta, + ECPoint alpha, + ECSchnorrZKP knowledgeProofAlpha, + Digest digest) + { + digest.reset(); + + updateDigestIncludingSize(digest, rawKey); + updateDigestIncludingSize(digest, clientId); + updateDigestIncludingSize(digest, gx1); + updateDigestIncludingSize(digest, gx2); + updateDigestIncludingSize(digest, knowledgeProofX1.getV()); + updateDigestIncludingSize(digest, knowledgeProofX1.getr()); + updateDigestIncludingSize(digest, knowledgeProofX2.getV()); + updateDigestIncludingSize(digest, knowledgeProofX2.getr()); + updateDigestIncludingSize(digest, serverId); + updateDigestIncludingSize(digest, gx3); + updateDigestIncludingSize(digest, gx4); + updateDigestIncludingSize(digest, knowledgeProofX3.getV()); + updateDigestIncludingSize(digest, knowledgeProofX3.getr()); + updateDigestIncludingSize(digest, knowledgeProofX4.getV()); + updateDigestIncludingSize(digest, knowledgeProofX4.getr()); + updateDigestIncludingSize(digest, beta); + updateDigestIncludingSize(digest, knowledgeProofBeta.getV()); + updateDigestIncludingSize(digest, knowledgeProofBeta.getr()); + updateDigestIncludingSize(digest, alpha); + updateDigestIncludingSize(digest, knowledgeProofAlpha.getV()); + updateDigestIncludingSize(digest, knowledgeProofAlpha.getr()); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return new BigInteger(output); + } + + /** + * Calculates r = x1 - t * h mod n, where h is the hashed key+transcript. + * + * @param x1 Private key x1 + * @param t t = H(username||password) mod n + * @param hTranscript Transcript + * @param n Order of the base point + * @return r + */ + public static BigInteger calculateR( + BigInteger x1, + BigInteger t, + BigInteger hTranscript, + BigInteger n) + { + return x1.subtract(t.multiply(hTranscript)).mod(n); + } + + /** + * Validates r by checking [g] * r + [T] * h equals X1 + * + * @param r The client response r + * @param gx1 Public key X1 + * @param h Transcript + * @param gt Password verifier T + * @param g Base point + * @param n order of the base point + * @throws CryptoException if the validation fails i.e the values do not equal. + */ + public static void validateR( + BigInteger r, + ECPoint gx1, + BigInteger h, + ECPoint gt, + ECPoint g, + BigInteger n) + throws CryptoException + { + if(!g.multiply(r).add(gt.multiply(h.mod(n))).equals(gx1)) + { + throw new CryptoException("Verification for r failed, g^r . T^h = X1 must be true"); + } + } + + /** + * Validates that an EC point X is a valid public key on the designated elliptic curve. + * + * @param X Public key + * @param curve Elliptic curve + * @param q Prime field for the coordinates + * @param coFactor Co-factor + * @throws CryptoException if the public key validation fails + */ + public static void validatePublicKey(ECPoint X, ECCurve curve, BigInteger q, BigInteger coFactor) throws CryptoException{ + + /* Public key validation based on the following paper (Sec 3) + * Antipa, A., Brown, D., Menezes, A., Struik, R. and Vanstone, S., + * "Validation of elliptic curve public keys," PKC, 2002 + * https://iacr.org/archive/pkc2003/25670211/25670211.pdf + */ + + // 1. X != infinity + if (X.isInfinity()) + { + throw new CryptoException("Public key validation failed: it cannot equal infinity"); + } + + ECPoint x_normalized = X.normalize(); + // 2. Check x and y coordinates are in Fq, i.e., x, y in [0, q-1] + if (x_normalized.getAffineXCoord().toBigInteger().signum() < 0 || + x_normalized.getAffineXCoord().toBigInteger().compareTo(q) >= 0 || + x_normalized.getAffineYCoord().toBigInteger().signum() < 0 || + x_normalized.getAffineYCoord().toBigInteger().compareTo(q) >= 0) + { + throw new CryptoException("Public key validation failed: x and y coordiantes are not in the field"); + } + // 3. Check X lies on the curve + try + { + curve.decodePoint(X.getEncoded(true)); + } + catch (Exception e) + { + throw new CryptoException("Public key validation failed: it does not lie on the curve"); + } + // 4. Check that nX = infinity. + // It is equivalent - but more efficient - to check the coFactor*X is not infinity + if (X.multiply(coFactor).isInfinity()) + { + throw new CryptoException("Public key validation failed: it is in a small order group"); + } + } + + /** + * Calculates the MacTag (to be used for key confirmation), as defined by + * NIST SP 800-56A Revision 3, + * Section 5.9.1 Unilateral Key Confirmation for Key Agreement Schemes. + *
+ * MacTag = HMAC(MacKey, MacLen, MacData)
+ *
+ * MacKey = H(K || "Owl_KC")
+ *
+ * MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4
+ *
+ * Note that both participants use "KC_1_U" because the sender of the key confirmation message
+ * is always the initiator for key confirmation.
+ *
+ * HMAC = {@link HMac} used with the given {@link Digest}
+ * H = The given {@link Digest}
+ * MacOutputBits = MacTagBits, hence truncation function omitted.
+ * MacLen = length of MacTag
+ *
+ *
+ * @param participantId Participant's identity
+ * @param partnerParticipantId The other participant's identity
+ * @param gx1 Public key X1
+ * @param gx2 Public key X2
+ * @param gx3 Public key X3
+ * @param gx4 Public key X4
+ * @param keyingMaterial Keying material
+ * @param digest Instance of a one-way hash
+ * @return MacTag for key confirmation
+ */
+ public static BigInteger calculateMacTag(
+ String participantId,
+ String partnerParticipantId,
+ ECPoint gx1,
+ ECPoint gx2,
+ ECPoint gx3,
+ ECPoint gx4,
+ BigInteger keyingMaterial,
+ Digest 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 || "Owl_KC") + *+ * + * @param keyingMaterial Keying material K + * @param digest Instance of a one-way hash + * @return H(K || "Owl_KC") + */ + private static byte[] calculateMacKey( + BigInteger keyingMaterial, + Digest 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, "Owl_KC"); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return output; + } + + /** + * Validates the MacTag received from the partner participant. + * + * @param participantId The participant's identity + * @param partnerParticipantId The other participant's identity + * @param gx1 Public key X1 + * @param gx2 Public key X2 + * @param gx3 Public key X3 + * @param gx4 Public key X4 + * @param keyingMaterial Keying material + * @param digest Instance of a one-way hash + * @param partnerMacTag The MacTag received from the partner. + * @throws CryptoException if the participantId strings are equal + */ + public static void validateMacTag( + String participantId, + String partnerParticipantId, + ECPoint gx1, + ECPoint gx2, + ECPoint gx3, + ECPoint gx4, + BigInteger keyingMaterial, + Digest digest, + BigInteger partnerMacTag) + throws CryptoException + { + /* + * 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 updateMac(Mac mac, ECPoint ecPoint) + { + byte[] byteArray = ecPoint.getEncoded(true); + mac.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateMac(Mac mac, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + mac.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, ECPoint ecPoint) + { + byte[] byteArray = ecPoint.getEncoded(true); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + protected static byte[] intToByteArray(int value) + { + return new byte[]{ + (byte)(value >>> 24), + (byte)(value >>> 16), + (byte)(value >>> 8), + (byte)value + }; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java b/core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java new file mode 100644 index 0000000000..afe109fcf2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java @@ -0,0 +1,228 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.SavableDigest; +import org.bouncycastle.crypto.digests.SHA256Digest; + +/** + * An example of an Owl key exchange process. + *
+ * In this example, both the client and server are on the same computer (in the same JVM, in fact). + * In reality, they would be in different locations, + * and would be sending their generated payloads to each other. + *
+ */ +public class Owl_Example { + + public static void main(String args[]) throws CryptoException + { + /* + * Initialization + * + * Pick an appropriate elliptic curve to use throughout the exchange. + * Note that both participants must use the same group. + * Note that the same curve must be used between user registration, + * user login and user password update. + */ + Owl_Curve curve = Owl_Curves.NIST_P256; + + ECCurve ecCurve = curve.getCurve(); + BigInteger a = curve.getA(); + BigInteger b = curve.getB(); + ECPoint g = curve.getG(); + BigInteger h = curve.getH(); + BigInteger n = curve.getN(); + BigInteger q = curve.getQ(); + + String clientPassword = "password"; + + System.out.println("********* Initialization **********"); + System.out.println("Public parameters for the elliptic curve over prime field:"); + System.out.println("Curve param a (" + a.bitLength() + " bits): "+ a.toString(16)); + System.out.println("Curve param b (" + b.bitLength() + " bits): "+ b.toString(16)); + System.out.println("Co-factor h (" + h.bitLength() + " bits): " + h.toString(16)); + System.out.println("Base point G (" + g.getEncoded(true).length + " bytes): " + new BigInteger(g.getEncoded(true)).toString(16)); + System.out.println("X coord of G (G not normalised) (" + g.getXCoord().toBigInteger().bitLength() + " bits): " + g.getXCoord().toBigInteger().toString(16)); + System.out.println("y coord of G (G not normalised) (" + g.getYCoord().toBigInteger().bitLength() + " bits): " + g.getYCoord().toBigInteger().toString(16)); + System.out.println("Order of the base point n (" + n.bitLength() + " bits): "+ n.toString(16)); + System.out.println("Prime field q (" + q.bitLength() + " bits): "+ q.toString(16)); + System.out.println(""); + + System.out.println("(Secret passwords used by Client: " + clientPassword + ")"); + System.out.println(""); + + /* + * Both participants must use the same hashing algorithm. + * Both participants muse use the same hashing algorithm + * and elliptic curve for registration and authentication. + */ + Digest digest = SHA256Digest.newInstance(); + SecureRandom random = new SecureRandom(); + + Owl_ClientRegistration clientReg = new Owl_ClientRegistration("client", clientPassword.toCharArray(), curve, digest, random); + Owl_ServerRegistration serverReg = new Owl_ServerRegistration("server", curve, digest, random); + + Owl_Client client = new Owl_Client("client", clientPassword.toCharArray(), curve, digest, random); + Owl_Server server = new Owl_Server("server", curve, digest, random); + + /* + * Initial User Registration + * Client initiates registration using their username and password of choice and + * sends a payload (over a secure channel) to the server which in turn safely + * and securely stores (server storage is upto the user of this handshake protocol). + */ + + Owl_InitialRegistration clientUserRegistration = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverUserRegistration = serverReg.registerUseronServer(clientUserRegistration); + + System.out.println("************ User Registration **************"); + System.out.println("Client sends to Server: "); + System.out.println("Username used to register = " + clientUserRegistration.getClientId()); + System.out.println("pi=" + clientUserRegistration.getPi().toString(16)); + System.out.println("T=" + new BigInteger(clientUserRegistration.getGt().getEncoded(true)).toString(16)); + System.out.println(""); + + System.out.println("Server stores internally: "); + System.out.println("Username used to register = " + serverUserRegistration.getClientId()); + System.out.println("KP{x3}: {V="+new BigInteger(serverUserRegistration.getKnowledgeProofForX3().getV().getEncoded(true)).toString(16)+"; r="+serverUserRegistration.getKnowledgeProofForX3().getr().toString(16)+"}"); + System.out.println("X3=" + new BigInteger(serverUserRegistration.getGx3().getEncoded(true)).toString(16)); + System.out.println("pi=" + serverUserRegistration.getPi().toString(16)); + System.out.println("T=" + new BigInteger(serverUserRegistration.getGt().getEncoded(true)).toString(16)); + System.out.println(""); + + /* + * First Pass + * The client initiates the login authentication process by creating and sending a + * payload to the server. + */ + + Owl_AuthenticationInitiate clientLoginStart = client.authenticationInitiate(); + System.out.println("************ First Pass ************"); + System.out.println("Client sends to server: "); + System.out.println("Username used to login: " + clientLoginStart.getClientId()); + System.out.println("X1=" + new BigInteger(clientLoginStart.getGx1().getEncoded(true)).toString(16)); + System.out.println("X2=" + new BigInteger(clientLoginStart.getGx2().getEncoded(true)).toString(16)); + System.out.println("KP{x1}: {V=" + new BigInteger(clientLoginStart.getKnowledgeProofForX1().getV().getEncoded(true)).toString(16) + "; r=" + clientLoginStart.getKnowledgeProofForX1().getr().toString(16) + "}"); + System.out.println("KP{x2}: {V=" + new BigInteger(clientLoginStart.getKnowledgeProofForX2().getV().getEncoded(true)).toString(16) + "; r=" + clientLoginStart.getKnowledgeProofForX2().getr().toString(16) + "}"); + System.out.println(""); + + /* + * Second Pass + * The server validates the clients initial payload, and takes as input + * the internally stored client data from the server. It validates the payload recieved from the client. + * It then creates and sends its own payload back to the client. + */ + + Owl_AuthenticationServerResponse serverLoginResponse = server.authenticationServerResponse(clientLoginStart, serverUserRegistration); + + System.out.println("************ Second Pass **************"); + System.out.println("Server verifies the client's KP{x1}: OK"); + System.out.println("Server verifies the client's KP{x2}: OK\n"); + System.out.println("Server sends to Client: "); + System.out.println("Server's unique ID: " + serverLoginResponse.getServerId()); + System.out.println("X3=" + new BigInteger(serverLoginResponse.getGx3().getEncoded(true)).toString(16)); + System.out.println("X4=" + new BigInteger(serverLoginResponse.getGx4().getEncoded(true)).toString(16)); + System.out.println("KP{x3}: {V=" + new BigInteger(serverLoginResponse.getKnowledgeProofForX3().getV().getEncoded(true)).toString(16) + "; r=" + serverLoginResponse.getKnowledgeProofForX3().getr().toString(16) + "}"); + System.out.println("KP{x4}: {V=" + new BigInteger(serverLoginResponse.getKnowledgeProofForX4().getV().getEncoded(true)).toString(16) + "; r=" + serverLoginResponse.getKnowledgeProofForX4().getr().toString(16) + "}"); + System.out.println("Beta="+new BigInteger(serverLoginResponse.getBeta().getEncoded(true)).toString(16)); + System.out.println("KP{Beta}: {V="+new BigInteger(serverLoginResponse.getKnowledgeProofForBeta().getV().getEncoded(true)).toString(16)+", r="+serverLoginResponse.getKnowledgeProofForBeta().getr().toString(16)+"}"); + System.out.println(""); + + /* + * Third Pass + * The client recieves and valildates the server's response. + * It then creates and sends the final payload of the handshake. + * In the same pass, the client may send an explicit key confirmation string. This is optional but recommended. + */ + + Owl_AuthenticationFinish clientLoginEnd = client.authenticationFinish(serverLoginResponse); + + System.out.println("************ Third Pass ************"); + System.out.println("Client verifies the server's KP{x3}: OK"); + System.out.println("Client verifies the server's KP{x4}: OK"); + System.out.println("Client verifies the server's KP{Beta}: OK\n"); + + BigInteger clientKeyingMaterial = client.calculateKeyingMaterial(); + System.out.println("Client computes key material K=" + clientKeyingMaterial.toString(16)); + System.out.println(""); + + System.out.println("Client sends to Server: "); + System.out.println("Username used to login: " + clientLoginEnd.getClientId()); + System.out.println("Alpha="+new BigInteger(clientLoginEnd.getAlpha().getEncoded(true)).toString(16)); + System.out.println("KP{Alpha}: {V="+new BigInteger(clientLoginEnd.getKnowledgeProofForAlpha().getV().getEncoded(true)).toString(16)+", r="+clientLoginEnd.getKnowledgeProofForAlpha().getr().toString(16)+"}"); + System.out.println("r="+ clientLoginEnd.getR().toString(16)); + + /* The client sends an explicit key confirmation string. This is optional but recommended */ + Owl_KeyConfirmation clientKCPayload = client.initiateKeyConfirmation(clientKeyingMaterial); + System.out.println("MacTag=" + clientKCPayload.getMacTag().toString(16)); + System.out.println(""); + + /* Fourth pass + * The server receives the client payload, validates it and uses it to calculate its own key. + * In this pass, the server may send an explicit key confirmation string. This is optional but recommended. + */ + + System.out.println("********* Fourth Pass ***********"); + server.authenticationServerEnd(clientLoginEnd); + System.out.println("Server verifies the client's KP{Alpha}: OK"); + System.out.println("Server verifies the client's r: OK\n"); + + /* + * The server computes the keying material. + */ + BigInteger serverKeyingMaterial = server.calculateKeyingMaterial(); + System.out.println("Server computes key material K=" + serverKeyingMaterial.toString(16)); + System.out.println(""); + + // The server computes key confirmation string + Owl_KeyConfirmation serverKCPayload = server.initiateKeyConfirmation(serverKeyingMaterial); + server.validateKeyConfirmation(clientKCPayload, serverKeyingMaterial); + System.out.println("Server checks Client's MacTag for key confirmation: OK\n"); + + System.out.println("Server sends to Client:"); + System.out.println("MacTag=" + serverKCPayload.getMacTag().toString(16)); + System.out.println(""); + + System.out.println("********* After the fourth Pass ***********"); + client.validateKeyConfirmation(serverKCPayload, clientKeyingMaterial); + System.out.println("Client checks Server's MacTag for key confirmation: OK"); + System.out.println(""); + + /* + * You must derive a session key from the keying material applicable + * to whatever encryption algorithm you want to use. + */ + + BigInteger clientKey = deriveSessionKey(clientKeyingMaterial); + BigInteger serverKey = deriveSessionKey(serverKeyingMaterial); + + System.out.println("Client's session key \t clientKey=" + clientKey.toString(16)); + System.out.println("Server's session key \t ServerKey=" + serverKey.toString(16)); + } + + private static BigInteger deriveSessionKey(BigInteger keyingMaterial) + { + /* + * You should use a secure key derivation function (KDF) to derive the session key. + * + * For the purposes of this example, I'm just going to use a hash of the keying material. + */ + SavableDigest digest = SHA256Digest.newInstance(); + + byte[] keyByteArray = keyingMaterial.toByteArray(); + + byte[] output = new byte[digest.getDigestSize()]; + + digest.update(keyByteArray, 0, keyByteArray.length); + + digest.doFinal(output, 0); + + return new BigInteger(output); + } +} diff --git a/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java new file mode 100644 index 0000000000..79c893c2b2 --- /dev/null +++ b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java @@ -0,0 +1,98 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; + +public class Owl_ClientRegistrationTest +{ + @Test + public void testConstruction() + throws CryptoException + { + Owl_Curve curve = Owl_Curves.NIST_P256; + SecureRandom random = new SecureRandom(); + Digest digest = new SHA256Digest(); + String clientId = "clientId"; + char[] password = "password".toCharArray(); + + //should succeed + assertDoesNotThrow(() -> + new Owl_ClientRegistration(clientId, password, curve, digest, random) + ); + + //null clientId + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(null, password, curve, digest, random) + ); + + //null password + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, null, curve, digest, random) + ); + + //empty password + assertThrows(IllegalArgumentException.class, () -> + new Owl_ClientRegistration(clientId, "".toCharArray(), curve, digest, random) + ); + + //null curve + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, password, null, digest, random) + ); + + //null digest + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, password, curve, null, random) + ); + + //null random + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, password, curve, digest, null) + ); + } + + @Test + public void testSuccessfulExchange() + { + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration initialUserReg = assertDoesNotThrow(() -> + clientReg.initiateUserRegistration() + ); + } + + @Test + public void testStateValidation() + { + Owl_ClientRegistration clientReg = createClientReg(); + //Testing the client here only using the server for help + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration initialUserReg = assertDoesNotThrow(() -> + clientReg.initiateUserRegistration() + ); + + //try call registration twice with the same client + + assertThrows(IllegalStateException.class, () -> + clientReg.initiateUserRegistration() + ); + } + + private Owl_ClientRegistration createClientReg() + { + return new Owl_ClientRegistration("client", "password".toCharArray(), Owl_Curves.NIST_P256); + } + private Owl_ServerRegistration createServerReg() + { + return new Owl_ServerRegistration("server", Owl_Curves.NIST_P256); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java new file mode 100644 index 0000000000..135964d54c --- /dev/null +++ b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java @@ -0,0 +1,359 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import java.util.function.Function; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; + +import org.bouncycastle.crypto.digests.SHA256Digest; + +public class Owl_ClientTest +{ + + public void testConstruction() + throws CryptoException + { + Owl_Curve curve = Owl_Curves.NIST_P256; + SecureRandom random = new SecureRandom(); + Digest digest = new SHA256Digest(); + String clientId = "clientId"; + char[] password = "password".toCharArray(); + + //should succeed + assertDoesNotThrow(() -> + new Owl_Client(clientId, password, curve, digest, random) + ); + + //null clientId + assertThrows(NullPointerException.class, () -> + new Owl_Client(null, password, curve, digest, random) + ); + + //null password + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, null, curve, digest, random) + ); + + //empty password + assertThrows(IllegalArgumentException.class, () -> + new Owl_Client(clientId, "".toCharArray(), curve, digest, random) + ); + + //null curve + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, password, null, digest, random) + ); + + //null digest + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, password, curve, null, random) + ); + + //null random + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, password, curve, digest, null) + ); + } + @Test + public void testSuccessfulExchange() + throws CryptoException + { + + Owl_Server server = createServer(); + Owl_Client client = createClient(); + + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration clientRegistrationPayload = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverRegistrationPayload = serverReg.registerUseronServer(clientRegistrationPayload); + + Owl_AuthenticationInitiate clientLoginInitialPayload = client.authenticationInitiate(); + Owl_AuthenticationServerResponse serverLoginResponsePayload = server.authenticationServerResponse(clientLoginInitialPayload, serverRegistrationPayload); + + Owl_AuthenticationFinish clientLoginFinishPayload = client.authenticationFinish(serverLoginResponsePayload); + server.authenticationServerEnd(clientLoginFinishPayload); + + BigInteger clientKeyingMaterial = client.calculateKeyingMaterial(); + BigInteger serverKeyingMaterial = server.calculateKeyingMaterial(); + + Owl_KeyConfirmation clientKCPayload = client.initiateKeyConfirmation(clientKeyingMaterial); + Owl_KeyConfirmation serverKCPayload = server.initiateKeyConfirmation(serverKeyingMaterial); + + client.validateKeyConfirmation(serverKCPayload, clientKeyingMaterial); + server.validateKeyConfirmation(clientKCPayload, serverKeyingMaterial); + + assertEquals(serverKeyingMaterial, clientKeyingMaterial); + + } + @Test + public void testIncorrectPassword() + throws CryptoException + { + Owl_Server server = createServer(); + Owl_Client client = createClientWithWrongPassword(); + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration clientRegistrationPayload = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverRegistrationPayload = serverReg.registerUseronServer(clientRegistrationPayload); + + Owl_AuthenticationInitiate clientLoginInitialPayload = client.authenticationInitiate(); + Owl_AuthenticationServerResponse serverLoginResponsePayload = server.authenticationServerResponse(clientLoginInitialPayload, serverRegistrationPayload); + + Owl_AuthenticationFinish clientLoginFinishPayload = client.authenticationFinish(serverLoginResponsePayload); + assertThrows(CryptoException.class, () -> + server.authenticationServerEnd(clientLoginFinishPayload) + ); +/* + BigInteger clientKeyingMaterial = client.calculateKeyingMaterial(); + BigInteger serverKeyingMaterial = server.calculateKeyingMaterial(); + + Owl_KeyConfirmation clientKCPayload = client.initiateKeyConfirmation(clientKeyingMaterial); + Owl_KeyConfirmation serverKCPayload = server.initiateKeyConfirmation(serverKeyingMaterial); + + // Validate incorrect passwords result in a CryptoException + + assertThrows(CryptoException.class, + () -> server.validateKeyConfirmation(clientKCPayload, serverKeyingMaterial), + "Server validation should fail with incorrect password"); + + assertThrows(CryptoException.class, + () -> client.validateKeyConfirmation(serverKCPayload, clientKeyingMaterial), + "Client validation should fail with incorrect password");*/ + } + @Test + public void testStateValidation() + throws CryptoException + { + + Owl_Server server = createServer(); + Owl_Client client = createClient(); + + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + // We're testing client here. Server and registration objects are just used for help. + + Owl_InitialRegistration clientRegistrationPayload = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverRegistrationPayload = serverReg.registerUseronServer(clientRegistrationPayload); + + // --- LOGIN INITIALIZATION CHECKS --- + assertEquals(Owl_Client.STATE_INITIALISED, client.getState()); + + Owl_AuthenticationInitiate clientLoginInitialPayload = client.authenticationInitiate(); + assertEquals(Owl_Client.STATE_LOGIN_INITIALISED, client.getState()); + + // client cannot initiate login twice + assertThrows(IllegalStateException.class, + client::authenticationInitiate); + + Owl_AuthenticationServerResponse serverLoginResponsePayload = + server.authenticationServerResponse(clientLoginInitialPayload, serverRegistrationPayload); + + // --- LOGIN FINISH CHECKS --- + // cannot calculate key before finishing authentication + assertThrows(IllegalStateException.class, + client::calculateKeyingMaterial); + + Owl_AuthenticationFinish clientLoginFinishPayload = + client.authenticationFinish(serverLoginResponsePayload); + assertEquals(Owl_Client.STATE_LOGIN_FINISHED, client.getState()); + + // cannot finish login twice + assertThrows(IllegalStateException.class, + () -> client.authenticationFinish(serverLoginResponsePayload)); + + server.authenticationServerEnd(clientLoginFinishPayload); + // --- KEY CALCULATION CHECKS --- + // cannot start key confirmation before calculating key + assertThrows(IllegalStateException.class, + () -> client.initiateKeyConfirmation(null)); + + // cannot validate key confirmation before calculating key + assertThrows(IllegalStateException.class, + () -> client.validateKeyConfirmation(null, null)); + + BigInteger rawClientKey = client.calculateKeyingMaterial(); + assertEquals(Owl_Client.STATE_KEY_CALCULATED, client.getState()); + + // cannot calculate key twice + assertThrows(IllegalStateException.class, + client::calculateKeyingMaterial); + + BigInteger rawServerKey = server.calculateKeyingMaterial(); + + // --- KEY CONFIRMATION CHECKS --- + + Owl_KeyConfirmation clientKC = client.initiateKeyConfirmation(rawClientKey); + assertEquals(Owl_Client.STATE_KC_INITIALISED, client.getState()); + + // cannot initiate key confirmation twice + assertThrows(IllegalStateException.class, + () -> client.initiateKeyConfirmation(rawClientKey)); + + Owl_KeyConfirmation serverKC = server.initiateKeyConfirmation(rawServerKey); + + client.validateKeyConfirmation(serverKC, rawClientKey); + assertEquals(Owl_Client.STATE_KC_VALIDATED, client.getState()); + + // cannot validate key confirmation twice + assertThrows(IllegalStateException.class, + () -> client.validateKeyConfirmation(serverKC, rawClientKey)); + + // server should validate successfully at the end + server.validateKeyConfirmation(clientKC, rawServerKey); + } + @Test + public void testAuthenticationFinish() + throws CryptoException + { + Owl_Client client = createClient(); + Owl_Server server = createServer(); + Owl_AuthenticationServerResponse serverResponse = runExchangeUntilPass3(client, server); + //should succeed + Owl_AuthenticationFinish clientLoginFinishPayload = assertDoesNotThrow(() -> client.authenticationFinish(serverResponse)); + + Owl_Client client2 = createClient(); + Owl_Server server2 = createServer(); + Owl_AuthenticationServerResponse serverResponse2 = runExchangeUntilPass3(client2, server2); + // Helper to modify server response + Function+ * This class encapsulates the values involved in the Schnorr + * zero-knowledge proof used in the Owl protocol. + */ +public class ECSchnorrZKP +{ + + /** + * The value of V = G x [v]. + */ + private final ECPoint V; + + /** + * The value of r = v - d * c mod n + */ + private final BigInteger r; + + /** + * Constructor for ECSchnorrZKP + * + * @param V Prover's commitment V = G x [v] + * @param r Prover's response r to a challenge c, r = v - d * c mod n + */ + public ECSchnorrZKP(ECPoint V, BigInteger r) + { + this.V = V; + this.r = r; + } + + /** + * Get the prover's commitment V = G x [v] where G is a base point on the elliptic curve and v is an ephemeral secret + * @return The prover's commitment + */ + public ECPoint getV() + { + return V; + } + + /** + * Get the prover's response r to the challenge c, r = v - d * c mod n where d is the prover's private key + * @return The prover's response + */ + public BigInteger getr() + { + return r; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java new file mode 100644 index 0000000000..fb826a33d1 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationFinish.java @@ -0,0 +1,103 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by the client during the third pass of an Owl exchange. + *
+ * Each {@link Owl_Client} creates and sends an instance + * of this payload to the {@link Owl_Server} after validating the previous payload + * {@link Owl_AuthenticationServerResponse}. + * The payload to send should be created via + * {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)}. + *
+ * Each {@link Owl_Client} must also validate the payload + * received from the {@link Owl_Server}, which is done by the same function + * {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)}. + */ +public class Owl_AuthenticationFinish +{ + /** + * Client's unique Id + */ + private final String clientId; + /** + * The value alpha = (x2 x pi) * [X1 + X3 + X4]. + */ + private final ECPoint alpha; + + /** + * The zero Knowledge proof for alpha. + *
+ * This is a class {@link ECSchnorrZKP} with two fields, containing {v * [G], r} for x2pi. + *
+ */ + private final ECSchnorrZKP knowledgeProofForAlpha; + + /** + * The value of r = x1 - t.h mod n + */ + private final BigInteger r; + + /** + * Constructor of Owl_AuthenticationFinish + * @param clientId Client's identity + * @param alpha The public key alpha sent by the client in the third pass + * @param knowledgeProofForAlpha The zero-knowledge proof for the knowledge of the private key for alpha + * @param r The response r for proving the knowledge of t=H(usrname||password) mod n. + */ + public Owl_AuthenticationFinish( + String clientId, + ECPoint alpha, + ECSchnorrZKP knowledgeProofForAlpha, + BigInteger r) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(alpha, "alpha"); + Owl_Util.validateNotNull(r, "r"); + Owl_Util.validateNotNull(knowledgeProofForAlpha, "knowledgeProofForAlpha"); + + this.clientId = clientId; + this.knowledgeProofForAlpha = knowledgeProofForAlpha; + this.alpha = alpha; + this.r = r; + } + + /** + * Get the client's identity (also known as username) + * @return The client's identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get the public key alpha = (x2 x pi) * [X1 + X3 + X4]. sent by the client in the third pass + * @return The public key alpha + */ + public ECPoint getAlpha() + { + return alpha; + } + + /** + * Get the response r as part of the zero-knowledge proof for proving the knowledge of t, r = x1 - t.h mod n where x1 is the ephemeral private key for the public key X1 sent in the first pass of Owl + * @return The response r sent by the client in the third pass + */ + public BigInteger getR() + { + return r; + } + + /** + * Get the Schnorr zero-knowledge proof for the knowledge of the private key (x2 x pi) for the public key alpha + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForAlpha() + { + return knowledgeProofForAlpha; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java new file mode 100644 index 0000000000..1b67e2d860 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationInitiate.java @@ -0,0 +1,123 @@ +package org.example; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by the client in the first pass of an Owl exchange. + *+ * Each {@link Owl_Client} creates and sends an instance + * of this payload to the {@link Owl_Server}. + * The payload to send should be created via + * {@link Owl_Client#authenticationInitiate()}. + */ +public class Owl_AuthenticationInitiate +{ + + /** + * Unique identifier for the client (this is the username) + *
+ * ClientId must not be the same as the server unique identifier, + *
+ */ + private final String clientId; + + /** + * The value of g^x1 + */ + private final ECPoint gx1; + + /** + * The value of g^x2 + */ + private final ECPoint gx2; + + /** + * The zero knowledge proof for x1. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x1. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX1; + + /** + * The zero knowledge proof for x2. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x2. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX2; + + /** + * Constructor of Owl_AuthenticationInitiate + * @param clientId the client's identity (or username) + * @param gx1 The public key X1 = x1 * [G] + * @param gx2 The public key X2 = x2 * [G] + * @param knowledgeProofForX1 The zero-knowledge proof for proving the knowledge of x1 + * @param knowledgeProofForX2 The zero-knowledge proof for proving the knowledge of x2 + */ + public Owl_AuthenticationInitiate( + String clientId, + ECPoint gx1, + ECPoint gx2, + ECSchnorrZKP knowledgeProofForX1, + ECSchnorrZKP knowledgeProofForX2) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(gx1, "gx1"); + Owl_Util.validateNotNull(gx2, "gx2"); + Owl_Util.validateNotNull(knowledgeProofForX1, "knowledgeProofForX1"); + Owl_Util.validateNotNull(knowledgeProofForX2, "knowledgeProofForX2"); + + this.clientId = clientId; + this.gx1 = gx1; + this.gx2 = gx2; + this.knowledgeProofForX1 = knowledgeProofForX1; + this.knowledgeProofForX2 = knowledgeProofForX2; + } + + /** + * Get the client's identity (or username) + * @return The client's identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get the client's public key X1 = x1 * [G] in the first pass of Owl + * @return The client's public key X1 + */ + public ECPoint getGx1() + { + return gx1; + } + + /** + * Get the client's public key X2 = x2 * [G] in the first pass of Owl + * @return The client's public key X2 + */ + public ECPoint getGx2() + { + return gx2; + } + + /** + * Get the zero-knowledge proof for the knowledge of x1 + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX1() + { + return knowledgeProofForX1; + } + + /** + * Get the zero-knowledge proof for the knowledge of x2 + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX2() + { + return knowledgeProofForX2; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java new file mode 100644 index 0000000000..f0e69ce515 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_AuthenticationServerResponse.java @@ -0,0 +1,167 @@ +package org.example; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by the server during the second pass of an Owl exchange. + *+ * Each {@link Owl_Server} creates and sends an instance + * of this payload to the {@link Owl_Client}. + * The payload to send should be created via + * {@link Owl_Server#authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)}. + *
+ * Each {@link Owl_Server} must also validate the payload + * received from the {@link Owl_Client} which comes in the form of {@link Owl_AuthenticationInitiate}. + * The {@link Owl_Server} must retrieve the {@link Owl_FinishRegistration} + * from wherever the server securely stored the initial login information. + * The received payload should be validated via the same function (in the same call). + */ +public class Owl_AuthenticationServerResponse +{ + /** + * Unique identifier for the server. + *
+ * Must not be the same as the unique identifier for the client (client username). + *
+ */ + private final String serverId; + + /** + * The value of g^x3 + */ + private final ECPoint gx3; + + /** + * The value of g^x4 + */ + private final ECPoint gx4; + + /** + * The zero knowledge proof for x3. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x3. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX3; + + /** + * The zero knowledge proof for x4. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x4. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX4; + + /** + * The value for beta = (X1 + X2 + X3)^x4pi + */ + private final ECPoint beta; + + /** + * The zero knowledge proof for beta. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {g^v, r} for x4pi. + *
+ */ + private final ECSchnorrZKP knowledgeProofForBeta; + + /** + * Constructor for Owl_AuthenticationServerResponse + * @param serverId The server's identity + * @param gx3 The public key X3 = x3 * [G] + * @param gx4 The public key X4 = x4 * [G] + * @param knowledgeProofForX3 The zero-knowledge proof for the knowledge of x3 + * @param knowledgeProofForX4 The zero-knowledge proof for the knowledge of x4 + * @param beta The public key beta = (x4 x pi) * [X1 + X2 + X3] + * @param knowledgeProofForBeta The zero-knowledge proof for the knowledge of (x4 x pi) for beta + */ + public Owl_AuthenticationServerResponse( + String serverId, + ECPoint gx3, + ECPoint gx4, + ECSchnorrZKP knowledgeProofForX3, + ECSchnorrZKP knowledgeProofForX4, + ECPoint beta, + ECSchnorrZKP knowledgeProofForBeta) + { + Owl_Util.validateNotNull(serverId, "serverId"); + Owl_Util.validateNotNull(gx3, "gx3"); + Owl_Util.validateNotNull(gx4, "gx4"); + Owl_Util.validateNotNull(knowledgeProofForX3, "knowledgeProofForX3"); + Owl_Util.validateNotNull(knowledgeProofForX4, "knowledgeProofForX4"); + Owl_Util.validateNotNull(beta, "beta"); + Owl_Util.validateNotNull(knowledgeProofForBeta, "knowledgeProofForBeta"); + + this.serverId = serverId; + this.gx3 = gx3; + this.gx4 = gx4; + this.knowledgeProofForX3 = knowledgeProofForX3; + this.knowledgeProofForX4 = knowledgeProofForX4; + this.beta = beta; + this.knowledgeProofForBeta = knowledgeProofForBeta; + } + + /** + * Get the server's identity + * @return The server's identity + */ + public String getServerId() + { + return serverId; + } + + /** + * Get the public key X3 = x3 * [G] + * @return The public key X3 + */ + public ECPoint getGx3() + { + return gx3; + } + + /** + * Get the public key X4 = x4 * [G] + * @return The public key X4 + */ + public ECPoint getGx4() + { + return gx4; + } + + /** + * Get the zero-knowledge proof for the knowledge of x3 for X3 = x3 * [G] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX3() + { + return knowledgeProofForX3; + } + + /** + * Get the zero-knowledge proof for the knowledge of x4 for X4 = x4 * [G] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX4() + { + return knowledgeProofForX4; + } + + /** + * Get the public key beta = (x4 x pi) * [X1 + X2 + X3] + * @return The public key beta + */ + public ECPoint getBeta() + { + return beta; + } + + /** + * Get the zero-knowledge proof for the knowledge of (x4 x pi) for the public key beta = (x4 x pi) * [X1 + X2 + X3] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForBeta() + { + return knowledgeProofForBeta; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java new file mode 100644 index 0000000000..0dbc2b26cf --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Client.java @@ -0,0 +1,586 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; + +/** + * A client in the Elliptic Curve Owl key exchange protocol. + *+ * Owl is an augmented password-authenticated key exchange (PAKE) protocol, + * defined by Feng Hao, Samiran Bag, Liqun Chen, and Paul C. van Oorschot + * in the paper "Owl: An Augmented Password-Authenticated Key Exchange Scheme". + * Owl builds on the same idea as J-PAKE (using Schnorr zero-knowledge proofs to enforce participants to follow the protocol specification honestly), + * but it is augmented to to provide the additional protection against server compromise. + * While J-PAKE is symmetric, Owl is asymmetric. Like J-PAKE, Owl can be implemented in either elliptic curve (EC) or finite field (FF) settings. + * This implementation is done in the elliptic curve setting. + * Any elliptic curve that is suitable for cryptography can be used for Owl (same for J-PAKE). + *
+ * In Owl, there is one client and one server communicating between each other. + * An instance of {@link Owl_Server} represents one server, and + * an instance of {@link Owl_Client} represents one client. + * These together make up the main machine through which the protocol is facilitated. + *
+ * There are two distinct phases that can be taken in Owl: user registration - where the client registers + * as a new user on the server; login - where an existing user (client) attempts to log in and establish a shared session key with the server + * based on password authentication. Using the session key, the user (client) can perform further actions (e.g., password update) over a secure channel, + * but these actions are outside the scope of this key exchange program. + *
+ * The user registration phase involves only one pass of communication from the client to the server. It's assumed that the following communication is done over a secure channel (e.g., using an out-of-band method). + *
After the third pass, the client has completed client-to-server authentication. At this point, both the client and the server can compute a shared key. They call the following methods to compute a raw keying material and do key confirmation. + *
+ * 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). + *
+ * The explicit key confirmation process is optional but highly recommended. It does not affect the round efficiency and adds a negligible computational cost. The client-to-server key confirmation string + * can be piggybacked in the third pass along with {@link Owl_AuthenticationFinish}. The server-to-client key confirmation string can be sent in the next pass together with encrypted data. + * If you do not execute key confirmation, then there is no assurance that both client and server have actually derived the same key, and the ensuing secure communication may fail. + * If the key confirmation succeeds, then the keys are guaranteed to be the same on both sides. + *
+ * The key confirmation process is implemented as specified in + * NIST SP 800-56A Revision 3, + * Section 5.9.1 Unilateral Key Confirmation for Key Agreement Schemes. + *
+ * This class is stateful and NOT threadsafe. + * Each instance should only be used for ONE complete Owl key exchange + * (i.e. a new {@link Owl_Server} and {@link Owl_Client} should be constructed for each new Owl key exchange). + */ +public class Owl_Client +{ + /* + * Possible internal states. Used for state checking. + */ + public static final int STATE_INITIALISED = 0; + public static final int STATE_LOGIN_INITIALISED = 10; + public static final int STATE_LOGIN_FINISHED = 20; + public static final int STATE_KEY_CALCULATED = 30; + public static final int STATE_KC_INITIALISED = 40; + public static final int STATE_KC_VALIDATED = 50; + + /** + * Unique identifier of this client. + * The client and server in the exchange must NOT share the same id. + * clientId is the same as the username in this PAKE + */ + private final String clientId; + + /** + * 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 final Digest digest; + + /** + * Source of secure random data. + */ + private final SecureRandom random; + + /** + * The serverId of the server in this exchange. + */ + private String serverId; + + private ECCurve.AbstractFp ecCurve; + private BigInteger q; + private BigInteger h; + private BigInteger n; + private ECPoint g; + /** + * Client's x1 + */ + private BigInteger x1; + /** + * Client's x2. + */ + private BigInteger x2; + /** + Client's gx1. + */ + private ECPoint gx1; + /** + * Client's gx2. + */ + private ECPoint gx2; + /** + * Server's gx3. + */ + private ECPoint gx3; + /** + * Server's gx4. + */ + private ECPoint gx4; + /** + * Client's user specified secret t = H(username||password) mod n + */ + private BigInteger t; + /** + * Shared secret used for authentication pi = H(t) mod n + */ + private BigInteger pi; + /** + * The current state. + * See theSTATE_* constants for possible values.
+ */
+ private int state;
+ /**
+ * The raw key K used to calculate a session key.
+ */
+ private ECPoint rawKey;
+ /**
+ * Client's alpha
+ */
+ private ECPoint alpha;
+ /**
+ * ECSchnorrZKP knowledge proof for x1, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX1;
+ /**
+ * ECSchnorrZKP knowledge proof for x2, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX2;
+ /**
+ * ECSchnorrZKP knowledge proof for x3, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX3;
+ /**
+ * ECSchnorrZKP knowledge proof for x4, using {@link ECSchnorrZKP}
+ */
+ private ECSchnorrZKP knowledgeProofForX4;
+
+ /**
+ * Convenience constructor for a new {@link Owl_Client} that uses
+ * the {@link Owl_Curves#NIST_P256} elliptic curve,
+ * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+ * + * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_Client( + String clientId, + char[] password) + { + this( + clientId, + password, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_Client} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_Client( + String clientId, + char[] password, + Owl_Curve curve) + { + this( + clientId, + password, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_Client}. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}.
+ *
+ * @param clientId unique identifier of this client.
+ * The server and client 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 curve elliptic curve.
+ * See {@link Owl_Curves} for standard curves
+ * @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 NullPointerException if any argument is null
+ * @throws IllegalArgumentException if password is empty
+ */
+ public Owl_Client(
+ String clientId,
+ char[] password,
+ Owl_Curve curve,
+ Digest digest,
+ SecureRandom random)
+ {
+ Owl_Util.validateNotNull(clientId, "clientId");
+ Owl_Util.validateNotNull(password, "password");
+ Owl_Util.validateNotNull(curve, "curve params");
+ Owl_Util.validateNotNull(digest, "digest");
+ Owl_Util.validateNotNull(random, "random");
+ if (password.length == 0)
+ {
+ throw new IllegalArgumentException("Password must not be empty.");
+ }
+
+ this.clientId = clientId;
+
+ /*
+ * Create a defensive copy so as to fully encapsulate the password.
+ *
+ * This array will contain the password for the lifetime of this
+ * client 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 = Arrays.copyOf(password, password.length);
+
+ this.ecCurve = curve.getCurve();
+ this.g = curve.getG();
+ this.h = curve.getH();
+ this.n = curve.getN();
+ this.q = curve.getQ();
+
+ this.digest = digest;
+ this.random = random;
+
+ this.state = STATE_INITIALISED;
+ }
+
+ /**
+ * Gets the current state of this client.
+ * See the STATE_* constants for possible values.
+ *
+ * @return the state of the client
+ */
+ public int getState()
+ {
+ return this.state;
+ }
+ /**
+ * Creates and returns the payload to send to the server as part of the first pass of the protocol.
+ *
+ * Must be called prior to {@link #authenticationFinish(Owl_AuthenticationServerResponse)} + * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_INITIALISED}. + * + * @return {@link Owl_AuthenticationInitiate} + * @throws IllegalStateException if called multiple times. + */ + public Owl_AuthenticationInitiate authenticationInitiate() + { + if (this.state >= STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Login already initiated by " + clientId); + } + this.t = calculateT(); + + this.pi = calculatePi(); + + this.x1 = Owl_Util.generateX1(n, random); + this.x2 = Owl_Util.generateX1(n, random); + + this.gx1 = Owl_Util.calculateGx(g, x1); + this.gx2 = Owl_Util.calculateGx(g, x2); + + this.knowledgeProofForX1 = Owl_Util.calculateZeroknowledgeProof(g, n, x1, gx1, digest, clientId, random); + this.knowledgeProofForX2 = Owl_Util.calculateZeroknowledgeProof(g, n, x2, gx2, digest, clientId, random); + + this.state = STATE_LOGIN_INITIALISED; + + return new Owl_AuthenticationInitiate(clientId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2); + } + + /** + * Finalises the login authentication protocol by creating and sending the final payload to the server. + * Validates the payload sent by the {@link Owl_Server#authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)} after login initilisation. + *
+ * Must be called prior to {@link #calculateKeyingMaterial()}. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_FINISHED}. + * + * @param authenticationServerResponse The payload sent by {@link Owl_Server#authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)} and to be validated. + * + * @return {@link Owl_AuthenticationFinish} + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #authenticationInitiate()} or called multiple times. + */ + public Owl_AuthenticationFinish authenticationFinish(Owl_AuthenticationServerResponse authenticationServerResponse) + throws CryptoException + { + if (this.state >= STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Login authentication already finished by: " + clientId); + } + if (this.state < STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Must initialise login authentication before calling authentication finish for: " + clientId); + } + this.serverId = authenticationServerResponse.getServerId(); + this.gx3 = authenticationServerResponse.getGx3(); + this.gx4 = authenticationServerResponse.getGx4(); + ECPoint beta = authenticationServerResponse.getBeta(); + ECSchnorrZKP knowledgeProofForX3 = authenticationServerResponse.getKnowledgeProofForX3(); + ECSchnorrZKP knowledgeProofForX4 = authenticationServerResponse.getKnowledgeProofForX4(); + ECSchnorrZKP knowledgeProofForBeta = authenticationServerResponse.getKnowledgeProofForBeta(); + + ECPoint betaG = Owl_Util.calculateGA(gx1, gx2, gx3); + + Owl_Util.validateParticipantIdsDiffer(clientId, authenticationServerResponse.getServerId()); + Owl_Util.validateZeroknowledgeProof(g, gx3, knowledgeProofForX3, q, n, ecCurve, h, authenticationServerResponse.getServerId(), digest); + Owl_Util.validateZeroknowledgeProof(g, gx4, knowledgeProofForX4, q, n, ecCurve, h, authenticationServerResponse.getServerId(), digest); + Owl_Util.validateZeroknowledgeProof(betaG, beta, knowledgeProofForBeta, q, n, ecCurve, h, authenticationServerResponse.getServerId(), digest); + + ECPoint alphaG = Owl_Util.calculateGA(gx1, gx3, gx4); + BigInteger x2pi = Owl_Util.calculateX2s(n, x2, pi); + ECPoint alpha = Owl_Util.calculateA(alphaG, x2pi); + + ECSchnorrZKP knowledgeProofForAlpha = Owl_Util.calculateZeroknowledgeProof(alphaG, n, x2pi, alpha, digest, clientId, random); + + this.rawKey = Owl_Util.calculateKeyingMaterial(gx4, x2, x2pi, beta); + + BigInteger hTranscript = Owl_Util.calculateTranscript(rawKey, clientId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2, serverId, gx3, gx4, + knowledgeProofForX3, knowledgeProofForX4, beta, knowledgeProofForBeta, alpha, knowledgeProofForAlpha, digest); + + BigInteger r = Owl_Util.calculateR(x1, t, hTranscript, n); + + this.state = STATE_LOGIN_FINISHED; + + return new Owl_AuthenticationFinish(clientId, alpha, knowledgeProofForAlpha, r); + } + + + /** + * 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 Owl_Client}). + *
+ * The keying material will be identical for client and server if and only if + * the login password is the same as the password stored by the server. i.e. If the client and + * server do not share the same password, then each 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 perform explicit + * key confirmation. See {@link Owl_Client} for details on how to execute + * key confirmation. + *
+ * If the passwords used for registration and login are different then this will be caught + * when validating r during {@link Owl_Server#authenticationServerEnd(Owl_AuthenticationFinish)}. + *
+ * {@link #authenticationFinish(Owl_AuthenticationServerResponse)} 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}. + * + * @return raw key material + * @throws IllegalStateException if called prior to {@link #authenticationFinish(Owl_AuthenticationServerResponse)}, + * or if called multiple times. + */ + public BigInteger calculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Key already calculated for " + clientId); + } + if (this.state < STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Login authentication must be finished prior to creating key for " + clientId); + } + + /* + * 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. + */ + Arrays.fill(password, (char)0); + this.password = null; + + BigInteger keyingMaterial = rawKey.normalize().getAffineXCoord().toBigInteger(); + /* + * 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.t = null; + this.pi = null; + this.rawKey = null; + + /* + * Do not clear gx* yet, since those are needed by key confirmation. + */ + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /** + * Creates and returns the payload to send to the server as part of Key Confirmation. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_INITIALISED}. + * + * @return {@link Owl_KeyConfirmation} + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public Owl_KeyConfirmation initiateKeyConfirmation(BigInteger keyingMaterial) + { + if (this.state >= STATE_KC_INITIALISED) + { + throw new IllegalStateException("Key Confirmation already initiated for " + this.clientId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to initialising key confirmation for " + this.clientId); + } + + BigInteger macTag = Owl_Util.calculateMacTag( + this.clientId, + this.serverId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest); + + this.state = STATE_KC_INITIALISED; + + return new Owl_KeyConfirmation(clientId, macTag); + } + + /** + * Validates the key confirmation payload received by the server. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_VALIDATED}. + * + * @param keyConfirmationPayload The key confirmation payload received from the other client. + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public void validateKeyConfirmation(Owl_KeyConfirmation keyConfirmationPayload, BigInteger keyingMaterial) + throws CryptoException + { + if (this.state >= STATE_KC_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for this payload for" + clientId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to validating this payload for " + this.clientId); + } + Owl_Util.validateParticipantIdsDiffer(clientId, keyConfirmationPayload.getId()); + Owl_Util.validateParticipantIdsEqual(this.serverId, keyConfirmationPayload.getId()); + + Owl_Util.validateMacTag( + this.clientId, + this.serverId, + this.gx1, + this.gx2, + this.gx3, + this.gx4, + keyingMaterial, + this.digest, + keyConfirmationPayload.getMacTag()); + + + /* + * Clear the rest of the fields. + */ + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_KC_VALIDATED; + } + + private BigInteger calculateT() + { + try + { + // t = H(username||password). Prepend each item with its byte length (int) to set clear boundary + return Owl_Util.calculateT(n, + String.valueOf(clientId.getBytes().length) + clientId + + String.valueOf(password.length) + new String(password), digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } + + private BigInteger calculatePi() + { + try + { + return Owl_Util.calculatePi(n, t, digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java new file mode 100644 index 0000000000..02377e75ba --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ClientRegistration.java @@ -0,0 +1,266 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; +/** + * A client in the Owl key exchange protocol specifically for the user registration phase. + *
+ * There is one client and one server communicating between each other. + * An instance of {@link Owl_ServerRegistration} represents one server, and + * an instance of {@link Owl_ClientRegistration} represents one client. + * These together make up the main machine through which user registration is facilitated. + *
+ * To execute the registration, construct an {@link Owl_ServerRegistration} on the server end, + * and construct an {@link Owl_ClientRegistration} on the client end. + * Each Owl registration will need a new and distinct {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration}. + * You cannot use the same {@link Owl_ServerRegistration} or {@link Owl_ClientRegistration} for multiple exchanges. + *
+ * For user login go to {@link Owl_Client} and {@link Owl_Server}. + * To execute the user registration phase, both + * {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration} must be constructed. + *
+ * The following communication between {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration}, must be + * facilitated over a secure communications channel as the leakage of the payload sent, + * would allow an attacker to reconstruct the secret password. + *
+ * Call the following methods in this order, the client initiates every exchange. + *
+ * This class is stateful and NOT threadsafe. + * Each instance should only be used for ONE complete Owl exchange + * (i.e. a new {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration} should be constructed for each new Owl exchange). + */ +public class Owl_ClientRegistration{ + /* + * Possible state for user registration. + */ + public static final boolean REGISTRATION_NOT_CALLED = false; + public static final boolean REGISTRATION_CALLED = true; + /** + * Unique identifier of this client. + *
+ * The client and server in the exchange must NOT share the same id. + *
+ */ + private final String clientId; + /** + * Shared secret. This only contains the secret between construction + * and the call to {@link #initiateUserRegistration()}. + *+ * i.e. When {@link #initiateUserRegistration()} is called, this buffer is overwritten with 0's, + * and the field is set to null. + *
+ */ + private char[] password; + /** + * Digest to use during calculations. + */ + private final Digest digest; + /** + * Source of secure random data. + */ + private final SecureRandom random; + /** + * Client's user specified secret t = H(username||password) mod n + */ + private BigInteger t; + + private BigInteger n; + private ECPoint g; + /** + * Checks if user registration is called more than once. + */ + private boolean registrationState; + /** + * Get the status of the user registration. + * I.E. whether or not this server has registered a user already. + * See theREGSITRATION_* constants for possible values.
+ * @return True if the user has been registered or false otherwise
+ */
+ public boolean getRegistrationState()
+ {
+ return this.registrationState;
+ }
+
+ /**
+ * Convenience constructor for a new {@link Owl_ClientRegistration} that uses
+ * the {@link Owl_Curves#NIST_P256} elliptic curve,
+ * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+ * + * After construction, the {@link #getRegistrationState() state} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 #initiateUserRegistration()} is called). + * Caller should clear the input password as soon as possible. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_ClientRegistration( + String clientId, + char[] password) + { + this( + clientId, + password, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_ClientRegistration} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getRegistrationState() state} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param clientId unique identifier of this client.. + * The server and client 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 #initiateUserRegistration()} is called). + * Caller should clear the input password as soon as possible. + * @param curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_ClientRegistration( + String clientId, + char[] password, + Owl_Curve curve) + { + this( + clientId, + password, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_ClientRegistration}. + *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param clientId unique identifier of this client. + * The server and client 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 #initiateUserRegistration()} is called). + * Caller should clear the input password as soon as possible. + * @param curve elliptic curve. + * See {@link Owl_Curves} for standard curves + * @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 NullPointerException if any argument is null + * @throws IllegalArgumentException if password is empty + */ + public Owl_ClientRegistration( + String clientId, + char[] password, + Owl_Curve curve, + Digest digest, + SecureRandom random) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(password, "password"); + Owl_Util.validateNotNull(curve, "curve params"); + Owl_Util.validateNotNull(digest, "digest"); + Owl_Util.validateNotNull(random, "random"); + if (password.length == 0) + { + throw new IllegalArgumentException("Password must not be empty."); + } + + this.clientId = clientId; + + /* + * Create a defensive copy so as to fully encapsulate the password. + * + * This array will contain the password for the lifetime of this + * client BEFORE {@link #initiateUserRegistration()} is called. + * + * i.e. When {@link #initiateUserRegistration()} 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 = Arrays.copyOf(password, password.length); + this.g = curve.getG(); + this.n = curve.getN(); + + this.digest = digest; + this.random = random; + + this.registrationState = REGISTRATION_NOT_CALLED; + } + /** + * Initiates user registration with the server. Creates the registration payload {@link Owl_InitialRegistration} and sends it to the server. + * MUST be sent over a secure channel. + *
+ * Must be called prior to {@link Owl_ServerRegistration#registerUseronServer(Owl_InitialRegistration)} + * @return {@link Owl_InitialRegistration} + * @throws IllegalStateException if this function is called more than once + */ + public Owl_InitialRegistration initiateUserRegistration() + { + if(this.registrationState) + { + throw new IllegalStateException("User login registration already begun by "+ clientId); + } + this.t = calculateT(); + + BigInteger pi = calculatePi(); + + ECPoint gt = Owl_Util.calculateGx(g, t); + + /* + * 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. + */ + Arrays.fill(password, (char)0); + this.password = null; + this.t = null; + this.registrationState = REGISTRATION_CALLED; + + return new Owl_InitialRegistration(clientId, pi, gt); + } + + private BigInteger calculateT() + { + try + { + // t = H(username||password). Prepend each item with its byte length (int) to set clear boundary + return Owl_Util.calculateT(n, + String.valueOf(clientId.getBytes().length) + clientId + + String.valueOf(password.length) + new String(password), digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } + private BigInteger calculatePi() + { + try + { + return Owl_Util.calculatePi(n, t, digest); + } + catch (CryptoException e) + { + throw Exceptions.illegalStateException(e.getMessage(), e); + } + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java new file mode 100644 index 0000000000..6282abe784 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curve.java @@ -0,0 +1,201 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; + +/** + * A pre-computed elliptic curve over a prime field, in short-Weierstrass form for use during an Owl exchange. + *
+ * In general, Owl can use any elliptic curve or prime order group + * that is suitable for public key cryptography. + *
+ * See {@link Owl_Curves} for convenient standard curves. + *
+ * NIST publishes + * many curves with different forms and levels of security. + */ +public class Owl_Curve +{ + private final ECCurve.AbstractFp curve; + private final ECPoint g; + + /** + * Constructs a new {@link Owl_Curve}. + *
+ * In general, you should use one of the pre-approved curves from + * {@link Owl_Curves}, 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. + * + * @param q The prime field modulus + * @param a The curve coefficient a + * @param b The curve coefficient b + * @param n The order of the base point G + * @param h The co-factor + * @param g_x The x coordinate of the base point G + * @param g_y The y coordinate of the base point G + * + * @throws NullPointerException if any argument is null + * @throws IllegalArgumentException if any of the above validations fail + */ + public Owl_Curve(BigInteger q, BigInteger a, BigInteger b, BigInteger n, BigInteger h, BigInteger g_x, BigInteger g_y) + { + Owl_Util.validateNotNull(a, "a"); + Owl_Util.validateNotNull(b, "b"); + Owl_Util.validateNotNull(q, "q"); + Owl_Util.validateNotNull(n, "n"); + Owl_Util.validateNotNull(h, "h"); + Owl_Util.validateNotNull(g_x, "g_x"); + Owl_Util.validateNotNull(g_y, "g_y"); + + /* + * Don't skip the checks on user-specified groups. + */ + + /* + * Note that these checks do not guarantee that n and q are prime. + * We just have reasonable certainty that they are prime. + */ + if (!q.isProbablePrime(20)) + { + throw new IllegalArgumentException("Field size q must be prime"); + } + + if (a.compareTo(BigInteger.ZERO) < 0 || a.compareTo(q) >= 0) + { + throw new IllegalArgumentException("The parameter 'a' is not in the field [0, q-1]"); + } + + if (b.compareTo(BigInteger.ZERO) < 0 || b.compareTo(q) >= 0) + { + throw new IllegalArgumentException("The parameter 'b' is not in the field [0, q-1]"); + } + + BigInteger d = calculateDeterminant(q, a, b); + if (d.equals(BigInteger.ZERO)) + { + throw new IllegalArgumentException("The curve is singular, i.e the discriminant is equal to 0 mod q."); + } + + if (!n.isProbablePrime(20)) + { + throw new IllegalArgumentException("The order n must be prime"); + } + + ECCurve.Fp curve = new ECCurve.Fp(q, a, b, n, h); + ECPoint g = curve.createPoint(g_x, g_y); + + if (!g.isValid()) + { + throw new IllegalArgumentException("The base point G does not lie on the curve."); + } + + this.curve = curve; + this.g = g; + } + + /** + * Internal package-private constructor used by the pre-approved + * groups in {@link Owl_Curves}. + * These pre-approved curves can avoid the expensive checks. + */ + Owl_Curve(ECCurve.AbstractFp curve, ECPoint g) + { + Owl_Util.validateNotNull(curve, "curve"); + Owl_Util.validateNotNull(g, "g"); + Owl_Util.validateNotNull(curve.getOrder(), "n"); + Owl_Util.validateNotNull(curve.getCofactor(), "h"); + + this.curve = curve; + this.g = g; + } + + /** + * Get the elliptic curve + * @return The curve + */ + public ECCurve.AbstractFp getCurve() + { + return curve; + } + + /** + * Get the base point G + * @return G + */ + public ECPoint getG() + { + return g; + } + + /** + * Get the curve coefficient a + * @return a + */ + public BigInteger getA() + { + return curve.getA().toBigInteger(); + } + + /** + * Get the curve coefficient b + * @return b + */ + public BigInteger getB() + { + return curve.getB().toBigInteger(); + } + + /** + * Get n, the order of the base point + * @return n + */ + public BigInteger getN() + { + return curve.getOrder(); + } + + /** + * Get the co-factor h of the curve + * @return h + */ + public BigInteger getH() + { + return curve.getCofactor(); + } + + /** + * Get the prime field modulus q of the curve + * @return q + */ + public BigInteger getQ() + { + return curve.getQ(); + } + + private static BigInteger calculateDeterminant(BigInteger q, BigInteger a, BigInteger b) + { + BigInteger a3x4 = a.multiply(a).mod(q).multiply(a).mod(q).shiftLeft(2); + BigInteger b2x27 = b.multiply(b).mod(q).multiply(BigInteger.valueOf(27)); + return a3x4.add(b2x27).mod(q); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java new file mode 100644 index 0000000000..41c43f181e --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Curves.java @@ -0,0 +1,50 @@ +package org.example; + +import org.bouncycastle.asn1.x9.X9ECParameters; +import org.bouncycastle.crypto.ec.CustomNamedCurves; +import org.bouncycastle.math.ec.ECCurve; + +/** + * Standard pre-computed elliptic curves for use by Owl. + * (Owl can use pre-computed elliptic curves or prime order groups, same as DSA and Diffie-Hellman.) + *
+ * This class contains some convenient constants for use as input for + * constructing {@link Owl_Client}s and {@link Owl_Server}. + *
+ * The prime order groups below are taken from NIST SP 800-186, + * "Recommendations for Discrete Logarithm-based Cryptography: Elliptic Curve Domain Parameters", + * published by NIST. + */ +public class Owl_Curves +{ + /** + * From NIST. + * 128-bit security. + */ + public static final Owl_Curve NIST_P256; + + /** + * From NIST. + * 192-bit security. + */ + public static final Owl_Curve NIST_P384; + + /** + * From NIST. + * 256-bit security. + */ + public static final Owl_Curve NIST_P521; + + static + { + NIST_P256 = getCurve("P-256"); + NIST_P384 = getCurve("P-384"); + NIST_P521 = getCurve("P-521"); + } + + private static Owl_Curve getCurve(String curveName) + { + X9ECParameters x9 = CustomNamedCurves.getByName(curveName); + return new Owl_Curve((ECCurve.AbstractFp)x9.getCurve(), x9.getG()); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java new file mode 100644 index 0000000000..513b27471c --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_FinishRegistration.java @@ -0,0 +1,121 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The final payload generated by the {@link Owl_ServerRegistration} during the user registration of a Owl exchange. + * This payload is to be stored securely by the server. + *
+ * Each {@link Owl_ServerRegistration} creates and sends an instance + * of this payload to be stored securely. + * The payload to send should be created via + * {@link Owl_ServerRegistration#registerUseronServer(Owl_InitialRegistration)}. + */ +public class Owl_FinishRegistration +{ + /** + * Unique identifier for the client in this registration phase. + *
+ * Must be different to the server's unique identifier. + *
+ */ + private final String clientId; + + /** + * The value x3 * [G]. + */ + private final ECPoint gx3; + + /** + * The zero knowledge proof for x3. + *+ * This is a class {@link ECSchnorrZKP} with two fields, containing {v*[G], r} for x3. + *
+ */ + private final ECSchnorrZKP knowledgeProofForX3; + + /** + * The value of pi = H(t), where t = H(Username||password) mod(n) + */ + private final BigInteger pi; + + /** + * The value of T = t * [G] + */ + private final ECPoint gt; + + /** + * Constructor of Owl_FinishRegistration + * @param clientId The client identity (or username) + * @param knowledgeProofForX3 The zero-knowledge proof for the knowledge of x3 for X3 + * @param gx3 The public key X3= [G] * x3 + * @param pi pi = H(t) where t=H(username || password) mod n + * @param gt T = t * [G] + */ + public Owl_FinishRegistration( + String clientId, + ECSchnorrZKP knowledgeProofForX3, + ECPoint gx3, + BigInteger pi, + ECPoint gt) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(pi, "pi"); + Owl_Util.validateNotNull(gt, "gt"); + Owl_Util.validateNotNull(gx3, "gx3"); + Owl_Util.validateNotNull(knowledgeProofForX3, "knowledgeProofForX3"); + + this.clientId = clientId; + this.knowledgeProofForX3 = knowledgeProofForX3; + this. gx3 = gx3; + this.pi = pi; + this.gt = gt; + } + + /** + * Get the client identity + * @return The client identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get pi = H(t), where t = H(Username||password) mod(n) + * @return pi + */ + public BigInteger getPi() + { + return pi; + } + + /** + * Get T = t * [G] + * @return T + */ + public ECPoint getGt() + { + return gt; + } + + /** + * Get X3 = x3 * [G] + * @return X3 + */ + public ECPoint getGx3() + { + return gx3; + } + + /** + * Get the zero-knowledge proof for the knowledge of x3 for X3 = x3 * [G] + * @return {@link ECSchnorrZKP} + */ + public ECSchnorrZKP getKnowledgeProofForX3() + { + return knowledgeProofForX3; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java new file mode 100644 index 0000000000..bfa87751df --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_InitialRegistration.java @@ -0,0 +1,84 @@ +package org.example; + +import java.math.BigInteger; + +import org.bouncycastle.math.ec.ECPoint; + +/** + * The payload sent by {@link Owl_ClientRegistration}, during the user registration phase of an Owl exchange. + *+ * The {@link Owl_ClientRegistration} creates and sends an instance + * of this payload to the {@link Owl_ServerRegistration}. + * The payload to send should be created via + * {@link Owl_ClientRegistration#initiateUserRegistration()}. + *
+ * Each {@link Owl_ServerRegistration} must also validate the payload + * received from the {@link Owl_ClientRegistration}. + * The received payload should be validated via + * {@link Owl_ServerRegistration#registerUseronServer(Owl_InitialRegistration)}. + */ +public class Owl_InitialRegistration +{ + /** + * Unique identifier for the client (same as username). + *
+ * Must not be the same as the unique identifier for the server. + *
+ */ + private final String clientId; + /** + * The value of pi = H(t), where t = H(Username||password) mod n + */ + private final BigInteger pi; + /** + * The value of T = t * [G] + */ + private final ECPoint gt; + + /** + * Constructor of Owl_InitialRegistration + * @param clientId Client identity (or username) + * @param pi pi = H(t), where t = H(Username||password) mod(n) + * @param gt T = t * [G] + */ + public Owl_InitialRegistration( + String clientId, + BigInteger pi, + ECPoint gt) + { + Owl_Util.validateNotNull(clientId, "clientId"); + Owl_Util.validateNotNull(pi, "pi"); + Owl_Util.validateNotNull(gt, "gt"); + + this.clientId = clientId; + this.pi = pi; + this.gt = gt; + } + + /** + * Get the client identity (or username) + * @return The client identity + */ + public String getClientId() + { + return clientId; + } + + /** + * Get pi = H(t), where t = H(Username||password) mod(n) + * @return pi + */ + public BigInteger getPi() + { + return pi; + } + + /** + * Get T = t * [G] + * @return T + */ + public ECPoint getGt() + { + return gt; + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java new file mode 100644 index 0000000000..99f7986d9b --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_KeyConfirmation.java @@ -0,0 +1,64 @@ +package org.example; + +import java.math.BigInteger; + +/** + * The payload sent/received during the explicit key confirmation stage of the protocol, + *+ * Both {@link Owl_Client} and {@link Owl_Server} create and send an instance + * of this payload to the other. + * The payload to send should be created via + * {@link Owl_Client#initiateKeyConfirmation(BigInteger)} + * or {@link Owl_Server#initiateKeyConfirmation(BigInteger)}. + *
+ * Both {@link Owl_Client} and {@link Owl_Server} must also validate the payload + * received from the other. + * The received payload should be validated via + * {@link Owl_Client#validateKeyConfirmation(Owl_KeyConfirmation, BigInteger)} + * {@link Owl_Server#validateKeyConfirmation(Owl_KeyConfirmation, BigInteger)} + */ +public class Owl_KeyConfirmation +{ + + /** + * The id of either {@link Owl_Client} or {@link Owl_Server} who created/sent this payload. + */ + private final String id; + + /** + * The value of MacTag, as computed by the key confirmation round. + * + * @see Owl_Util#calculateMacTag + */ + private final BigInteger macTag; + + /** + * Constructor of Owl_KeyConfirmation + * @param id The identity of the sender + * @param magTag The key confirmation string + */ + public Owl_KeyConfirmation(String id, BigInteger magTag) + { + this.id = id; + this.macTag = magTag; + } + + /** + * Get the identity of the sender + * @return The identity of the sender + */ + public String getId() + { + return id; + } + + /** + * Get the MAC tag which serves as a key confirmation string + * @return The MAC tag + */ + public BigInteger getMacTag() + { + return macTag; + } + +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java new file mode 100644 index 0000000000..6fa4a4c892 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Server.java @@ -0,0 +1,453 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; + +/** + * A server in the Elliptic Curve Owl key exchange protocol. + *
+ * See {@link Owl_Client} for more details about Owl. + */ +public class Owl_Server +{ + + /* + * Possible internal states. Used for state checking. + */ + public static final int STATE_INITIALISED = 0; + public static final int STATE_LOGIN_INITIALISED = 10; + public static final int STATE_LOGIN_FINISHED = 20; + public static final int STATE_KEY_CALCULATED = 30; + public static final int STATE_KC_INITIALISED = 40; + public static final int STATE_KC_VALIDATED = 50; + + /** + * Unique identifier of this server. + * The client and server in the exchange must NOT share the same id. + */ + private final String serverId; + + /** + * Unique identifier for the client in the exchange. + */ + private String clientId; + + /** + * Digest to use during calculations. + */ + private final Digest digest; + + /** + * Source of secure random data. + */ + private final SecureRandom random; + + private ECCurve.AbstractFp ecCurve; + private BigInteger q; + private BigInteger h; + private BigInteger n; + private ECPoint g; + + /** + * Server's x4. + */ + private BigInteger x4; + /** + * Client's gx1. + */ + private ECPoint gx1; + /** + * Client's gx2. + */ + private ECPoint gx2; + /** + * Server's gx3. + */ + private ECPoint gx3; + /** + * Server's gx4. + */ + private ECPoint gx4; + /** + * Shared secret used for authentication pi = H(t) mod n + */ + private BigInteger pi; + /** + * Client's T, Password verifier stored on server + */ + private ECPoint gt; + /** + * Server's beta value used in login authentication + */ + private ECPoint beta; + /** + * ECSchnorrZKP knowledge proof for x1, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX1; + /** + * ECSchnorrZKP knowledge proof for x2, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX2; + /** + * ECSchnorrZKP knowledge proof for x3, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX3; + /** + * ECSchnorrZKP knowledge proof for x4, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForX4; + /** + * ECSchnorrZKP knowledge proof for beta, using {@link ECSchnorrZKP} + */ + private ECSchnorrZKP knowledgeProofForBeta; + /** + * The raw key K used to calculate a session key. + */ + private ECPoint rawKey; + /** + * The current state. + * See the STATE_* constants for possible values. + */ + private int state; + + /** + * Convenience constructor for a new {@link Owl_Server} that uses + * the {@link Owl_Curves#NIST_P256} elliptic curve, + * a SHA-256 digest, and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @throws NullPointerException if any argument is null + */ + public Owl_Server( + String serverId) + { + this( + serverId, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_Server} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @param curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + */ + public Owl_Server( + String serverId, + Owl_Curve curve) + { + this( + serverId, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_Server}. + *
+ * After construction, the {@link #getState() state} will be {@link #STATE_INITIALISED}.
+ *
+ * @param serverId unique identifier of this server.
+ * The client and server in the exchange must NOT share the same id.
+ * @param curve elliptic curve
+ * See {@link Owl_Curves} for standard curves
+ * @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 x3 and x4, and for the zero knowledge proofs
+ * @throws NullPointerException if any argument is null
+ */
+ public Owl_Server(
+ String serverId,
+ Owl_Curve curve,
+ Digest digest,
+ SecureRandom random)
+ {
+ Owl_Util.validateNotNull(serverId, "serverId");
+ Owl_Util.validateNotNull(curve, "curve params");
+ Owl_Util.validateNotNull(digest, "digest");
+ Owl_Util.validateNotNull(random, "random");
+
+ this.serverId = serverId;
+
+ this.ecCurve = curve.getCurve();
+ this.g = curve.getG();
+ this.h = curve.getH();
+ this.n = curve.getN();
+ this.q = curve.getQ();
+
+ this.digest = digest;
+ this.random = random;
+
+ this.state = STATE_INITIALISED;
+ }
+
+ /**
+ * Gets the current state of this server.
+ * See the STATE_* constants for possible values.
+ *
+ * @return The state of the server
+ */
+ public int getState()
+ {
+ return this.state;
+ }
+
+ /**
+ * Validates the payload sent by {@link Owl_Client#authenticationInitiate()} by {@link Owl_Client}, and then creates a new {@link Owl_AuthenticationServerResponse} payload and sends it to the {@link Owl_Client}.
+ *
+ * Must be called prior to {@link #authenticationServerEnd(Owl_AuthenticationFinish)}. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_INITIALISED}. + * + * @param authenticationInitiate payload sent by {@link Owl_Client#authenticationInitiate()} to be validated and used for further calculation. + * @param userLoginCredentials comes from the server where it stored the user login credentials as part of the user login registration. + * @return {@link Owl_AuthenticationServerResponse} + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called multiple times. + */ + public Owl_AuthenticationServerResponse authenticationServerResponse( + Owl_AuthenticationInitiate authenticationInitiate, + Owl_FinishRegistration userLoginCredentials) + throws CryptoException + { + if (this.state >= STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Response to client authentication initiation already created by " + serverId); + } + this.clientId = authenticationInitiate.getClientId(); + this.gx1 = authenticationInitiate.getGx1(); + this.gx2 = authenticationInitiate.getGx2(); + + this.knowledgeProofForX1 = authenticationInitiate.getKnowledgeProofForX1(); + this.knowledgeProofForX2 = authenticationInitiate.getKnowledgeProofForX2(); + + Owl_Util.validateParticipantIdsDiffer(serverId, authenticationInitiate.getClientId()); + Owl_Util.validateZeroknowledgeProof(g, gx1, knowledgeProofForX1, q, n, ecCurve, h, authenticationInitiate.getClientId(), digest); + Owl_Util.validateZeroknowledgeProof(g, gx2, knowledgeProofForX2, q, n, ecCurve, h, authenticationInitiate.getClientId(), digest); + + this.x4 = Owl_Util.generateX1(n, random); + + this.gx4 = Owl_Util.calculateGx(g, x4); + + this.knowledgeProofForX4 = Owl_Util.calculateZeroknowledgeProof(g, n, x4, gx4, digest, serverId, random); + + this.gx3 = userLoginCredentials.getGx3(); + this.pi = userLoginCredentials.getPi(); + this.knowledgeProofForX3 = userLoginCredentials.getKnowledgeProofForX3(); + this.gt = userLoginCredentials.getGt(); + + Owl_Util.validateParticipantIdsEqual(this.clientId, userLoginCredentials.getClientId()); + + ECPoint betaG = Owl_Util.calculateGA(gx1, gx2, gx3); + BigInteger x4pi = Owl_Util.calculateX2s(n, x4, pi); + this.beta = Owl_Util.calculateA(betaG, x4pi); + this.knowledgeProofForBeta = Owl_Util.calculateZeroknowledgeProof(betaG, n, x4pi, beta, digest, serverId, random); + + this.state = STATE_LOGIN_INITIALISED; + + return new Owl_AuthenticationServerResponse(serverId, gx3, gx4, knowledgeProofForX3, knowledgeProofForX4, beta, knowledgeProofForBeta); + } + + /** + * Validates the payload received from the client during the third pass of the Owl protocol. + * Must be called prior to {@link #calculateKeyingMaterial()}. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_LOGIN_FINISHED}. + * + * @param authenticationFinish payload sent by {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)} to be validated. + * + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #authenticationServerResponse(Owl_AuthenticationInitiate, Owl_FinishRegistration)}, or multiple times + */ + public void authenticationServerEnd(Owl_AuthenticationFinish authenticationFinish) + throws CryptoException + { + if (this.state >= STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Server's authentication ending already called by " + serverId); + } + if (this.state < STATE_LOGIN_INITIALISED) + { + throw new IllegalStateException("Authentication server response required before authentication finish by " + this.serverId); + } + + ECPoint alpha = authenticationFinish.getAlpha(); + ECPoint alphaG = Owl_Util.calculateGA(gx1, gx3, gx4); + ECSchnorrZKP knowledgeProofForAlpha = authenticationFinish.getKnowledgeProofForAlpha(); + + Owl_Util.validateZeroknowledgeProof(alphaG, alpha, knowledgeProofForAlpha, q, n, ecCurve, h, clientId, digest); + + BigInteger x4pi = Owl_Util.calculateX2s(n, x4, pi); + this.rawKey = Owl_Util.calculateKeyingMaterial(gx2, x4, x4pi, alpha); + + BigInteger hTranscript = Owl_Util.calculateTranscript(rawKey, clientId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2, serverId, gx3, gx4, + knowledgeProofForX3, knowledgeProofForX4, beta, knowledgeProofForBeta, alpha, knowledgeProofForAlpha, digest); + + Owl_Util.validateR(authenticationFinish.getR(), gx1, hTranscript, gt, g, n); + Owl_Util.validateParticipantIdsDiffer(serverId, authenticationFinish.getClientId()); + Owl_Util.validateParticipantIdsEqual(this.clientId, authenticationFinish.getClientId()); + + this.state = STATE_LOGIN_FINISHED; + } + + /** + * 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 Owl_Server}). + *
+ * The keying material will be identical for client and server if and only if + * the login password is the same as the password stored by the server. i.e. If the client and + * server do not share the same password, then each will derive a different key. + * Rememeber, the server does not explicitly hold the password, but a secret value derived from the password + * sent to the server by the client during user registration. + * Therefore, if you immediately start using a key derived from + * the keying material, then you must handle detection of incorrect keys. + * Validation of the r value also detects if passwords are different between user registration and user login. + * If you want to check the equality of the key materials derived at the two sides explicitly, you can perform explicit + * key confirmation. See {@link Owl_Server} for details on how to execute + * key confirmation. + *
+ * {@link #authenticationServerEnd(Owl_AuthenticationFinish)} must be called prior to this method. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KEY_CALCULATED}. + * + * @return The raw key material produced by the Owl key exchange process + * @throws IllegalStateException if called prior to {@link #authenticationServerEnd(Owl_AuthenticationFinish)}, + * or if called multiple times. + */ + public BigInteger calculateKeyingMaterial() + { + if (this.state >= STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Key already calculated for " + serverId); + } + if (this.state < STATE_LOGIN_FINISHED) + { + throw new IllegalStateException("Server must validate client's final payload prior to creating key for " + serverId); + } + + BigInteger keyingMaterial = rawKey.normalize().getAffineXCoord().toBigInteger(); + /* + * 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 key x4 are leaked, + * the attacker might be able to brute-force the password. + */ + this.x4 = null; + this.beta = null; + this.gt = null; + this.rawKey = null; + + /* + * Do not clear gx* yet, since those are needed by key confirmation. + */ + this.state = STATE_KEY_CALCULATED; + + return keyingMaterial; + } + + /** + * Creates and returns the payload to send to the client as part of Key Confirmation. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_INITIALISED}. + * + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @return {@link Owl_KeyConfirmation} + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public Owl_KeyConfirmation initiateKeyConfirmation(BigInteger keyingMaterial) + { + if (this.state >= STATE_KC_INITIALISED) + { + throw new IllegalStateException("Key confirmation payload already created for " + this.serverId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated prior to creating key confirmation payload for " + this.serverId); + } + + BigInteger macTag = Owl_Util.calculateMacTag( + this.serverId, + this.clientId, + this.gx3, + this.gx4, + this.gx1, + this.gx2, + keyingMaterial, + this.digest); + + this.state = STATE_KC_INITIALISED; + + return new Owl_KeyConfirmation(serverId, macTag); + } + + /** + * Validates the payload received from the client as part of Key Confirmation. + *
+ * See {@link Owl_Client} for more details on Key Confirmation. + *
+ * After execution, the {@link #getState() state} will be {@link #STATE_KC_VALIDATED}. + * + * @param keyConfirmationPayload The key confirmation payload received from the client.. + * @param keyingMaterial The keying material as returned from {@link #calculateKeyingMaterial()}. + * @throws CryptoException if validation fails. + * @throws IllegalStateException if called prior to {@link #calculateKeyingMaterial()}, or multiple times + */ + public void validateKeyConfirmation(Owl_KeyConfirmation keyConfirmationPayload, BigInteger keyingMaterial) + throws CryptoException + { + if (this.state >= STATE_KC_VALIDATED) + { + throw new IllegalStateException("Validation already attempted for key confirmation payload by " + serverId); + } + if (this.state < STATE_KEY_CALCULATED) + { + throw new IllegalStateException("Keying material must be calculated validated prior to validating key confirmation payload for " + this.serverId); + } + Owl_Util.validateParticipantIdsDiffer(serverId, keyConfirmationPayload.getId()); + Owl_Util.validateParticipantIdsEqual(this.clientId, keyConfirmationPayload.getId()); + + Owl_Util.validateMacTag( + this.serverId, + this.clientId, + this.gx3, + this.gx4, + this.gx1, + this.gx2, + keyingMaterial, + this.digest, + keyConfirmationPayload.getMacTag()); + + /* + * Clear the rest of the fields. + */ + this.gx1 = null; + this.gx2 = null; + this.gx3 = null; + this.gx4 = null; + + this.state = STATE_KC_VALIDATED; + } +} diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java new file mode 100644 index 0000000000..4e165f6765 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_ServerRegistration.java @@ -0,0 +1,188 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.CryptoServicesRegistrar; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.Exceptions; + +/** + * A server in the Owl key exchange protocol specifically for the user registration phase. + *
+ * See {@link Owl_ClientRegistration} for more details on the user registration in Owl. + *
+ * This class is stateful and NOT threadsafe.
+ * Each instance should only be used for ONE complete Owl registration phase
+ * (i.e. a new {@link Owl_ServerRegistration} and {@link Owl_ClientRegistration} should be constructed for each new Owl exchange).
+ */
+public class Owl_ServerRegistration
+{
+ /*
+ * Possible state for user registration.
+ */
+ public static final boolean REGISTRATION_NOT_CALLED = false;
+ public static final boolean REGISTRATION_CALLED = true;
+ /**
+ * Unique identifier of this server.
+ * The client and server in the exchange must NOT share the same id.
+ */
+ private final String serverId;
+ /**
+ * Digest to use during calculations.
+ */
+ private final Digest digest;
+
+ /**
+ * Source of secure random data.
+ */
+ private final SecureRandom random;
+
+ private ECCurve.AbstractFp ecCurve;
+ private BigInteger h;
+ private BigInteger q;
+ private BigInteger n;
+ private ECPoint g;
+ /**
+ * Checks if user registration is called more than once.
+ */
+ private boolean registrationState;
+
+ /**
+ * Check's the status of the user registration
+ * I.E. whether or not this server has registered a user already.
+ * See the REGSITRATION_* constants for possible values.
+ * @return true if the user has been registered or false otherwise
+ */
+ public boolean getRegistrationState()
+ {
+ return this.registrationState;
+ }
+ /**
+ * Convenience constructor for a new {@link Owl_ServerRegistration} that uses
+ * the {@link Owl_Curves#NIST_P256} elliptic curve,
+ * a SHA-256 digest, and a default {@link SecureRandom} implementation.
+ *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @throws NullPointerException if any argument is null + */ + public Owl_ServerRegistration( + String serverId) + { + this( + serverId, + Owl_Curves.NIST_P256); + } + + /** + * Convenience constructor for a new {@link Owl_ServerRegistration} that uses + * a SHA-256 digest and a default {@link SecureRandom} implementation. + *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param serverId unique identifier of this server. + * The server and client in the exchange must NOT share the same id. + * @param curve elliptic curve + * See {@link Owl_Curves} for standard curves. + * @throws NullPointerException if any argument is null + */ + public Owl_ServerRegistration( + String serverId, + Owl_Curve curve) + { + this( + serverId, + curve, + SHA256Digest.newInstance(), + CryptoServicesRegistrar.getSecureRandom()); + } + + /** + * Construct a new {@link Owl_ServerRegistration}. + *
+ * After construction, the {@link #getRegistrationState() registrationState} will be {@link #REGISTRATION_NOT_CALLED}. + * + * @param serverId unique identifier of this server. + * The client and server in the exchange must NOT share the same id. + * @param curve elliptic curve; see {@link Owl_Curves} for standard curves + * @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 x3 and x4, and for the zero knowledge proofs + * @throws NullPointerException if any argument is null + */ + public Owl_ServerRegistration( + String serverId, + Owl_Curve curve, + Digest digest, + SecureRandom random) + { + Owl_Util.validateNotNull(serverId, "serverId"); + Owl_Util.validateNotNull(curve, "curve params"); + Owl_Util.validateNotNull(digest, "digest"); + Owl_Util.validateNotNull(random, "random"); + + this.serverId = serverId; + this.ecCurve = curve.getCurve(); + this.q = curve.getQ(); + this.h = curve.getH(); + this.g = curve.getG(); + this.n = curve.getN(); + + this.digest = digest; + this.random = random; + + this.registrationState = REGISTRATION_NOT_CALLED; + } + /** + * Initiates user registration with the server. Creates the registration payload {@link Owl_InitialRegistration} and sends it to the server. + * MUST be sent over a secure channel. + *
+ * Must be called prior to {@link #registerUseronServer(Owl_InitialRegistration)} + * @throws IllegalStateException if this function is called more than once + */ + + /** + * Receives the payload sent by the client as part of user registration, and stores necessary values in the server. + *
+ * Must be called after {@link Owl_ClientRegistration#initiateUserRegistration()} by the {@link Owl_Client}. + * @param userLoginRegistrationReceived {@link Owl_InitialRegistration} + * @return {@link Owl_FinishRegistration} + * @throws IllegalStateException if this functions is called more than once. + * @throws CryptoException if validation of the payload fails + */ + public Owl_FinishRegistration registerUseronServer( + Owl_InitialRegistration userLoginRegistrationReceived + ) + throws CryptoException + { + if(this.registrationState) + { + throw new IllegalStateException("Server has already registrered this payload, by "+ serverId); + } + BigInteger x3 = Owl_Util.generateX1(n, random); + + ECPoint gx3 = Owl_Util.calculateGx(g, x3); + + ECSchnorrZKP knowledgeProofForX3 = Owl_Util.calculateZeroknowledgeProof(g, n, x3, gx3, digest, serverId, random); + + String clientId = userLoginRegistrationReceived.getClientId(); + BigInteger pi = userLoginRegistrationReceived.getPi(); + ECPoint gt = userLoginRegistrationReceived.getGt(); + + Owl_Util.validateParticipantIdsDiffer(clientId, serverId); + if (pi.compareTo(BigInteger.ONE)==-1 || pi.compareTo(n.subtract(BigInteger.ONE)) == 1) { + throw new CryptoException("pi is not in the range of [1, n-1]. for " + serverId); + } + Owl_Util.validatePublicKey(gt, ecCurve, q, h); + this.registrationState = REGISTRATION_CALLED; + + return new Owl_FinishRegistration(clientId, knowledgeProofForX3, gx3, pi, gt); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java new file mode 100644 index 0000000000..f9c5195e5d --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/agreement/owl/Owl_Util.java @@ -0,0 +1,794 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.Mac; +import org.bouncycastle.crypto.macs.HMac; +import org.bouncycastle.crypto.params.KeyParameter; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.BigIntegers; +import org.bouncycastle.util.Strings; + +/** + * Primitives needed for an Owl exchange. + *
+ * The recommended way to perform an Owl exchange is by using + * an {@link Owl_Server} and {@link Owl_Client} + * (and for user registration {@link Owl_ClientRegistration} and {@link Owl_ServerRegistration}). + * Internally, those two classes call these primitive + * operations in {@link Owl_Util}. + *
+ * The primitives, however, can be used without a {@link Owl_Server} + * or {@link Owl_Client} if needed. + */ +public class Owl_Util +{ + static final BigInteger ZERO = BigInteger.valueOf(0); + static final BigInteger ONE = BigInteger.valueOf(1); + + /** + * Return a value that can be used as x1, x2, x3 or x4 during round 1. + *
+ * The returned value is a random value in the range [1, n-1].
+ * @param n The order of the base point
+ * @param random SecureRandom
+ * @return A random value within [1, n-1]
+ */
+ public static BigInteger generateX1(
+ BigInteger n,
+ SecureRandom random)
+ {
+ BigInteger min = ONE;
+ BigInteger max = n.subtract(ONE);
+ return BigIntegers.createRandomInRange(min, max, random);
+ }
+
+ /**
+ * Calculate X = [G] * x as done in round 1.
+ * Also used to calculate T = [G] * t as seen in the initial user registration
+ *
+ * @param g Base point G
+ * @param x Scalar x
+ * @return [G] * x
+ */
+ public static ECPoint calculateGx(
+ ECPoint g,
+ BigInteger x)
+ {
+ return g.multiply(x);
+ }
+
+ /**
+ * Calculate the combined public key GA = X1 * X3 * X4 from three public keys X1, X3 and X4.
+ *
+ * @param gx1 Public key X1
+ * @param gx3 Public key X3
+ * @param gx4 Public key X4
+ * @return The combined public key
+ * @throws CryptoException if any of the parameters are the infinity point on the elliptic curve.
+ */
+ public static ECPoint calculateGA(
+ ECPoint gx1,
+ ECPoint gx3,
+ ECPoint gx4) throws CryptoException {
+
+ if (gx1.isInfinity()) {
+ throw new CryptoException("Public key X1 cannot be infinity");
+ }
+ if (gx3.isInfinity()) {
+ throw new CryptoException("Public key X3 cannot be infinity");
+ }
+ if (gx4.isInfinity()) {
+ throw new CryptoException("Public key X4 cannot be infinity");
+ }
+
+ ECPoint Gx = gx1.add(gx3).add(gx4);
+
+ if (Gx.isInfinity()) {
+ throw new CryptoException("The combined public key cannot be infinity");
+ }
+
+ return Gx;
+ }
+
+
+ /**
+ * Calculate x2 * pi mod n as done in second pass of Owl.
+ *
+ * @param n The order of the base point
+ * @param x2 The private key x2
+ * @param pi pi = H(t) mod n
+ * @return x2 * pi mod n
+ */
+ public static BigInteger calculateX2s(
+ BigInteger n,
+ BigInteger x2,
+ BigInteger pi)
+ {
+ return x2.multiply(pi).mod(n);
+ }
+
+
+ /**
+ * Calculate alpha or beta as done in the second pass.
+ */
+ /**
+ * Calculate the public key from a base point and a scalar
+ * @param gA Base point
+ * @param x2pi Scalar
+ * @return [gA] * x2pi
+ */
+ public static ECPoint calculateA(
+ ECPoint gA,
+ BigInteger x2pi)
+ {
+ // alpha = Galpha^(x*pi)
+ return gA.multiply(x2pi);
+ }
+
+ /**
+ * Calculate t for the user's initial registration onto the server.
+ * t is calculated by H(username||password) mod n
+ *
+ * @param n The order of the base point
+ * @param string xxx
+ * @param digest Instance of Digest
+ * @return t = H(username||password) mod n
+ * @throws CryptoException if t = 0 mod n
+ */
+ public static BigInteger calculateT(
+ BigInteger n,
+ String string,
+ Digest digest)
+ throws CryptoException
+ {
+ BigInteger t = calculateHash(string, digest).mod(n);
+ if (t.signum() == 0)
+ {
+ throw new CryptoException("MUST ensure t is not equal to 0 modulo n");
+ }
+ return t;
+ }
+
+ /**
+ * Calculate pi = H(t) mod(n) for initial registration.
+ *
+ * @param n The order of the base point
+ * @param t t = H(username||password) mod n
+ * @param digest Instance of a one-way hash
+ * @return pi
+ * @throws CryptoException When pi = 0 mod n
+ */
+ public static BigInteger calculatePi(
+ BigInteger n,
+ BigInteger t,
+ Digest digest)
+ throws CryptoException
+ {
+ BigInteger pi = calculateHash(t, digest).mod(n);
+ if(pi.compareTo(BigInteger.ZERO)==0){
+ throw new CryptoException("MUST ensure that pi is not equal to 0 modulo n");
+ }
+ return pi;
+ }
+ /**
+ * Calculate a zero-knowledge proof of x using Schnorr's signature.
+ * The returned object has two fields {V = [G] * v, r = v-x*h} for x.
+ *
+ * @param generator The base point on the curve
+ * @param n The order of the base point
+ * @param x The private key
+ * @param X The public key
+ * @param digest Instance of a one-way hash
+ * @param userID The identity of the prover
+ * @param random Instance of a secure random number generator
+ * @return {@link ECSchnorrZKP}
+ */
+ public static ECSchnorrZKP calculateZeroknowledgeProof(
+ ECPoint generator,
+ BigInteger n,
+ BigInteger x,
+ ECPoint X,
+ Digest digest,
+ String userID,
+ SecureRandom random)
+ {
+
+ /* Generate a random v from [1, n-1], and compute V = G*v */
+ BigInteger v = BigIntegers.createRandomInRange(BigInteger.ONE, n.subtract(BigInteger.ONE), random);
+ ECPoint V = generator.multiply(v);
+ BigInteger h = calculateHashForZeroknowledgeProof(generator, V, X, userID, digest); // h
+ // r = v-x*h mod n
+
+ return new ECSchnorrZKP(V, v.subtract(x.multiply(h)).mod(n));
+ }
+
+
+ private static BigInteger calculateHash(
+ String string,
+ Digest digest)
+ {
+ digest.reset();
+
+ byte[] byteArray = Strings.toUTF8ByteArray(string);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+
+ return new BigInteger(output);
+ }
+
+ private static BigInteger calculateHash(
+ BigInteger bigint,
+ Digest digest)
+ {
+ digest.reset();
+
+ byte[] byteArray = bigint.toByteArray();
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+
+ return new BigInteger(output);
+ }
+
+
+ private static BigInteger calculateHashForZeroknowledgeProof(
+ ECPoint g,
+ ECPoint v,
+ ECPoint x,
+ String participantId,
+ Digest digest)
+ {
+ digest.reset();
+
+ updateDigestIncludingSize(digest, g);
+
+ updateDigestIncludingSize(digest, v);
+
+ updateDigestIncludingSize(digest, x);
+
+ updateDigestIncludingSize(digest, participantId);
+
+ byte[] output = new byte[digest.getDigestSize()];
+ digest.doFinal(output, 0);
+
+ return new BigInteger(output);
+ }
+
+
+ private static void updateDigestIncludingSize(
+ Digest digest,
+ ECPoint ecPoint)
+ {
+ byte[] byteArray = ecPoint.getEncoded(true);
+ digest.update(intToByteArray(byteArray.length), 0, 4);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ }
+
+ private static void updateDigestIncludingSize(
+ Digest digest,
+ String string)
+ {
+ byte[] byteArray = Strings.toUTF8ByteArray(string);
+ digest.update(intToByteArray(byteArray.length), 0, 4);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ }
+
+ private static void updateDigestIncludingSize(
+ Digest digest,
+ BigInteger bigInteger)
+ {
+ byte[] byteArray = bigInteger.toByteArray();
+ digest.update(intToByteArray(byteArray.length), 0, 4);
+ digest.update(byteArray, 0, byteArray.length);
+ Arrays.fill(byteArray, (byte)0);
+ }
+
+ /**
+ * Validates the zero-knowledge proof (generated by
+ * {@link #calculateZeroknowledgeProof(ECPoint, BigInteger, BigInteger, ECPoint, Digest, String, SecureRandom)})
+ * is correct.
+ *
+ * @param generator The base point on the curve
+ * @param X The public key
+ * @param zkp The zero-knowledge proof {@link ECSchnorrZKP}
+ * @param q The prime modulus for the coordinates
+ * @param n The oder of the base point
+ * @param curve The elliptic curve
+ * @param coFactor The co-factor
+ * @param userID The identity of the prover
+ * @param digest Instance of a one-way hash
+ * @throws CryptoException If the zero-knowledge proof is not correct
+ */
+
+ public static void validateZeroknowledgeProof(
+ ECPoint generator,
+ ECPoint X,
+ ECSchnorrZKP zkp,
+ BigInteger q,
+ BigInteger n,
+ ECCurve curve,
+ BigInteger coFactor,
+ String userID,
+ Digest digest)
+ throws CryptoException
+ {
+ ECPoint V = zkp.getV();
+ BigInteger r = zkp.getr();
+ /* ZKP: {V=G*v, r} */
+ BigInteger h = calculateHashForZeroknowledgeProof(generator, V, X, userID, digest);
+
+ // First, ensure X is a valid public key
+ validatePublicKey(X, curve, q, coFactor);
+
+ // Now check if V = G*r + X*h.
+ // Given that {G, X} are valid points on the curve, the equality implies that V is also a point on the curve.
+ if (!V.equals(generator.multiply(r).add(X.multiply(h.mod(n)))))
+ {
+ throw new CryptoException("Zero-knowledge proof validation failed: V = G*r + X*h must hold");
+ }
+
+ }
+
+ /**
+ * Validates that the given participant ids are not equal.
+ * (For the Owl exchange, each participant must use a unique id.)
+ *
+ * @param participantId1 The identity of the first participant
+ * @param participantId2 The identity of the second participate
+ * @throws CryptoException if the participantId strings are equal.
+ */
+ public static void validateParticipantIdsDiffer(
+ String participantId1,
+ String participantId2)
+ throws CryptoException
+ {
+ 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 (client/server).
+ *
+ * @param expectedParticipantId The expected participant's identity
+ * @param actualParticipantId The actual participate's identity
+
+ * @throws CryptoException if the participantId strings are equal.
+ */
+ public static void validateParticipantIdsEqual(
+ String expectedParticipantId,
+ String actualParticipantId)
+ throws CryptoException
+ {
+ 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.
+ *
+ * @param object object in question
+ * @param description name of the object (to be used in exception message)
+ * @throws NullPointerException if the object is null.
+ */
+ public static void validateNotNull(
+ Object object,
+ String description)
+ {
+ if (object == null)
+ {
+ throw new NullPointerException(description + " must not be null");
+ }
+ }
+
+ /**
+ * Calculates the keying material, which can be done after {@link Owl_Client#authenticationFinish(Owl_AuthenticationServerResponse)} has completed
+ * for client and when {@link Owl_Server#authenticationServerEnd(Owl_AuthenticationFinish)} has completed for server.
+ * 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 Owl_Server} or {@link Owl_Client}).
+ *
+ * KeyingMaterial = [Beta - [X4] * x2pi] * x2 + *+ * + * @param gx4 Public key X4 = x4 * [G] + * @param x2 Private key x2 + * @param x2pi Private key x2*pi + * @param B The public key Beta + * @return Raw key material K = [Beta - [X4] * x2pi] * x2) + */ + public static ECPoint calculateKeyingMaterial( + ECPoint gx4, + BigInteger x2, + BigInteger x2pi, + ECPoint B) + { + return B.subtract(gx4.multiply(x2pi)).multiply(x2); + } + + /** + * Calculates the 'transcript' which is, in order, everything communicated between the two parties + * in the login authentication protocol used to compute a common session key. + * Transcript is required for the calculation of r, as a compiler is used + * to prove the knowledge of t in an asymmetric setting. + * For more information: Owl: An Augmented Password-Authenticated + * Key Exchange Scheme + * + * @param rawKey Raw key material + * @param clientId Client identity + * @param gx1 Public key X1 + * @param gx2 Public key X2 + * @param knowledgeProofX1 Zero-knowledge proof for X1 + * @param knowledgeProofX2 Zero-knowledge proof for X2 + * @param serverId Server identity + * @param gx3 Public key X3 + * @param gx4 Public key X4 + * @param knowledgeProofX3 Zero-knowledge proof for X3 + * @param knowledgeProofX4 Zero-knowledege proof for X4 + * @param beta Public key Beta + * @param knowledgeProofBeta Zero-knowledge proof for Beta + * @param alpha Public key Alpha + * @param knowledgeProofAlpha Zero-knowledge proof for Alpha + * @param digest Instance of MAC + * @return Transcript + */ + public static BigInteger calculateTranscript( + ECPoint rawKey, + String clientId, + ECPoint gx1, + ECPoint gx2, + ECSchnorrZKP knowledgeProofX1, + ECSchnorrZKP knowledgeProofX2, + String serverId, + ECPoint gx3, + ECPoint gx4, + ECSchnorrZKP knowledgeProofX3, + ECSchnorrZKP knowledgeProofX4, + ECPoint beta, + ECSchnorrZKP knowledgeProofBeta, + ECPoint alpha, + ECSchnorrZKP knowledgeProofAlpha, + Digest digest) + { + digest.reset(); + + updateDigestIncludingSize(digest, rawKey); + updateDigestIncludingSize(digest, clientId); + updateDigestIncludingSize(digest, gx1); + updateDigestIncludingSize(digest, gx2); + updateDigestIncludingSize(digest, knowledgeProofX1.getV()); + updateDigestIncludingSize(digest, knowledgeProofX1.getr()); + updateDigestIncludingSize(digest, knowledgeProofX2.getV()); + updateDigestIncludingSize(digest, knowledgeProofX2.getr()); + updateDigestIncludingSize(digest, serverId); + updateDigestIncludingSize(digest, gx3); + updateDigestIncludingSize(digest, gx4); + updateDigestIncludingSize(digest, knowledgeProofX3.getV()); + updateDigestIncludingSize(digest, knowledgeProofX3.getr()); + updateDigestIncludingSize(digest, knowledgeProofX4.getV()); + updateDigestIncludingSize(digest, knowledgeProofX4.getr()); + updateDigestIncludingSize(digest, beta); + updateDigestIncludingSize(digest, knowledgeProofBeta.getV()); + updateDigestIncludingSize(digest, knowledgeProofBeta.getr()); + updateDigestIncludingSize(digest, alpha); + updateDigestIncludingSize(digest, knowledgeProofAlpha.getV()); + updateDigestIncludingSize(digest, knowledgeProofAlpha.getr()); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return new BigInteger(output); + } + + /** + * Calculates r = x1 - t * h mod n, where h is the hashed key+transcript. + * + * @param x1 Private key x1 + * @param t t = H(username||password) mod n + * @param hTranscript Transcript + * @param n Order of the base point + * @return r + */ + public static BigInteger calculateR( + BigInteger x1, + BigInteger t, + BigInteger hTranscript, + BigInteger n) + { + return x1.subtract(t.multiply(hTranscript)).mod(n); + } + + /** + * Validates r by checking [g] * r + [T] * h equals X1 + * + * @param r The client response r + * @param gx1 Public key X1 + * @param h Transcript + * @param gt Password verifier T + * @param g Base point + * @param n order of the base point + * @throws CryptoException if the validation fails i.e the values do not equal. + */ + public static void validateR( + BigInteger r, + ECPoint gx1, + BigInteger h, + ECPoint gt, + ECPoint g, + BigInteger n) + throws CryptoException + { + if(!g.multiply(r).add(gt.multiply(h.mod(n))).equals(gx1)) + { + throw new CryptoException("Verification for r failed, g^r . T^h = X1 must be true"); + } + } + + /** + * Validates that an EC point X is a valid public key on the designated elliptic curve. + * + * @param X Public key + * @param curve Elliptic curve + * @param q Prime field for the coordinates + * @param coFactor Co-factor + * @throws CryptoException if the public key validation fails + */ + public static void validatePublicKey(ECPoint X, ECCurve curve, BigInteger q, BigInteger coFactor) throws CryptoException{ + + /* Public key validation based on the following paper (Sec 3) + * Antipa, A., Brown, D., Menezes, A., Struik, R. and Vanstone, S., + * "Validation of elliptic curve public keys," PKC, 2002 + * https://iacr.org/archive/pkc2003/25670211/25670211.pdf + */ + + // 1. X != infinity + if (X.isInfinity()) + { + throw new CryptoException("Public key validation failed: it cannot equal infinity"); + } + + ECPoint x_normalized = X.normalize(); + // 2. Check x and y coordinates are in Fq, i.e., x, y in [0, q-1] + if (x_normalized.getAffineXCoord().toBigInteger().signum() < 0 || + x_normalized.getAffineXCoord().toBigInteger().compareTo(q) >= 0 || + x_normalized.getAffineYCoord().toBigInteger().signum() < 0 || + x_normalized.getAffineYCoord().toBigInteger().compareTo(q) >= 0) + { + throw new CryptoException("Public key validation failed: x and y coordiantes are not in the field"); + } + // 3. Check X lies on the curve + try + { + curve.decodePoint(X.getEncoded(true)); + } + catch (Exception e) + { + throw new CryptoException("Public key validation failed: it does not lie on the curve"); + } + // 4. Check that nX = infinity. + // It is equivalent - but more efficient - to check the coFactor*X is not infinity + if (X.multiply(coFactor).isInfinity()) + { + throw new CryptoException("Public key validation failed: it is in a small order group"); + } + } + + /** + * Calculates the MacTag (to be used for key confirmation), as defined by + * NIST SP 800-56A Revision 3, + * Section 5.9.1 Unilateral Key Confirmation for Key Agreement Schemes. + *
+ * MacTag = HMAC(MacKey, MacLen, MacData)
+ *
+ * MacKey = H(K || "Owl_KC")
+ *
+ * MacData = "KC_1_U" || participantId || partnerParticipantId || gx1 || gx2 || gx3 || gx4
+ *
+ * Note that both participants use "KC_1_U" because the sender of the key confirmation message
+ * is always the initiator for key confirmation.
+ *
+ * HMAC = {@link HMac} used with the given {@link Digest}
+ * H = The given {@link Digest}
+ * MacOutputBits = MacTagBits, hence truncation function omitted.
+ * MacLen = length of MacTag
+ *
+ *
+ * @param participantId Participant's identity
+ * @param partnerParticipantId The other participant's identity
+ * @param gx1 Public key X1
+ * @param gx2 Public key X2
+ * @param gx3 Public key X3
+ * @param gx4 Public key X4
+ * @param keyingMaterial Keying material
+ * @param digest Instance of a one-way hash
+ * @return MacTag for key confirmation
+ */
+ public static BigInteger calculateMacTag(
+ String participantId,
+ String partnerParticipantId,
+ ECPoint gx1,
+ ECPoint gx2,
+ ECPoint gx3,
+ ECPoint gx4,
+ BigInteger keyingMaterial,
+ Digest 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 || "Owl_KC") + *+ * + * @param keyingMaterial Keying material K + * @param digest Instance of a one-way hash + * @return H(K || "Owl_KC") + */ + private static byte[] calculateMacKey( + BigInteger keyingMaterial, + Digest 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, "Owl_KC"); + + byte[] output = new byte[digest.getDigestSize()]; + digest.doFinal(output, 0); + + return output; + } + + /** + * Validates the MacTag received from the partner participant. + * + * @param participantId The participant's identity + * @param partnerParticipantId The other participant's identity + * @param gx1 Public key X1 + * @param gx2 Public key X2 + * @param gx3 Public key X3 + * @param gx4 Public key X4 + * @param keyingMaterial Keying material + * @param digest Instance of a one-way hash + * @param partnerMacTag The MacTag received from the partner. + * @throws CryptoException if the participantId strings are equal + */ + public static void validateMacTag( + String participantId, + String partnerParticipantId, + ECPoint gx1, + ECPoint gx2, + ECPoint gx3, + ECPoint gx4, + BigInteger keyingMaterial, + Digest digest, + BigInteger partnerMacTag) + throws CryptoException + { + /* + * 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 updateMac(Mac mac, ECPoint ecPoint) + { + byte[] byteArray = ecPoint.getEncoded(true); + mac.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateMac(Mac mac, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + mac.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, ECPoint ecPoint) + { + byte[] byteArray = ecPoint.getEncoded(true); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, String string) + { + byte[] byteArray = Strings.toUTF8ByteArray(string); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + private static void updateDigest(Digest digest, BigInteger bigInteger) + { + byte[] byteArray = BigIntegers.asUnsignedByteArray(bigInteger); + digest.update(byteArray, 0, byteArray.length); + Arrays.fill(byteArray, (byte)0); + } + + protected static byte[] intToByteArray(int value) + { + return new byte[]{ + (byte)(value >>> 24), + (byte)(value >>> 16), + (byte)(value >>> 8), + (byte)value + }; + } + +} diff --git a/core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java b/core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java new file mode 100644 index 0000000000..afe109fcf2 --- /dev/null +++ b/core/src/main/java/org/bouncycastle/crypto/examples/Owl_Example.java @@ -0,0 +1,228 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.SavableDigest; +import org.bouncycastle.crypto.digests.SHA256Digest; + +/** + * An example of an Owl key exchange process. + *
+ * In this example, both the client and server are on the same computer (in the same JVM, in fact). + * In reality, they would be in different locations, + * and would be sending their generated payloads to each other. + *
+ */ +public class Owl_Example { + + public static void main(String args[]) throws CryptoException + { + /* + * Initialization + * + * Pick an appropriate elliptic curve to use throughout the exchange. + * Note that both participants must use the same group. + * Note that the same curve must be used between user registration, + * user login and user password update. + */ + Owl_Curve curve = Owl_Curves.NIST_P256; + + ECCurve ecCurve = curve.getCurve(); + BigInteger a = curve.getA(); + BigInteger b = curve.getB(); + ECPoint g = curve.getG(); + BigInteger h = curve.getH(); + BigInteger n = curve.getN(); + BigInteger q = curve.getQ(); + + String clientPassword = "password"; + + System.out.println("********* Initialization **********"); + System.out.println("Public parameters for the elliptic curve over prime field:"); + System.out.println("Curve param a (" + a.bitLength() + " bits): "+ a.toString(16)); + System.out.println("Curve param b (" + b.bitLength() + " bits): "+ b.toString(16)); + System.out.println("Co-factor h (" + h.bitLength() + " bits): " + h.toString(16)); + System.out.println("Base point G (" + g.getEncoded(true).length + " bytes): " + new BigInteger(g.getEncoded(true)).toString(16)); + System.out.println("X coord of G (G not normalised) (" + g.getXCoord().toBigInteger().bitLength() + " bits): " + g.getXCoord().toBigInteger().toString(16)); + System.out.println("y coord of G (G not normalised) (" + g.getYCoord().toBigInteger().bitLength() + " bits): " + g.getYCoord().toBigInteger().toString(16)); + System.out.println("Order of the base point n (" + n.bitLength() + " bits): "+ n.toString(16)); + System.out.println("Prime field q (" + q.bitLength() + " bits): "+ q.toString(16)); + System.out.println(""); + + System.out.println("(Secret passwords used by Client: " + clientPassword + ")"); + System.out.println(""); + + /* + * Both participants must use the same hashing algorithm. + * Both participants muse use the same hashing algorithm + * and elliptic curve for registration and authentication. + */ + Digest digest = SHA256Digest.newInstance(); + SecureRandom random = new SecureRandom(); + + Owl_ClientRegistration clientReg = new Owl_ClientRegistration("client", clientPassword.toCharArray(), curve, digest, random); + Owl_ServerRegistration serverReg = new Owl_ServerRegistration("server", curve, digest, random); + + Owl_Client client = new Owl_Client("client", clientPassword.toCharArray(), curve, digest, random); + Owl_Server server = new Owl_Server("server", curve, digest, random); + + /* + * Initial User Registration + * Client initiates registration using their username and password of choice and + * sends a payload (over a secure channel) to the server which in turn safely + * and securely stores (server storage is upto the user of this handshake protocol). + */ + + Owl_InitialRegistration clientUserRegistration = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverUserRegistration = serverReg.registerUseronServer(clientUserRegistration); + + System.out.println("************ User Registration **************"); + System.out.println("Client sends to Server: "); + System.out.println("Username used to register = " + clientUserRegistration.getClientId()); + System.out.println("pi=" + clientUserRegistration.getPi().toString(16)); + System.out.println("T=" + new BigInteger(clientUserRegistration.getGt().getEncoded(true)).toString(16)); + System.out.println(""); + + System.out.println("Server stores internally: "); + System.out.println("Username used to register = " + serverUserRegistration.getClientId()); + System.out.println("KP{x3}: {V="+new BigInteger(serverUserRegistration.getKnowledgeProofForX3().getV().getEncoded(true)).toString(16)+"; r="+serverUserRegistration.getKnowledgeProofForX3().getr().toString(16)+"}"); + System.out.println("X3=" + new BigInteger(serverUserRegistration.getGx3().getEncoded(true)).toString(16)); + System.out.println("pi=" + serverUserRegistration.getPi().toString(16)); + System.out.println("T=" + new BigInteger(serverUserRegistration.getGt().getEncoded(true)).toString(16)); + System.out.println(""); + + /* + * First Pass + * The client initiates the login authentication process by creating and sending a + * payload to the server. + */ + + Owl_AuthenticationInitiate clientLoginStart = client.authenticationInitiate(); + System.out.println("************ First Pass ************"); + System.out.println("Client sends to server: "); + System.out.println("Username used to login: " + clientLoginStart.getClientId()); + System.out.println("X1=" + new BigInteger(clientLoginStart.getGx1().getEncoded(true)).toString(16)); + System.out.println("X2=" + new BigInteger(clientLoginStart.getGx2().getEncoded(true)).toString(16)); + System.out.println("KP{x1}: {V=" + new BigInteger(clientLoginStart.getKnowledgeProofForX1().getV().getEncoded(true)).toString(16) + "; r=" + clientLoginStart.getKnowledgeProofForX1().getr().toString(16) + "}"); + System.out.println("KP{x2}: {V=" + new BigInteger(clientLoginStart.getKnowledgeProofForX2().getV().getEncoded(true)).toString(16) + "; r=" + clientLoginStart.getKnowledgeProofForX2().getr().toString(16) + "}"); + System.out.println(""); + + /* + * Second Pass + * The server validates the clients initial payload, and takes as input + * the internally stored client data from the server. It validates the payload recieved from the client. + * It then creates and sends its own payload back to the client. + */ + + Owl_AuthenticationServerResponse serverLoginResponse = server.authenticationServerResponse(clientLoginStart, serverUserRegistration); + + System.out.println("************ Second Pass **************"); + System.out.println("Server verifies the client's KP{x1}: OK"); + System.out.println("Server verifies the client's KP{x2}: OK\n"); + System.out.println("Server sends to Client: "); + System.out.println("Server's unique ID: " + serverLoginResponse.getServerId()); + System.out.println("X3=" + new BigInteger(serverLoginResponse.getGx3().getEncoded(true)).toString(16)); + System.out.println("X4=" + new BigInteger(serverLoginResponse.getGx4().getEncoded(true)).toString(16)); + System.out.println("KP{x3}: {V=" + new BigInteger(serverLoginResponse.getKnowledgeProofForX3().getV().getEncoded(true)).toString(16) + "; r=" + serverLoginResponse.getKnowledgeProofForX3().getr().toString(16) + "}"); + System.out.println("KP{x4}: {V=" + new BigInteger(serverLoginResponse.getKnowledgeProofForX4().getV().getEncoded(true)).toString(16) + "; r=" + serverLoginResponse.getKnowledgeProofForX4().getr().toString(16) + "}"); + System.out.println("Beta="+new BigInteger(serverLoginResponse.getBeta().getEncoded(true)).toString(16)); + System.out.println("KP{Beta}: {V="+new BigInteger(serverLoginResponse.getKnowledgeProofForBeta().getV().getEncoded(true)).toString(16)+", r="+serverLoginResponse.getKnowledgeProofForBeta().getr().toString(16)+"}"); + System.out.println(""); + + /* + * Third Pass + * The client recieves and valildates the server's response. + * It then creates and sends the final payload of the handshake. + * In the same pass, the client may send an explicit key confirmation string. This is optional but recommended. + */ + + Owl_AuthenticationFinish clientLoginEnd = client.authenticationFinish(serverLoginResponse); + + System.out.println("************ Third Pass ************"); + System.out.println("Client verifies the server's KP{x3}: OK"); + System.out.println("Client verifies the server's KP{x4}: OK"); + System.out.println("Client verifies the server's KP{Beta}: OK\n"); + + BigInteger clientKeyingMaterial = client.calculateKeyingMaterial(); + System.out.println("Client computes key material K=" + clientKeyingMaterial.toString(16)); + System.out.println(""); + + System.out.println("Client sends to Server: "); + System.out.println("Username used to login: " + clientLoginEnd.getClientId()); + System.out.println("Alpha="+new BigInteger(clientLoginEnd.getAlpha().getEncoded(true)).toString(16)); + System.out.println("KP{Alpha}: {V="+new BigInteger(clientLoginEnd.getKnowledgeProofForAlpha().getV().getEncoded(true)).toString(16)+", r="+clientLoginEnd.getKnowledgeProofForAlpha().getr().toString(16)+"}"); + System.out.println("r="+ clientLoginEnd.getR().toString(16)); + + /* The client sends an explicit key confirmation string. This is optional but recommended */ + Owl_KeyConfirmation clientKCPayload = client.initiateKeyConfirmation(clientKeyingMaterial); + System.out.println("MacTag=" + clientKCPayload.getMacTag().toString(16)); + System.out.println(""); + + /* Fourth pass + * The server receives the client payload, validates it and uses it to calculate its own key. + * In this pass, the server may send an explicit key confirmation string. This is optional but recommended. + */ + + System.out.println("********* Fourth Pass ***********"); + server.authenticationServerEnd(clientLoginEnd); + System.out.println("Server verifies the client's KP{Alpha}: OK"); + System.out.println("Server verifies the client's r: OK\n"); + + /* + * The server computes the keying material. + */ + BigInteger serverKeyingMaterial = server.calculateKeyingMaterial(); + System.out.println("Server computes key material K=" + serverKeyingMaterial.toString(16)); + System.out.println(""); + + // The server computes key confirmation string + Owl_KeyConfirmation serverKCPayload = server.initiateKeyConfirmation(serverKeyingMaterial); + server.validateKeyConfirmation(clientKCPayload, serverKeyingMaterial); + System.out.println("Server checks Client's MacTag for key confirmation: OK\n"); + + System.out.println("Server sends to Client:"); + System.out.println("MacTag=" + serverKCPayload.getMacTag().toString(16)); + System.out.println(""); + + System.out.println("********* After the fourth Pass ***********"); + client.validateKeyConfirmation(serverKCPayload, clientKeyingMaterial); + System.out.println("Client checks Server's MacTag for key confirmation: OK"); + System.out.println(""); + + /* + * You must derive a session key from the keying material applicable + * to whatever encryption algorithm you want to use. + */ + + BigInteger clientKey = deriveSessionKey(clientKeyingMaterial); + BigInteger serverKey = deriveSessionKey(serverKeyingMaterial); + + System.out.println("Client's session key \t clientKey=" + clientKey.toString(16)); + System.out.println("Server's session key \t ServerKey=" + serverKey.toString(16)); + } + + private static BigInteger deriveSessionKey(BigInteger keyingMaterial) + { + /* + * You should use a secure key derivation function (KDF) to derive the session key. + * + * For the purposes of this example, I'm just going to use a hash of the keying material. + */ + SavableDigest digest = SHA256Digest.newInstance(); + + byte[] keyByteArray = keyingMaterial.toByteArray(); + + byte[] output = new byte[digest.getDigestSize()]; + + digest.update(keyByteArray, 0, keyByteArray.length); + + digest.doFinal(output, 0); + + return new BigInteger(output); + } +} diff --git a/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java new file mode 100644 index 0000000000..79c893c2b2 --- /dev/null +++ b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientRegistrationTest.java @@ -0,0 +1,98 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; + +public class Owl_ClientRegistrationTest +{ + @Test + public void testConstruction() + throws CryptoException + { + Owl_Curve curve = Owl_Curves.NIST_P256; + SecureRandom random = new SecureRandom(); + Digest digest = new SHA256Digest(); + String clientId = "clientId"; + char[] password = "password".toCharArray(); + + //should succeed + assertDoesNotThrow(() -> + new Owl_ClientRegistration(clientId, password, curve, digest, random) + ); + + //null clientId + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(null, password, curve, digest, random) + ); + + //null password + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, null, curve, digest, random) + ); + + //empty password + assertThrows(IllegalArgumentException.class, () -> + new Owl_ClientRegistration(clientId, "".toCharArray(), curve, digest, random) + ); + + //null curve + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, password, null, digest, random) + ); + + //null digest + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, password, curve, null, random) + ); + + //null random + assertThrows(NullPointerException.class, () -> + new Owl_ClientRegistration(clientId, password, curve, digest, null) + ); + } + + @Test + public void testSuccessfulExchange() + { + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration initialUserReg = assertDoesNotThrow(() -> + clientReg.initiateUserRegistration() + ); + } + + @Test + public void testStateValidation() + { + Owl_ClientRegistration clientReg = createClientReg(); + //Testing the client here only using the server for help + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration initialUserReg = assertDoesNotThrow(() -> + clientReg.initiateUserRegistration() + ); + + //try call registration twice with the same client + + assertThrows(IllegalStateException.class, () -> + clientReg.initiateUserRegistration() + ); + } + + private Owl_ClientRegistration createClientReg() + { + return new Owl_ClientRegistration("client", "password".toCharArray(), Owl_Curves.NIST_P256); + } + private Owl_ServerRegistration createServerReg() + { + return new Owl_ServerRegistration("server", Owl_Curves.NIST_P256); + } +} \ No newline at end of file diff --git a/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java new file mode 100644 index 0000000000..135964d54c --- /dev/null +++ b/core/src/test/java/org/bouncycastle/crypto/agreement/owl_test/Owl_ClientTest.java @@ -0,0 +1,359 @@ +package org.example; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import java.util.function.Function; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import org.bouncycastle.crypto.CryptoException; +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.math.ec.ECPoint; +import org.bouncycastle.math.ec.ECCurve; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.crypto.digests.SHA256Digest; + +import org.bouncycastle.crypto.digests.SHA256Digest; + +public class Owl_ClientTest +{ + + public void testConstruction() + throws CryptoException + { + Owl_Curve curve = Owl_Curves.NIST_P256; + SecureRandom random = new SecureRandom(); + Digest digest = new SHA256Digest(); + String clientId = "clientId"; + char[] password = "password".toCharArray(); + + //should succeed + assertDoesNotThrow(() -> + new Owl_Client(clientId, password, curve, digest, random) + ); + + //null clientId + assertThrows(NullPointerException.class, () -> + new Owl_Client(null, password, curve, digest, random) + ); + + //null password + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, null, curve, digest, random) + ); + + //empty password + assertThrows(IllegalArgumentException.class, () -> + new Owl_Client(clientId, "".toCharArray(), curve, digest, random) + ); + + //null curve + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, password, null, digest, random) + ); + + //null digest + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, password, curve, null, random) + ); + + //null random + assertThrows(NullPointerException.class, () -> + new Owl_Client(clientId, password, curve, digest, null) + ); + } + @Test + public void testSuccessfulExchange() + throws CryptoException + { + + Owl_Server server = createServer(); + Owl_Client client = createClient(); + + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration clientRegistrationPayload = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverRegistrationPayload = serverReg.registerUseronServer(clientRegistrationPayload); + + Owl_AuthenticationInitiate clientLoginInitialPayload = client.authenticationInitiate(); + Owl_AuthenticationServerResponse serverLoginResponsePayload = server.authenticationServerResponse(clientLoginInitialPayload, serverRegistrationPayload); + + Owl_AuthenticationFinish clientLoginFinishPayload = client.authenticationFinish(serverLoginResponsePayload); + server.authenticationServerEnd(clientLoginFinishPayload); + + BigInteger clientKeyingMaterial = client.calculateKeyingMaterial(); + BigInteger serverKeyingMaterial = server.calculateKeyingMaterial(); + + Owl_KeyConfirmation clientKCPayload = client.initiateKeyConfirmation(clientKeyingMaterial); + Owl_KeyConfirmation serverKCPayload = server.initiateKeyConfirmation(serverKeyingMaterial); + + client.validateKeyConfirmation(serverKCPayload, clientKeyingMaterial); + server.validateKeyConfirmation(clientKCPayload, serverKeyingMaterial); + + assertEquals(serverKeyingMaterial, clientKeyingMaterial); + + } + @Test + public void testIncorrectPassword() + throws CryptoException + { + Owl_Server server = createServer(); + Owl_Client client = createClientWithWrongPassword(); + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + Owl_InitialRegistration clientRegistrationPayload = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverRegistrationPayload = serverReg.registerUseronServer(clientRegistrationPayload); + + Owl_AuthenticationInitiate clientLoginInitialPayload = client.authenticationInitiate(); + Owl_AuthenticationServerResponse serverLoginResponsePayload = server.authenticationServerResponse(clientLoginInitialPayload, serverRegistrationPayload); + + Owl_AuthenticationFinish clientLoginFinishPayload = client.authenticationFinish(serverLoginResponsePayload); + assertThrows(CryptoException.class, () -> + server.authenticationServerEnd(clientLoginFinishPayload) + ); +/* + BigInteger clientKeyingMaterial = client.calculateKeyingMaterial(); + BigInteger serverKeyingMaterial = server.calculateKeyingMaterial(); + + Owl_KeyConfirmation clientKCPayload = client.initiateKeyConfirmation(clientKeyingMaterial); + Owl_KeyConfirmation serverKCPayload = server.initiateKeyConfirmation(serverKeyingMaterial); + + // Validate incorrect passwords result in a CryptoException + + assertThrows(CryptoException.class, + () -> server.validateKeyConfirmation(clientKCPayload, serverKeyingMaterial), + "Server validation should fail with incorrect password"); + + assertThrows(CryptoException.class, + () -> client.validateKeyConfirmation(serverKCPayload, clientKeyingMaterial), + "Client validation should fail with incorrect password");*/ + } + @Test + public void testStateValidation() + throws CryptoException + { + + Owl_Server server = createServer(); + Owl_Client client = createClient(); + + Owl_ClientRegistration clientReg = createClientReg(); + Owl_ServerRegistration serverReg = createServerReg(); + + // We're testing client here. Server and registration objects are just used for help. + + Owl_InitialRegistration clientRegistrationPayload = clientReg.initiateUserRegistration(); + Owl_FinishRegistration serverRegistrationPayload = serverReg.registerUseronServer(clientRegistrationPayload); + + // --- LOGIN INITIALIZATION CHECKS --- + assertEquals(Owl_Client.STATE_INITIALISED, client.getState()); + + Owl_AuthenticationInitiate clientLoginInitialPayload = client.authenticationInitiate(); + assertEquals(Owl_Client.STATE_LOGIN_INITIALISED, client.getState()); + + // client cannot initiate login twice + assertThrows(IllegalStateException.class, + client::authenticationInitiate); + + Owl_AuthenticationServerResponse serverLoginResponsePayload = + server.authenticationServerResponse(clientLoginInitialPayload, serverRegistrationPayload); + + // --- LOGIN FINISH CHECKS --- + // cannot calculate key before finishing authentication + assertThrows(IllegalStateException.class, + client::calculateKeyingMaterial); + + Owl_AuthenticationFinish clientLoginFinishPayload = + client.authenticationFinish(serverLoginResponsePayload); + assertEquals(Owl_Client.STATE_LOGIN_FINISHED, client.getState()); + + // cannot finish login twice + assertThrows(IllegalStateException.class, + () -> client.authenticationFinish(serverLoginResponsePayload)); + + server.authenticationServerEnd(clientLoginFinishPayload); + // --- KEY CALCULATION CHECKS --- + // cannot start key confirmation before calculating key + assertThrows(IllegalStateException.class, + () -> client.initiateKeyConfirmation(null)); + + // cannot validate key confirmation before calculating key + assertThrows(IllegalStateException.class, + () -> client.validateKeyConfirmation(null, null)); + + BigInteger rawClientKey = client.calculateKeyingMaterial(); + assertEquals(Owl_Client.STATE_KEY_CALCULATED, client.getState()); + + // cannot calculate key twice + assertThrows(IllegalStateException.class, + client::calculateKeyingMaterial); + + BigInteger rawServerKey = server.calculateKeyingMaterial(); + + // --- KEY CONFIRMATION CHECKS --- + + Owl_KeyConfirmation clientKC = client.initiateKeyConfirmation(rawClientKey); + assertEquals(Owl_Client.STATE_KC_INITIALISED, client.getState()); + + // cannot initiate key confirmation twice + assertThrows(IllegalStateException.class, + () -> client.initiateKeyConfirmation(rawClientKey)); + + Owl_KeyConfirmation serverKC = server.initiateKeyConfirmation(rawServerKey); + + client.validateKeyConfirmation(serverKC, rawClientKey); + assertEquals(Owl_Client.STATE_KC_VALIDATED, client.getState()); + + // cannot validate key confirmation twice + assertThrows(IllegalStateException.class, + () -> client.validateKeyConfirmation(serverKC, rawClientKey)); + + // server should validate successfully at the end + server.validateKeyConfirmation(clientKC, rawServerKey); + } + @Test + public void testAuthenticationFinish() + throws CryptoException + { + Owl_Client client = createClient(); + Owl_Server server = createServer(); + Owl_AuthenticationServerResponse serverResponse = runExchangeUntilPass3(client, server); + //should succeed + Owl_AuthenticationFinish clientLoginFinishPayload = assertDoesNotThrow(() -> client.authenticationFinish(serverResponse)); + + Owl_Client client2 = createClient(); + Owl_Server server2 = createServer(); + Owl_AuthenticationServerResponse serverResponse2 = runExchangeUntilPass3(client2, server2); + // Helper to modify server response + Function