/src/botan/src/lib/modes/aead/ccm/ccm.cpp
Line | Count | Source |
1 | | /* |
2 | | * CCM Mode Encryption |
3 | | * (C) 2013,2018 Jack Lloyd |
4 | | * (C) 2016 Daniel Neus, Rohde & Schwarz Cybersecurity |
5 | | * |
6 | | * Botan is released under the Simplified BSD License (see license.txt) |
7 | | */ |
8 | | |
9 | | #include <botan/internal/ccm.h> |
10 | | |
11 | | #include <botan/exceptn.h> |
12 | | #include <botan/mem_ops.h> |
13 | | #include <botan/internal/ct_utils.h> |
14 | | #include <botan/internal/fmt.h> |
15 | | #include <botan/internal/loadstor.h> |
16 | | |
17 | | namespace Botan { |
18 | | |
19 | | // 128-bit cipher is intrinsic to CCM definition |
20 | | static const size_t CCM_BS = 16; |
21 | | |
22 | | /* |
23 | | * CCM_Mode Constructor |
24 | | */ |
25 | | CCM_Mode::CCM_Mode(std::unique_ptr<BlockCipher> cipher, size_t tag_size, size_t L) : |
26 | 161 | m_tag_size(tag_size), m_L(L), m_cipher(std::move(cipher)) { |
27 | 161 | if(m_cipher->block_size() != CCM_BS) { |
28 | 0 | throw Invalid_Argument(m_cipher->name() + " cannot be used with CCM mode"); |
29 | 0 | } |
30 | | |
31 | 161 | if(L < 2 || L > 8) { |
32 | 0 | throw Invalid_Argument(fmt("Invalid CCM L value {}", L)); |
33 | 0 | } |
34 | | |
35 | 161 | if(tag_size < 4 || tag_size > 16 || tag_size % 2 != 0) { |
36 | 0 | throw Invalid_Argument(fmt("Invalid CCM tag length {}", tag_size)); |
37 | 0 | } |
38 | 161 | } |
39 | | |
40 | 0 | void CCM_Mode::clear() { |
41 | 0 | m_cipher->clear(); |
42 | 0 | reset(); |
43 | 0 | } |
44 | | |
45 | 132 | void CCM_Mode::reset() { |
46 | 132 | m_nonce.clear(); |
47 | 132 | m_msg_buf.clear(); |
48 | 132 | m_ad_buf.clear(); |
49 | 132 | } |
50 | | |
51 | 0 | std::string CCM_Mode::name() const { |
52 | 0 | return fmt("{}/CCM({},{})", m_cipher->name(), tag_size(), L()); |
53 | 0 | } |
54 | | |
55 | 210 | bool CCM_Mode::valid_nonce_length(size_t length) const { |
56 | 210 | return (length == (15 - L())); |
57 | 210 | } |
58 | | |
59 | 0 | size_t CCM_Mode::default_nonce_length() const { |
60 | 0 | return (15 - L()); |
61 | 0 | } |
62 | | |
63 | 0 | size_t CCM_Mode::update_granularity() const { |
64 | 0 | return 1; |
65 | 0 | } |
66 | | |
67 | 0 | size_t CCM_Mode::ideal_granularity() const { |
68 | | // Completely arbitrary |
69 | 0 | return m_cipher->parallel_bytes(); |
70 | 0 | } |
71 | | |
72 | 0 | bool CCM_Mode::requires_entire_message() const { |
73 | 0 | return true; |
74 | 0 | } |
75 | | |
76 | 161 | Key_Length_Specification CCM_Mode::key_spec() const { |
77 | 161 | return m_cipher->key_spec(); |
78 | 161 | } |
79 | | |
80 | 0 | bool CCM_Mode::has_keying_material() const { |
81 | 0 | return m_cipher->has_keying_material(); |
82 | 0 | } |
83 | | |
84 | 161 | void CCM_Mode::key_schedule(std::span<const uint8_t> key) { |
85 | 161 | m_cipher->set_key(key); |
86 | 161 | } |
87 | | |
88 | 210 | void CCM_Mode::set_associated_data_n(size_t idx, std::span<const uint8_t> ad) { |
89 | 210 | BOTAN_ARG_CHECK(idx == 0, "CCM: cannot handle non-zero index in set_associated_data_n"); |
90 | | |
91 | 210 | m_ad_buf.clear(); |
92 | | |
93 | 210 | if(!ad.empty()) { |
94 | | // FIXME: support larger AD using length encoding rules |
95 | 210 | BOTAN_ARG_CHECK(ad.size() < (0xFFFF - 0xFF), "Supported CCM AD length"); |
96 | | |
97 | 210 | m_ad_buf.push_back(get_byte<0>(static_cast<uint16_t>(ad.size()))); |
98 | 210 | m_ad_buf.push_back(get_byte<1>(static_cast<uint16_t>(ad.size()))); |
99 | 210 | m_ad_buf.insert(m_ad_buf.end(), ad.begin(), ad.end()); |
100 | 420 | while(m_ad_buf.size() % CCM_BS != 0) { |
101 | 210 | m_ad_buf.push_back(0); // pad with zeros to full block size |
102 | 210 | } |
103 | 210 | } |
104 | 210 | } |
105 | | |
106 | 210 | void CCM_Mode::start_msg(const uint8_t nonce[], size_t nonce_len) { |
107 | 210 | if(!valid_nonce_length(nonce_len)) { |
108 | 0 | throw Invalid_IV_Length(name(), nonce_len); |
109 | 0 | } |
110 | | |
111 | 210 | m_nonce.assign(nonce, nonce + nonce_len); |
112 | 210 | m_msg_buf.clear(); |
113 | 210 | } |
114 | | |
115 | 0 | size_t CCM_Mode::process_msg(uint8_t buf[], size_t sz) { |
116 | 0 | BOTAN_STATE_CHECK(!m_nonce.empty()); |
117 | 0 | m_msg_buf.insert(m_msg_buf.end(), buf, buf + sz); |
118 | | |
119 | | // CCM message length is limited to 2^(8*L) - 1 bytes |
120 | 0 | if(L() < 8) { |
121 | 0 | const uint64_t max_msg_len = (static_cast<uint64_t>(1) << (8 * L())) - 1; |
122 | 0 | if(m_msg_buf.size() > max_msg_len) { |
123 | 0 | throw Invalid_State("CCM message length exceeds the limit for L"); |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | 0 | return 0; // no output until finished |
128 | 0 | } |
129 | | |
130 | 210 | void CCM_Mode::encode_length(uint64_t len, uint8_t out[]) { |
131 | 210 | const size_t len_bytes = L(); |
132 | | |
133 | 210 | BOTAN_ASSERT_NOMSG(len_bytes >= 2 && len_bytes <= 8); |
134 | | |
135 | 840 | for(size_t i = 0; i != len_bytes; ++i) { |
136 | 630 | out[len_bytes - 1 - i] = get_byte_var(sizeof(uint64_t) - 1 - i, len); |
137 | 630 | } |
138 | | |
139 | 210 | if(len_bytes < 8 && (len >> (len_bytes * 8)) > 0) { |
140 | 0 | throw Encoding_Error("CCM message length too long to encode in L field"); |
141 | 0 | } |
142 | 210 | } |
143 | | |
144 | 3.60k | void CCM_Mode::inc(secure_vector<uint8_t>& C) { |
145 | 3.61k | for(size_t i = 0; i != C.size(); ++i) { |
146 | 3.61k | uint8_t& b = C[C.size() - i - 1]; |
147 | 3.61k | b += 1; |
148 | 3.61k | if(b > 0) { |
149 | 3.60k | break; |
150 | 3.60k | } |
151 | 3.61k | } |
152 | 3.60k | } |
153 | | |
154 | 210 | secure_vector<uint8_t> CCM_Mode::format_b0(size_t sz) { |
155 | 210 | if(m_nonce.size() != 15 - L()) { |
156 | 0 | throw Invalid_State("CCM mode must set nonce"); |
157 | 0 | } |
158 | 210 | secure_vector<uint8_t> B0(CCM_BS); |
159 | | |
160 | 210 | const uint8_t b_flags = |
161 | 210 | static_cast<uint8_t>((!m_ad_buf.empty() ? 64 : 0) + (((tag_size() / 2) - 1) << 3) + (L() - 1)); |
162 | | |
163 | 210 | B0[0] = b_flags; |
164 | 210 | copy_mem(&B0[1], m_nonce.data(), m_nonce.size()); |
165 | 210 | encode_length(sz, &B0[m_nonce.size() + 1]); |
166 | | |
167 | 210 | return B0; |
168 | 210 | } |
169 | | |
170 | 210 | secure_vector<uint8_t> CCM_Mode::format_c0() { |
171 | 210 | if(m_nonce.size() != 15 - L()) { |
172 | 0 | throw Invalid_State("CCM mode must set nonce"); |
173 | 0 | } |
174 | 210 | secure_vector<uint8_t> C(CCM_BS); |
175 | | |
176 | 210 | const uint8_t a_flags = static_cast<uint8_t>(L() - 1); |
177 | | |
178 | 210 | C[0] = a_flags; |
179 | 210 | copy_mem(&C[1], m_nonce.data(), m_nonce.size()); |
180 | | |
181 | 210 | return C; |
182 | 210 | } |
183 | | |
184 | 132 | void CCM_Encryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) { |
185 | 132 | BOTAN_ARG_CHECK(buffer.size() >= offset, "Offset is out of range"); |
186 | | |
187 | 132 | buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end()); |
188 | | |
189 | 132 | const size_t sz = buffer.size() - offset; |
190 | 132 | uint8_t* buf = buffer.data() + offset; |
191 | | |
192 | 132 | const secure_vector<uint8_t>& ad = ad_buf(); |
193 | 132 | BOTAN_ARG_CHECK(ad.size() % CCM_BS == 0, "AD is block size multiple"); |
194 | | |
195 | 132 | const BlockCipher& E = cipher(); |
196 | | |
197 | 132 | secure_vector<uint8_t> T(CCM_BS); |
198 | 132 | E.encrypt(format_b0(sz), T); |
199 | | |
200 | 264 | for(size_t i = 0; i != ad.size(); i += CCM_BS) { |
201 | 132 | xor_buf(T.data(), &ad[i], CCM_BS); |
202 | 132 | E.encrypt(T); |
203 | 132 | } |
204 | | |
205 | 132 | secure_vector<uint8_t> C = format_c0(); |
206 | 132 | secure_vector<uint8_t> S0(CCM_BS); |
207 | 132 | E.encrypt(C, S0); |
208 | 132 | inc(C); |
209 | | |
210 | 132 | secure_vector<uint8_t> X(CCM_BS); |
211 | | |
212 | 132 | const uint8_t* buf_end = &buf[sz]; |
213 | | |
214 | 264 | while(buf != buf_end) { |
215 | 132 | const size_t to_proc = std::min<size_t>(CCM_BS, buf_end - buf); |
216 | | |
217 | 132 | xor_buf(T.data(), buf, to_proc); |
218 | 132 | E.encrypt(T); |
219 | | |
220 | 132 | E.encrypt(C, X); |
221 | 132 | xor_buf(buf, X.data(), to_proc); |
222 | 132 | inc(C); |
223 | | |
224 | 132 | buf += to_proc; |
225 | 132 | } |
226 | | |
227 | 132 | T ^= S0; |
228 | | |
229 | 132 | buffer += std::make_pair(T.data(), tag_size()); |
230 | | |
231 | 132 | reset(); |
232 | 132 | } |
233 | | |
234 | 78 | void CCM_Decryption::finish_msg(secure_vector<uint8_t>& buffer, size_t offset) { |
235 | 78 | BOTAN_ARG_CHECK(buffer.size() >= offset, "Offset is out of range"); |
236 | | |
237 | 78 | buffer.insert(buffer.begin() + offset, msg_buf().begin(), msg_buf().end()); |
238 | | |
239 | 78 | const size_t sz = buffer.size() - offset; |
240 | 78 | uint8_t* buf = buffer.data() + offset; |
241 | | |
242 | 78 | BOTAN_ARG_CHECK(sz >= tag_size(), "input did not include the tag"); |
243 | | |
244 | 78 | const secure_vector<uint8_t>& ad = ad_buf(); |
245 | 78 | BOTAN_ARG_CHECK(ad.size() % CCM_BS == 0, "AD is block size multiple"); |
246 | | |
247 | 78 | const BlockCipher& E = cipher(); |
248 | | |
249 | 78 | secure_vector<uint8_t> T(CCM_BS); |
250 | 78 | E.encrypt(format_b0(sz - tag_size()), T); |
251 | | |
252 | 156 | for(size_t i = 0; i != ad.size(); i += CCM_BS) { |
253 | 78 | xor_buf(T.data(), &ad[i], CCM_BS); |
254 | 78 | E.encrypt(T); |
255 | 78 | } |
256 | | |
257 | 78 | secure_vector<uint8_t> C = format_c0(); |
258 | | |
259 | 78 | secure_vector<uint8_t> S0(CCM_BS); |
260 | 78 | E.encrypt(C, S0); |
261 | 78 | inc(C); |
262 | | |
263 | 78 | secure_vector<uint8_t> X(CCM_BS); |
264 | | |
265 | 78 | const uint8_t* buf_end = &buf[sz - tag_size()]; |
266 | | |
267 | 3.34k | while(buf != buf_end) { |
268 | 3.26k | const size_t to_proc = std::min<size_t>(CCM_BS, buf_end - buf); |
269 | | |
270 | 3.26k | E.encrypt(C, X); |
271 | 3.26k | xor_buf(buf, X.data(), to_proc); |
272 | 3.26k | inc(C); |
273 | | |
274 | 3.26k | xor_buf(T.data(), buf, to_proc); |
275 | 3.26k | E.encrypt(T); |
276 | | |
277 | 3.26k | buf += to_proc; |
278 | 3.26k | } |
279 | | |
280 | 78 | T ^= S0; |
281 | | |
282 | 78 | if(!CT::is_equal(T.data(), buf_end, tag_size()).as_bool()) { |
283 | 78 | clear_mem(std::span{buffer}.subspan(offset, sz - tag_size())); |
284 | 78 | throw Invalid_Authentication_Tag("CCM tag check failed"); |
285 | 78 | } |
286 | | |
287 | 0 | buffer.resize(buffer.size() - tag_size()); |
288 | |
|
289 | 0 | reset(); |
290 | 0 | } |
291 | | |
292 | | } // namespace Botan |