1 /**
2 * HKDF
3 * 
4 * Copyright:
5 * (C) 2013 Jack Lloyd
6 * (C) 2014-2015 Etienne Cimon
7 *
8 * License:
9 * Botan is released under the Simplified BSD License (see LICENSE.md)
10 */
11 module botan.prf.hkdf;
12 
13 import botan.constants;
14 static if (BOTAN_HAS_HKDF):
15 
16 import botan.mac.mac;
17 import botan.hash.hash;
18 import botan.utils.types;
19 import botan.utils.mem_ops;
20 import std.algorithm : min;
21 
22 /**
23 * HKDF, see @rfc 5869 for details
24 */
25 final class HKDF
26 {
27 public:
28     this(MessageAuthenticationCode extractor,
29          MessageAuthenticationCode prf) 
30     {
31         m_extractor = extractor;
32         m_prf = prf;
33     }
34 
35     this(MessageAuthenticationCode prf)
36     {
37         m_extractor = prf;
38         m_prf = m_extractor.clone(); 
39     }
40 
41     void startExtract(const(ubyte)* salt, size_t salt_len)
42     {
43         m_extractor.setKey(salt, salt_len);
44     }
45 
46     void extract(const(ubyte)* input, size_t input_len)
47     {
48         m_extractor.update(input, input_len);
49     }
50 
51     void finishExtract()
52     {
53         m_prf.setKey(m_extractor.finished());
54     }
55 
56 
57     /**
58     * Only call after extract
59     * Params:
60     *  output = the output
61     *  output_len = must be less than 256*hashlen
62     *  info = the underlying data
63     *  info_len = length of underlying data
64     */
65     void expand(ubyte* output, size_t output_len,
66                 const(ubyte)* info, size_t info_len)
67     {
68         if (output_len > m_prf.outputLength * 255)
69             throw new InvalidArgument("HKDF requested output too large");
70         
71         ubyte counter = 1;
72         
73         SecureVector!ubyte T;
74         
75         while (output_len)
76         {
77             m_prf.update(T);
78             m_prf.update(info, info_len);
79             m_prf.update(counter++);
80             T = m_prf.finished();
81             
82             const size_t to_write = min(T.length, output_len);
83             copyMem(output, T.ptr, to_write);
84             output += to_write;
85             output_len -= to_write;
86         }
87     }
88 
89 
90     @property string name() const
91     {
92         return "HKDF(" ~ m_prf.name ~ ")";
93     }
94 
95     void clear()
96     {
97         m_extractor.clear();
98         m_prf.clear();
99     }
100 private:
101     Unique!MessageAuthenticationCode m_extractor;
102     Unique!MessageAuthenticationCode m_prf;
103 }
104 
105 
106 static if (BOTAN_TEST):
107 
108 import botan.test;
109 import botan.codec.hex;
110 import botan.libstate.libstate;
111 import memutils.hashmap;
112 
113 private shared size_t total_tests;
114 
115 SecureVector!ubyte hkdf()(string hkdf_algo,
116                           auto const ref SecureVector!ubyte ikm,
117                           auto const ref SecureVector!ubyte salt,
118                           auto const ref SecureVector!ubyte info,
119                           size_t L)
120 {
121     AlgorithmFactory af = globalState().algorithmFactory();
122     
123     const string algo = hkdf_algo[5 .. hkdf_algo.length-1];
124     //logTrace("Testing: HMAC(", algo, ")");
125     const MessageAuthenticationCode mac_proto = af.prototypeMac("HMAC(" ~ algo ~ ")");
126     
127     if (!mac_proto)
128         throw new InvalidArgument("Bad HKDF hash '" ~ algo ~ "'");
129     
130     auto hkdf = scoped!HKDF(mac_proto.clone(), mac_proto.clone());
131     
132     hkdf.startExtract(salt.ptr, salt.length);
133     hkdf.extract(ikm.ptr, ikm.length);
134     hkdf.finishExtract();
135     
136     SecureVector!ubyte key = SecureVector!ubyte(L);
137     hkdf.expand(key.ptr, key.length, info.ptr, info.length);
138     return key;
139 }
140 
141 size_t hkdfTest(string algo, string ikm, string salt, string info, string okm, size_t L)
142 {
143     import core.atomic;
144     atomicOp!"+="(total_tests, 1);
145     const string got = hexEncode(hkdf(algo, 
146                                       hexDecodeLocked(ikm), 
147                                       hexDecodeLocked(salt), 
148                                       hexDecodeLocked(info),
149                                       L));
150     
151     if (got != okm)
152     {
153         logTrace("HKDF got " ~ got ~ " expected " ~ okm);
154         return 1;
155     }
156     
157     return 0;
158 }
159 
160 static if (BOTAN_HAS_TESTS && !SKIP_HKDF_TEST) unittest
161 {
162     logDebug("Testing hkdf.d ...");
163     File vec = File("test_data/hkdf.vec", "r");
164     
165     size_t fails = runTestsBb(vec, "HKDF", "OKM", true,
166         (ref HashMap!(string, string) m)
167         {
168             return hkdfTest(m["HKDF"], m["IKM"], m.get("salt"), m.get("info"), m["OKM"], to!uint(m["L"]));
169         });
170     
171     testReport("hkdf", total_tests, fails);
172 }