1 /**
2 * ECDH
3 * 
4 * Copyright:
5 * (C) 2007 Falko Strenzke, FlexSecure GmbH
6 *          Manuel Hartl, FlexSecure GmbH
7 * (C) 2008-2010 Jack Lloyd
8 * (C) 2014-2015 Etienne Cimon
9 *
10 * License:
11 * Botan is released under the Simplified BSD License (see LICENSE.md)
12 */
13 module botan.pubkey.algo.ecdh;
14 
15 import botan.constants;
16 static if (BOTAN_HAS_PUBLIC_KEY_CRYPTO && BOTAN_HAS_ECDH):
17 
18 public import botan.pubkey.pubkey;
19 import botan.pubkey.algo.ecc_key;
20 import botan.pubkey.pk_ops;
21 import botan.math.bigint.bigint;
22 import memutils.helpers : Embed;
23 
24 struct ECDHOptions {
25     enum algoName = "ECDH";
26     enum msgParts = 1;
27 }
28 
29 /**
30 * This class represents ECDH Public Keys.
31 */
32 struct ECDHPublicKey
33 {
34 public:
35     alias Options = ECDHOptions;
36     __gshared immutable string algoName = Options.algoName;
37 
38     this(in AlgorithmIdentifier alg_id, const ref SecureVector!ubyte key_bits) 
39     { 
40 		m_owned = true;
41         m_pub = new ECPublicKey(Options(), alg_id, key_bits);
42     }
43 
44     /**
45     * Construct a public key from a given public point.
46     *
47     * Params:
48     *  dom_par = the domain parameters associated with this key
49     *  public_point = the public point defining this key
50     */
51     this()(auto const ref ECGroup dom_par, auto const ref PointGFp public_point) 
52     {
53 		m_owned = true;
54         m_pub = new ECPublicKey(Options(), dom_par, public_point);
55     }
56 
57     this(PrivateKey pkey) { m_pub = cast(ECPublicKey) pkey; }
58     this(PublicKey pkey) { m_pub = cast(ECPublicKey) pkey; }
59 
60     mixin Embed!(m_pub, m_owned);
61 
62 	bool m_owned;
63     ECPublicKey m_pub;
64 }
65 
66 /**
67 * This class represents ECDH Private Keys.
68 */
69 struct ECDHPrivateKey
70 {
71 public:
72     alias Options = ECDHOptions;
73     __gshared immutable string algoName = Options.algoName;
74 
75     this(in AlgorithmIdentifier alg_id, const ref SecureVector!ubyte key_bits) 
76     {
77 		m_owned = true;
78         m_priv = new ECPrivateKey(Options(), alg_id, key_bits);
79     }
80 
81     /**
82     * Generate a new private key
83     * Params:
84     *  rng = a random number generator
85     *  domain = parameters to used for this key
86     *  x = the private key; if zero, a new random key is generated
87     */
88     this(RandomNumberGenerator rng, const ref ECGroup domain, BigInt x = BigInt(0)) 
89     {
90 		m_owned = true;
91         m_priv = new ECPrivateKey(Options(), rng, domain, x);
92     }
93 
94     this(RandomNumberGenerator rng, const ref ECGroup domain) { auto bi = BigInt(0); this(rng, domain, bi.move()); }
95 
96     this(PrivateKey pkey) { m_priv = cast(ECPrivateKey) pkey; }
97 
98     mixin Embed!(m_priv, m_owned);
99 
100 	bool m_owned;
101     ECPrivateKey m_priv;
102 
103 }
104 
105 /**
106 * ECDH operation
107 */
108 final class ECDHKAOperation : KeyAgreement
109 {
110 public:
111     this(in PrivateKey pkey) {
112         this(cast(ECPrivateKey) pkey);
113     }
114 
115     this(in ECDHPrivateKey pkey) {
116         this(pkey.m_priv);
117     }
118 
119     this(in ECPrivateKey key) 
120     {
121         m_curve = &key.domain().getCurve();
122         m_cofactor = &key.domain().getCofactor();
123         m_l_times_priv = inverseMod(*m_cofactor, key.domain().getOrder()) * key.privateValue();
124     }
125 
126     override SecureVector!ubyte agree(const(ubyte)* w, size_t w_len)
127     {
128         PointGFp point = OS2ECP(w, w_len, *m_curve);
129 		auto tmp = point * (*m_cofactor);
130         PointGFp S = tmp * m_l_times_priv;
131 
132         assert(S.onTheCurve(), "ECDH agreed value was on the curve");
133         
134         return BigInt.encode1363(S.getAffineX(),
135                                   m_curve.getP().bytes());
136     }
137 private:
138     const CurveGFp* m_curve;
139     const BigInt* m_cofactor;
140     BigInt m_l_times_priv;
141 }
142 
143 static if (BOTAN_TEST):
144 
145 import botan.test;
146 import botan.pubkey.pubkey;
147 import botan.cert.x509.x509self;
148 import botan.asn1.der_enc;
149 import botan.rng.auto_rng;
150 import core.atomic : atomicOp;
151 shared(size_t) total_tests;
152 
153 size_t testEcdhNormalDerivation(RandomNumberGenerator rng)
154 {
155     size_t fails = 0;
156     ECGroup dom_pars = ECGroup(OID("1.3.132.0.8"));
157 
158 
159     auto private_a = ECDHPrivateKey(rng, dom_pars);
160     
161     auto private_b = ECDHPrivateKey(rng, dom_pars); //public_a.getCurve()
162     
163     auto ka = scoped!PKKeyAgreement(private_a, "KDF2(SHA-1)");
164     auto kb = scoped!PKKeyAgreement(private_b, "KDF2(SHA-1)");
165     
166     SymmetricKey alice_key = ka.deriveKey(32, private_b.publicValue());
167     SymmetricKey bob_key = kb.deriveKey(32, private_a.publicValue());
168     // 1 test
169     if (alice_key != bob_key)
170     {
171         logError("The two keys didn't match!");
172         logDebug("Alice's key was: " ~ alice_key.toString());
173         logDebug("Bob's key was: " ~ bob_key.toString());
174         atomicOp!"+="(total_tests, cast(size_t)1);
175         ++fails;
176     }
177 
178     return fails;
179 }
180 
181 size_t testEcdhSomeDp(RandomNumberGenerator rng)
182 {
183     size_t fails = 0;
184     
185     Vector!string oids;
186     oids.pushBack("1.2.840.10045.3.1.7");
187     oids.pushBack("1.3.132.0.8");
188     oids.pushBack("1.2.840.10045.3.1.1");
189     // 3 tests
190     foreach (oid_str; oids[])
191     {
192         OID oid = OID(oid_str);
193         ECGroup dom_pars = ECGroup(oid);
194         
195         auto private_a = ECDHPrivateKey(rng, dom_pars);
196         auto private_b = ECDHPrivateKey(rng, dom_pars);
197         
198         auto ka = scoped!PKKeyAgreement(private_a, "KDF2(SHA-1)");
199         auto kb = scoped!PKKeyAgreement(private_b, "KDF2(SHA-1)");
200         
201         SymmetricKey alice_key = ka.deriveKey(32, private_b.publicValue());
202         SymmetricKey bob_key = kb.deriveKey(32, private_a.publicValue());
203         
204         mixin( CHECK_MESSAGE( `alice_key == bob_key`, "different keys - Alice s key was: ` ~ alice_key.toString() ~ `, Bob's key was: ` ~ bob_key.toString() ~ `" ) );
205     }
206     
207     return fails;
208 }
209 
210 size_t testEcdhDerDerivation(RandomNumberGenerator rng)
211 {
212     size_t fails = 0;
213     
214     Vector!string oids;
215     oids.pushBack("1.2.840.10045.3.1.7");
216     oids.pushBack("1.3.132.0.8");
217     oids.pushBack("1.2.840.10045.3.1.1");
218     // 3 tests
219     foreach (oid_str; oids[])
220     {
221         OID oid = OID(oid_str);
222         ECGroup dom_pars = ECGroup(oid);
223         
224         auto private_a = ECDHPrivateKey(rng, dom_pars);
225         auto private_b = ECDHPrivateKey(rng, dom_pars);
226         
227         Vector!ubyte key_a = private_a.publicValue();
228         Vector!ubyte key_b = private_b.publicValue();
229         
230         auto ka = scoped!PKKeyAgreement(private_a, "KDF2(SHA-1)");
231         auto kb = scoped!PKKeyAgreement(private_b, "KDF2(SHA-1)");
232         
233         SymmetricKey alice_key = ka.deriveKey(32, key_b);
234         SymmetricKey bob_key = kb.deriveKey(32, key_a);
235         
236         mixin( CHECK_MESSAGE( `alice_key == bob_key`, "different keys - Alice's key was: ` ~ alice_key.toString() ~ `, Bob's key was: ` ~ bob_key.toString() ~ `" ) );
237         
238     }
239     
240     return fails;
241 }
242 
243 static if (BOTAN_HAS_TESTS && !SKIP_ECDH_TEST) unittest
244 {
245     logDebug("Testing ecdh.d ...");
246     size_t fails = 0;
247     
248 	Unique!AutoSeededRNG rng = new AutoSeededRNG;
249     
250     fails += testEcdhNormalDerivation(*rng);
251     fails += testEcdhSomeDp(*rng);
252     fails += testEcdhDerDerivation(*rng);
253     
254     testReport("ECDH", total_tests, fails);
255 }