Software APIs
aes_kwp.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 
5 #include "sw/device/lib/crypto/impl/aes_kwp/aes_kwp.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 
14 #include "sw/device/lib/crypto/drivers/aes.h"
15 #include "sw/device/lib/crypto/impl/status.h"
16 
17 // Module ID for status codes.
18 #define MODULE_ID MAKE_MODULE_ID('k', 'w', 'p')
19 
20 enum {
21  /** Number of bytes in a semiblock (half an AES block). */
22  kSemiblockBytes = kAesBlockNumBytes / 2,
23  /** Number of 32-bit words in a semiblock (half an AES block). */
24  kSemiblockWords = kSemiblockBytes / sizeof(uint32_t),
25 };
26 
27 status_t aes_kwp_wrap(const aes_key_t kek, const uint32_t *plaintext,
28  const size_t plaintext_len, uint32_t *ciphertext) {
29  // The plaintext length is expected to be at most 2^32 bytes.
30  if (plaintext_len > UINT32_MAX || plaintext_len == 0) {
31  return OTCRYPTO_BAD_ARGS;
32  }
33 
34  // Calculate the number of semiblocks needed for the plaintext (round up to
35  // the next semiblock).
36  size_t plaintext_semiblocks = ceil_div(plaintext_len, kSemiblockBytes);
37  size_t pad_len = kSemiblockBytes * plaintext_semiblocks - plaintext_len;
38 
39  if (plaintext_semiblocks < 2) {
40  // Plaintext is too short.
41  return OTCRYPTO_BAD_ARGS;
42  }
43 
44  // Load the AES block with the encryption key.
45  HARDENED_TRY(aes_encrypt_begin(kek, /*iv=*/NULL));
46 
47  // This implementation follows the "indexing" method for the wrapping
48  // function, as described in RFC 3394, section 2.2.1:
49  // https://datatracker.ietf.org/doc/html/rfc3394#section-2.2.1
50 
51  // Construct the first semiblock (A): a fixed 32-bit prefix followed by the
52  // byte-length encoded as a big-endian 32-bit integer.
53  aes_block_t block = {
54  .data = {0xa65959a6, __builtin_bswap32(plaintext_len), 0, 0},
55  };
56 
57  // Initialize the output buffer with (A || plaintext || padding).
58  size_t plaintext_words = ceil_div(plaintext_len, sizeof(uint32_t));
59  hardened_memcpy(ciphertext, block.data, kSemiblockWords);
60  hardened_memcpy(ciphertext + kSemiblockWords, plaintext, plaintext_words);
61  unsigned char *pad_start =
62  ((unsigned char *)ciphertext) + kSemiblockBytes + plaintext_len;
63  memset(pad_start, 0, pad_len);
64 
65  uint64_t t = 1;
66  for (size_t j = 0; j < 6; j++) {
67  for (size_t i = 1; i <= plaintext_semiblocks; i++) {
68  // Copy R[i] into the block (A should already be present).
69  hardened_memcpy(block.data + kSemiblockWords,
70  ciphertext + i * kSemiblockWords, kSemiblockWords);
71  HARDENED_TRY(aes_update(/*dest=*/NULL, &block));
72  HARDENED_TRY(aes_update(&block, /*src=*/NULL));
73 
74  // Encode the index and XOR it with the first semiblock, creating A for
75  // the next iteration.
76  block.data[0] ^= __builtin_bswap32((uint32_t)(t >> 32));
77  block.data[1] ^= __builtin_bswap32((uint32_t)(t & UINT32_MAX));
78  t++;
79 
80  // Copy the last two words back into R[i].
81  hardened_memcpy(ciphertext + i * kSemiblockWords,
82  block.data + kSemiblockWords, kSemiblockWords);
83  }
84  }
85 
86  // Copy A into the first semiblock of the ciphertext.
87  hardened_memcpy(ciphertext, block.data, kSemiblockWords);
88  return OTCRYPTO_OK;
89 }
90 
91 status_t aes_kwp_unwrap(const aes_key_t kek, const uint32_t *ciphertext,
92  const size_t ciphertext_len, hardened_bool_t *success,
93  uint32_t *plaintext) {
94  // The ciphertext length is expected to be nonempty, at most 2^32 bytes, and
95  // a multiple of the semiblock size.
96  if (ciphertext_len > UINT32_MAX || ciphertext_len == 0 ||
97  ciphertext_len % kSemiblockBytes != 0) {
98  return OTCRYPTO_BAD_ARGS;
99  }
100 
101  // Calculate the number of semiblocks.
102  size_t ciphertext_semiblocks = ciphertext_len / kSemiblockBytes;
103 
104  if (ciphertext_semiblocks < 3) {
105  // Ciphertext is too short.
106  return OTCRYPTO_BAD_ARGS;
107  }
108 
109  // Load the AES block with the decryption key.
110  HARDENED_TRY(aes_decrypt_begin(kek, /*iv=*/NULL));
111 
112  // This implementation follows the "indexing" method for the wrapping
113  // function, as described in RFC 3394, section 2.2.2:
114  // https://datatracker.ietf.org/doc/html/rfc3394#section-2.2.2
115 
116  // Set the first semiblock, A.
117  aes_block_t block = {
118  .data = {ciphertext[0], ciphertext[1], 0, 0},
119  };
120 
121  // Initialize the working buffer, R.
122  uint32_t r[(ciphertext_semiblocks - 1) * kSemiblockWords];
123  hardened_memcpy(r, ciphertext + kSemiblockWords, ARRAYSIZE(r));
124 
125  uint64_t t = 6 * ((uint64_t)ciphertext_semiblocks - 1);
126  for (size_t j = 0; j < 6; j++) {
127  for (size_t i = ciphertext_semiblocks - 1; 1 <= i; i--) {
128  // Encode the index and XOR it with the first semiblock (A ^ t).
129  block.data[0] ^= __builtin_bswap32((uint32_t)(t >> 32));
130  block.data[1] ^= __builtin_bswap32((uint32_t)(t & UINT32_MAX));
131  t--;
132 
133  // Copy R[i] into the block (A ^ t should already be present).
134  hardened_memcpy(block.data + kSemiblockWords,
135  r + (i - 1) * kSemiblockWords, kSemiblockWords);
136  HARDENED_TRY(aes_update(/*dest=*/NULL, &block));
137  HARDENED_TRY(aes_update(&block, /*src=*/NULL));
138 
139  // Copy the last two words back into R[i].
140  hardened_memcpy(r + (i - 1) * kSemiblockWords,
141  block.data + kSemiblockWords, kSemiblockWords);
142  }
143  }
144 
145  // Check that the first 32 bits of A match the AES-KWP fixed prefix.
146  if (block.data[0] != 0xa65959a6) {
147  *success = kHardenedBoolFalse;
148  return OTCRYPTO_OK;
149  }
150 
151  // Decode the next 32 bits of A as the plaintext length.
152  size_t plaintext_len = __builtin_bswap32(block.data[1]);
153  size_t pad_len =
154  kSemiblockBytes * (ciphertext_semiblocks - 1) - plaintext_len;
155 
156  // Check that the padding length is valid.
157  if (pad_len >= kSemiblockBytes) {
158  *success = kHardenedBoolFalse;
159  return OTCRYPTO_OK;
160  }
161 
162  // Check that the padding bytes are zero. Note: this should happen only after
163  // the prefix check. Otherwise it could expose a padding oracle, because
164  // memcmp is not constant-time.
165  if (pad_len != 0) {
166  uint8_t exp_pad[pad_len];
167  memset(exp_pad, 0, pad_len);
168  unsigned char *pad_start = ((unsigned char *)r) + plaintext_len;
169  if (memcmp(pad_start, exp_pad, pad_len) != 0) {
170  *success = kHardenedBoolFalse;
171  return OTCRYPTO_OK;
172  }
173  }
174 
175  // Copy the plaintext into the destination buffer.
176  size_t plaintext_words = ceil_div(plaintext_len, sizeof(uint32_t));
177  hardened_memcpy(plaintext, r, plaintext_words);
178 
179  // Return success.
180  *success = kHardenedBoolTrue;
181  return OTCRYPTO_OK;
182 }