JPakeParticipant.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. #if !BESTHTTP_DISABLE_ALTERNATE_SSL && (!UNITY_WEBGL || UNITY_EDITOR)
  2. #pragma warning disable
  3. using System;
  4. using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto;
  5. using BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Digests;
  6. using BestHTTP.SecureProtocol.Org.BouncyCastle.Math;
  7. using BestHTTP.SecureProtocol.Org.BouncyCastle.Security;
  8. namespace BestHTTP.SecureProtocol.Org.BouncyCastle.Crypto.Agreement.JPake
  9. {
  10. /// <summary>
  11. /// A participant in a Password Authenticated Key Exchange by Juggling (J-PAKE) exchange.
  12. ///
  13. /// The J-PAKE exchange is defined by Feng Hao and Peter Ryan in the paper
  14. /// <a href="http://grouper.ieee.org/groups/1363/Research/contributions/hao-ryan-2008.pdf">
  15. /// "Password Authenticated Key Exchange by Juggling, 2008."</a>
  16. ///
  17. /// The J-PAKE protocol is symmetric.
  18. /// There is no notion of a <i>client</i> or <i>server</i>, but rather just two <i>participants</i>.
  19. /// An instance of JPakeParticipant represents one participant, and
  20. /// is the primary interface for executing the exchange.
  21. ///
  22. /// To execute an exchange, construct a JPakeParticipant on each end,
  23. /// and call the following 7 methods
  24. /// (once and only once, in the given order, for each participant, sending messages between them as described):
  25. ///
  26. /// CreateRound1PayloadToSend() - and send the payload to the other participant
  27. /// ValidateRound1PayloadReceived(JPakeRound1Payload) - use the payload received from the other participant
  28. /// CreateRound2PayloadToSend() - and send the payload to the other participant
  29. /// ValidateRound2PayloadReceived(JPakeRound2Payload) - use the payload received from the other participant
  30. /// CalculateKeyingMaterial()
  31. /// CreateRound3PayloadToSend(BigInteger) - and send the payload to the other participant
  32. /// ValidateRound3PayloadReceived(JPakeRound3Payload, BigInteger) - use the payload received from the other participant
  33. ///
  34. /// Each side should derive a session key from the keying material returned by CalculateKeyingMaterial().
  35. /// The caller is responsible for deriving the session key using a secure key derivation function (KDF).
  36. ///
  37. /// Round 3 is an optional key confirmation process.
  38. /// If you do not execute round 3, then there is no assurance that both participants are using the same key.
  39. /// (i.e. if the participants used different passwords, then their session keys will differ.)
  40. ///
  41. /// If the round 3 validation succeeds, then the keys are guaranteed to be the same on both sides.
  42. ///
  43. /// The symmetric design can easily support the asymmetric cases when one party initiates the communication.
  44. /// e.g. Sometimes the round1 payload and round2 payload may be sent in one pass.
  45. /// Also, in some cases, the key confirmation payload can be sent together with the round2 payload.
  46. /// These are the trivial techniques to optimize the communication.
  47. ///
  48. /// The key confirmation process is implemented as specified in
  49. /// <a href="http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf">NIST SP 800-56A Revision 1</a>,
  50. /// Section 8.2 Unilateral Key Confirmation for Key Agreement Schemes.
  51. ///
  52. /// This class is stateful and NOT threadsafe.
  53. /// Each instance should only be used for ONE complete J-PAKE exchange
  54. /// (i.e. a new JPakeParticipant should be constructed for each new J-PAKE exchange).
  55. /// </summary>
  56. public class JPakeParticipant
  57. {
  58. // Possible internal states. Used for state checking.
  59. public static readonly int STATE_INITIALIZED = 0;
  60. public static readonly int STATE_ROUND_1_CREATED = 10;
  61. public static readonly int STATE_ROUND_1_VALIDATED = 20;
  62. public static readonly int STATE_ROUND_2_CREATED = 30;
  63. public static readonly int STATE_ROUND_2_VALIDATED = 40;
  64. public static readonly int STATE_KEY_CALCULATED = 50;
  65. public static readonly int STATE_ROUND_3_CREATED = 60;
  66. public static readonly int STATE_ROUND_3_VALIDATED = 70;
  67. // Unique identifier of this participant.
  68. // The two participants in the exchange must NOT share the same id.
  69. private string participantId;
  70. // Shared secret. This only contains the secret between construction
  71. // and the call to CalculateKeyingMaterial().
  72. //
  73. // i.e. When CalculateKeyingMaterial() is called, this buffer overwritten with 0's,
  74. // and the field is set to null.
  75. private char[] password;
  76. // Digest to use during calculations.
  77. private IDigest digest;
  78. // Source of secure random data.
  79. private readonly SecureRandom random;
  80. private readonly BigInteger p;
  81. private readonly BigInteger q;
  82. private readonly BigInteger g;
  83. // The participantId of the other participant in this exchange.
  84. private string partnerParticipantId;
  85. // Alice's x1 or Bob's x3.
  86. private BigInteger x1;
  87. // Alice's x2 or Bob's x4.
  88. private BigInteger x2;
  89. // Alice's g^x1 or Bob's g^x3.
  90. private BigInteger gx1;
  91. // Alice's g^x2 or Bob's g^x4.
  92. private BigInteger gx2;
  93. // Alice's g^x3 or Bob's g^x1.
  94. private BigInteger gx3;
  95. // Alice's g^x4 or Bob's g^x2.
  96. private BigInteger gx4;
  97. // Alice's B or Bob's A.
  98. private BigInteger b;
  99. // The current state.
  100. // See the <tt>STATE_*</tt> constants for possible values.
  101. private int state;
  102. /// <summary>
  103. /// Convenience constructor for a new JPakeParticipant that uses
  104. /// the JPakePrimeOrderGroups#NIST_3072 prime order group,
  105. /// a SHA-256 digest, and a default SecureRandom implementation.
  106. ///
  107. /// After construction, the State state will be STATE_INITIALIZED.
  108. ///
  109. /// Throws NullReferenceException if any argument is null. Throws
  110. /// ArgumentException if password is empty.
  111. /// </summary>
  112. /// <param name="participantId">Unique identifier of this participant.
  113. /// The two participants in the exchange must NOT share the same id.</param>
  114. /// <param name="password">Shared secret.
  115. /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
  116. /// Caller should clear the input password as soon as possible.</param>
  117. public JPakeParticipant(string participantId, char[] password)
  118. : this(participantId, password, JPakePrimeOrderGroups.NIST_3072) { }
  119. /// <summary>
  120. /// Convenience constructor for a new JPakeParticipant that uses
  121. /// a SHA-256 digest, and a default SecureRandom implementation.
  122. ///
  123. /// After construction, the State state will be STATE_INITIALIZED.
  124. ///
  125. /// Throws NullReferenceException if any argument is null. Throws
  126. /// ArgumentException if password is empty.
  127. /// </summary>
  128. /// <param name="participantId">Unique identifier of this participant.
  129. /// The two participants in the exchange must NOT share the same id.</param>
  130. /// <param name="password">Shared secret.
  131. /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
  132. /// Caller should clear the input password as soon as possible.</param>
  133. /// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
  134. public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group)
  135. : this(participantId, password, group, new Sha256Digest(), new SecureRandom()) { }
  136. /// <summary>
  137. /// Constructor for a new JPakeParticipant.
  138. ///
  139. /// After construction, the State state will be STATE_INITIALIZED.
  140. ///
  141. /// Throws NullReferenceException if any argument is null. Throws
  142. /// ArgumentException if password is empty.
  143. /// </summary>
  144. /// <param name="participantId">Unique identifier of this participant.
  145. /// The two participants in the exchange must NOT share the same id.</param>
  146. /// <param name="password">Shared secret.
  147. /// A defensive copy of this array is made (and cleared once CalculateKeyingMaterial() is called).
  148. /// Caller should clear the input password as soon as possible.</param>
  149. /// <param name="group">Prime order group. See JPakePrimeOrderGroups for standard groups.</param>
  150. /// <param name="digest">Digest to use during zero knowledge proofs and key confirmation
  151. /// (SHA-256 or stronger preferred).</param>
  152. /// <param name="random">Source of secure random data for x1 and x2, and for the zero knowledge proofs.</param>
  153. public JPakeParticipant(string participantId, char[] password, JPakePrimeOrderGroup group, IDigest digest, SecureRandom random)
  154. {
  155. JPakeUtilities.ValidateNotNull(participantId, "participantId");
  156. JPakeUtilities.ValidateNotNull(password, "password");
  157. JPakeUtilities.ValidateNotNull(group, "p");
  158. JPakeUtilities.ValidateNotNull(digest, "digest");
  159. JPakeUtilities.ValidateNotNull(random, "random");
  160. if (password.Length == 0)
  161. {
  162. throw new ArgumentException("Password must not be empty.");
  163. }
  164. this.participantId = participantId;
  165. // Create a defensive copy so as to fully encapsulate the password.
  166. //
  167. // This array will contain the password for the lifetime of this
  168. // participant BEFORE CalculateKeyingMaterial() is called.
  169. //
  170. // i.e. When CalculateKeyingMaterial() is called, the array will be cleared
  171. // in order to remove the password from memory.
  172. //
  173. // The caller is responsible for clearing the original password array
  174. // given as input to this constructor.
  175. this.password = new char[password.Length];
  176. Array.Copy(password, this.password, password.Length);
  177. this.p = group.P;
  178. this.q = group.Q;
  179. this.g = group.G;
  180. this.digest = digest;
  181. this.random = random;
  182. this.state = STATE_INITIALIZED;
  183. }
  184. /// <summary>
  185. /// Gets the current state of this participant.
  186. /// See the <tt>STATE_*</tt> constants for possible values.
  187. /// </summary>
  188. public virtual int State
  189. {
  190. get { return state; }
  191. }
  192. /// <summary>
  193. /// Creates and returns the payload to send to the other participant during round 1.
  194. ///
  195. /// After execution, the State state} will be STATE_ROUND_1_CREATED}.
  196. /// </summary>
  197. public virtual JPakeRound1Payload CreateRound1PayloadToSend()
  198. {
  199. if (this.state >= STATE_ROUND_1_CREATED)
  200. throw new InvalidOperationException("Round 1 payload already created for " + this.participantId);
  201. this.x1 = JPakeUtilities.GenerateX1(q, random);
  202. this.x2 = JPakeUtilities.GenerateX2(q, random);
  203. this.gx1 = JPakeUtilities.CalculateGx(p, g, x1);
  204. this.gx2 = JPakeUtilities.CalculateGx(p, g, x2);
  205. BigInteger[] knowledgeProofForX1 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx1, x1, participantId, digest, random);
  206. BigInteger[] knowledgeProofForX2 = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, g, gx2, x2, participantId, digest, random);
  207. this.state = STATE_ROUND_1_CREATED;
  208. return new JPakeRound1Payload(participantId, gx1, gx2, knowledgeProofForX1, knowledgeProofForX2);
  209. }
  210. /// <summary>
  211. /// Validates the payload received from the other participant during round 1.
  212. ///
  213. /// Must be called prior to CreateRound2PayloadToSend().
  214. ///
  215. /// After execution, the State state will be STATE_ROUND_1_VALIDATED.
  216. ///
  217. /// Throws CryptoException if validation fails. Throws InvalidOperationException
  218. /// if called multiple times.
  219. /// </summary>
  220. public virtual void ValidateRound1PayloadReceived(JPakeRound1Payload round1PayloadReceived)
  221. {
  222. if (this.state >= STATE_ROUND_1_VALIDATED)
  223. throw new InvalidOperationException("Validation already attempted for round 1 payload for " + this.participantId);
  224. this.partnerParticipantId = round1PayloadReceived.ParticipantId;
  225. this.gx3 = round1PayloadReceived.Gx1;
  226. this.gx4 = round1PayloadReceived.Gx2;
  227. BigInteger[] knowledgeProofForX3 = round1PayloadReceived.KnowledgeProofForX1;
  228. BigInteger[] knowledgeProofForX4 = round1PayloadReceived.KnowledgeProofForX2;
  229. JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round1PayloadReceived.ParticipantId);
  230. JPakeUtilities.ValidateGx4(gx4);
  231. JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx3, knowledgeProofForX3, round1PayloadReceived.ParticipantId, digest);
  232. JPakeUtilities.ValidateZeroKnowledgeProof(p, q, g, gx4, knowledgeProofForX4, round1PayloadReceived.ParticipantId, digest);
  233. this.state = STATE_ROUND_1_VALIDATED;
  234. }
  235. /// <summary>
  236. /// Creates and returns the payload to send to the other participant during round 2.
  237. ///
  238. /// ValidateRound1PayloadReceived(JPakeRound1Payload) must be called prior to this method.
  239. ///
  240. /// After execution, the State state will be STATE_ROUND_2_CREATED.
  241. ///
  242. /// Throws InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
  243. /// </summary>
  244. public virtual JPakeRound2Payload CreateRound2PayloadToSend()
  245. {
  246. if (this.state >= STATE_ROUND_2_CREATED)
  247. throw new InvalidOperationException("Round 2 payload already created for " + this.participantId);
  248. if (this.state < STATE_ROUND_1_VALIDATED)
  249. throw new InvalidOperationException("Round 1 payload must be validated prior to creating round 2 payload for " + this.participantId);
  250. BigInteger gA = JPakeUtilities.CalculateGA(p, gx1, gx3, gx4);
  251. BigInteger s = JPakeUtilities.CalculateS(password);
  252. BigInteger x2s = JPakeUtilities.CalculateX2s(q, x2, s);
  253. BigInteger A = JPakeUtilities.CalculateA(p, q, gA, x2s);
  254. BigInteger[] knowledgeProofForX2s = JPakeUtilities.CalculateZeroKnowledgeProof(p, q, gA, A, x2s, participantId, digest, random);
  255. this.state = STATE_ROUND_2_CREATED;
  256. return new JPakeRound2Payload(participantId, A, knowledgeProofForX2s);
  257. }
  258. /// <summary>
  259. /// Validates the payload received from the other participant during round 2.
  260. /// Note that this DOES NOT detect a non-common password.
  261. /// The only indication of a non-common password is through derivation
  262. /// of different keys (which can be detected explicitly by executing round 3 and round 4)
  263. ///
  264. /// Must be called prior to CalculateKeyingMaterial().
  265. ///
  266. /// After execution, the State state will be STATE_ROUND_2_VALIDATED.
  267. ///
  268. /// Throws CryptoException if validation fails. Throws
  269. /// InvalidOperationException if called prior to ValidateRound1PayloadReceived(JPakeRound1Payload), or multiple times
  270. /// </summary>
  271. public virtual void ValidateRound2PayloadReceived(JPakeRound2Payload round2PayloadReceived)
  272. {
  273. if (this.state >= STATE_ROUND_2_VALIDATED)
  274. throw new InvalidOperationException("Validation already attempted for round 2 payload for " + this.participantId);
  275. if (this.state < STATE_ROUND_1_VALIDATED)
  276. throw new InvalidOperationException("Round 1 payload must be validated prior to validation round 2 payload for " + this.participantId);
  277. BigInteger gB = JPakeUtilities.CalculateGA(p, gx3, gx1, gx2);
  278. this.b = round2PayloadReceived.A;
  279. BigInteger[] knowledgeProofForX4s = round2PayloadReceived.KnowledgeProofForX2s;
  280. JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round2PayloadReceived.ParticipantId);
  281. JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round2PayloadReceived.ParticipantId);
  282. JPakeUtilities.ValidateGa(gB);
  283. JPakeUtilities.ValidateZeroKnowledgeProof(p, q, gB, b, knowledgeProofForX4s, round2PayloadReceived.ParticipantId, digest);
  284. this.state = STATE_ROUND_2_VALIDATED;
  285. }
  286. /// <summary>
  287. /// Calculates and returns the key material.
  288. /// A session key must be derived from this key material using a secure key derivation function (KDF).
  289. /// The KDF used to derive the key is handled externally (i.e. not by JPakeParticipant).
  290. ///
  291. /// The keying material will be identical for each participant if and only if
  292. /// each participant's password is the same. i.e. If the participants do not
  293. /// share the same password, then each participant will derive a different key.
  294. /// Therefore, if you immediately start using a key derived from
  295. /// the keying material, then you must handle detection of incorrect keys.
  296. /// If you want to handle this detection explicitly, you can optionally perform
  297. /// rounds 3 and 4. See JPakeParticipant for details on how to execute
  298. /// rounds 3 and 4.
  299. ///
  300. /// The keying material will be in the range <tt>[0, p-1]</tt>.
  301. ///
  302. /// ValidateRound2PayloadReceived(JPakeRound2Payload) must be called prior to this method.
  303. ///
  304. /// As a side effect, the internal password array is cleared, since it is no longer needed.
  305. ///
  306. /// After execution, the State state will be STATE_KEY_CALCULATED.
  307. ///
  308. /// Throws InvalidOperationException if called prior to ValidateRound2PayloadReceived(JPakeRound2Payload),
  309. /// or if called multiple times.
  310. /// </summary>
  311. public virtual BigInteger CalculateKeyingMaterial()
  312. {
  313. if (this.state >= STATE_KEY_CALCULATED)
  314. throw new InvalidOperationException("Key already calculated for " + participantId);
  315. if (this.state < STATE_ROUND_2_VALIDATED)
  316. throw new InvalidOperationException("Round 2 payload must be validated prior to creating key for " + participantId);
  317. BigInteger s = JPakeUtilities.CalculateS(password);
  318. // Clear the password array from memory, since we don't need it anymore.
  319. // Also set the field to null as a flag to indicate that the key has already been calculated.
  320. Array.Clear(password, 0, password.Length);
  321. this.password = null;
  322. BigInteger keyingMaterial = JPakeUtilities.CalculateKeyingMaterial(p, q, gx4, x2, s, b);
  323. // Clear the ephemeral private key fields as well.
  324. // Note that we're relying on the garbage collector to do its job to clean these up.
  325. // The old objects will hang around in memory until the garbage collector destroys them.
  326. //
  327. // If the ephemeral private keys x1 and x2 are leaked,
  328. // the attacker might be able to brute-force the password.
  329. this.x1 = null;
  330. this.x2 = null;
  331. this.b = null;
  332. // Do not clear gx* yet, since those are needed by round 3.
  333. this.state = STATE_KEY_CALCULATED;
  334. return keyingMaterial;
  335. }
  336. /// <summary>
  337. /// Creates and returns the payload to send to the other participant during round 3.
  338. ///
  339. /// See JPakeParticipant for more details on round 3.
  340. ///
  341. /// After execution, the State state} will be STATE_ROUND_3_CREATED.
  342. /// Throws InvalidOperationException if called prior to CalculateKeyingMaterial, or multiple
  343. /// times.
  344. /// </summary>
  345. /// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param>
  346. public virtual JPakeRound3Payload CreateRound3PayloadToSend(BigInteger keyingMaterial)
  347. {
  348. if (this.state >= STATE_ROUND_3_CREATED)
  349. throw new InvalidOperationException("Round 3 payload already created for " + this.participantId);
  350. if (this.state < STATE_KEY_CALCULATED)
  351. throw new InvalidOperationException("Keying material must be calculated prior to creating round 3 payload for " + this.participantId);
  352. BigInteger macTag = JPakeUtilities.CalculateMacTag(
  353. this.participantId,
  354. this.partnerParticipantId,
  355. this.gx1,
  356. this.gx2,
  357. this.gx3,
  358. this.gx4,
  359. keyingMaterial,
  360. this.digest);
  361. this.state = STATE_ROUND_3_CREATED;
  362. return new JPakeRound3Payload(participantId, macTag);
  363. }
  364. /// <summary>
  365. /// Validates the payload received from the other participant during round 3.
  366. ///
  367. /// See JPakeParticipant for more details on round 3.
  368. ///
  369. /// After execution, the State state will be STATE_ROUND_3_VALIDATED.
  370. ///
  371. /// Throws CryptoException if validation fails. Throws InvalidOperationException if called prior to
  372. /// CalculateKeyingMaterial or multiple times
  373. /// </summary>
  374. /// <param name="round3PayloadReceived">The round 3 payload received from the other participant.</param>
  375. /// <param name="keyingMaterial">The keying material as returned from CalculateKeyingMaterial().</param>
  376. public virtual void ValidateRound3PayloadReceived(JPakeRound3Payload round3PayloadReceived, BigInteger keyingMaterial)
  377. {
  378. if (this.state >= STATE_ROUND_3_VALIDATED)
  379. throw new InvalidOperationException("Validation already attempted for round 3 payload for " + this.participantId);
  380. if (this.state < STATE_KEY_CALCULATED)
  381. throw new InvalidOperationException("Keying material must be calculated prior to validating round 3 payload for " + this.participantId);
  382. JPakeUtilities.ValidateParticipantIdsDiffer(participantId, round3PayloadReceived.ParticipantId);
  383. JPakeUtilities.ValidateParticipantIdsEqual(this.partnerParticipantId, round3PayloadReceived.ParticipantId);
  384. JPakeUtilities.ValidateMacTag(
  385. this.participantId,
  386. this.partnerParticipantId,
  387. this.gx1,
  388. this.gx2,
  389. this.gx3,
  390. this.gx4,
  391. keyingMaterial,
  392. this.digest,
  393. round3PayloadReceived.MacTag);
  394. // Clear the rest of the fields.
  395. this.gx1 = null;
  396. this.gx2 = null;
  397. this.gx3 = null;
  398. this.gx4 = null;
  399. this.state = STATE_ROUND_3_VALIDATED;
  400. }
  401. }
  402. }
  403. #pragma warning restore
  404. #endif