Software APIs
ecdh.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 
6 #include "sw/device/lib/base/status.h"
12 #include "sw/device/lib/testing/test_framework/ujson_ottf.h"
13 #include "sw/device/lib/ujson/ujson.h"
14 #include "sw/device/tests/crypto/cryptotest/json/ecdh_commands.h"
15 
16 enum {
17  /**
18  * Bytes in one share of an unmasked P-256 private key.
19  */
20  kP256PrivateKeyBytes = 32,
21  /**
22  * Bytes in one share of a masked P-256 private key.
23  */
24  kP256MaskedPrivateKeyBytes = 40,
25  /**
26  * Words in one share of a masked P-256 private key.
27  */
28  kP256MaskedPrivateKeyWords = kP256MaskedPrivateKeyBytes / sizeof(uint32_t),
29  /**
30  * Bytes in a P-256 public key coordinate.
31  */
32  kP256CoordinateBytes = 32,
33  /**
34  * Words in a P-256 public key coordinate.
35  */
36  kP256CoordinateWords = kP256CoordinateBytes / sizeof(uint32_t),
37  /**
38  * Bytes in one share of an ECDH/P-256 shared secret.
39  */
40  kP256SharedSecretBytes = 32,
41  /**
42  * Bytes in one share of an unmasked P-384 private key.
43  */
44  kP384PrivateKeyBytes = 48,
45  /**
46  * Bytes in one share of a masked P-384 private key.
47  */
48  kP384MaskedPrivateKeyBytes = 56,
49  /**
50  * Words in one share of a masked P-384 private key.
51  */
52  kP384MaskedPrivateKeyWords = kP384MaskedPrivateKeyBytes / sizeof(uint32_t),
53  /**
54  * Bytes in a P-384 public key coordinate.
55  */
56  kP384CoordinateBytes = 48,
57  /**
58  * Words in a P-384 public key coordinate.
59  */
60  kP384CoordinateWords = kP384CoordinateBytes / sizeof(uint32_t),
61  /**
62  * Bytes in one share of an ECDH/P-384 shared secret.
63  */
64  kP384SharedSecretBytes = 48,
65 };
66 
67 /**
68  * Run ECDH with curve P-256.
69  *
70  * The caller should ensure at least kP256SharedSecretBytes of space are
71  * allocated at `ss`.
72  *
73  * If the cryptolib returns an error saying the input is invalid, the shared
74  * secret will be zero and the `valid` output will be false.
75  *
76  * @param d Private key.
77  * @param qx Public key x coordinate.
78  * @param qx Public key y coordinate.
79  * @param[out] ss Shared secret key.
80  * @param[out] valid Whether the input arguments were valid.
81  * @return Status code (OK or error).
82  */
83 static status_t ecdh_p256(cryptotest_ecdh_private_key_t d,
84  cryptotest_ecdh_coordinate_t qx,
85  cryptotest_ecdh_coordinate_t qy, uint32_t *ss,
86  bool *valid) {
87  if (d.d0_len > kP256MaskedPrivateKeyBytes ||
88  d.d1_len > kP256MaskedPrivateKeyBytes) {
89  LOG_ERROR(
90  "Bad P-256 private key share length (should both be <= %d): (d0 = %d, "
91  "d1 "
92  "= %d)",
93  kP256PrivateKeyBytes, d.d0_len, d.d1_len);
94  return INVALID_ARGUMENT();
95  }
96  if (qx.coordinate_len > kP256CoordinateBytes ||
97  qy.coordinate_len > kP256CoordinateBytes) {
98  LOG_ERROR(
99  "Bad P-256 coordinate length (should both be <= %d): (x = %d, y = %d)",
100  kP256CoordinateBytes, qx.coordinate_len, qy.coordinate_len);
101  return INVALID_ARGUMENT();
102  }
103 
104  // Construct the private key object.
105  // TODO(#20762): once key-import exists for ECDH, use that instead.
106  uint32_t private_keyblob[kP256MaskedPrivateKeyWords * 2];
107  memset(private_keyblob, 0, sizeof(private_keyblob));
108  memcpy(private_keyblob, d.d0, d.d0_len);
109  memcpy(private_keyblob + kP256MaskedPrivateKeyWords, d.d1, d.d1_len);
110  otcrypto_blinded_key_t private_key = {
111  .config =
112  {
113  .version = kOtcryptoLibVersion1,
114  .key_mode = kOtcryptoKeyModeEcdhP256,
115  .key_length = kP256PrivateKeyBytes,
116  .hw_backed = kHardenedBoolFalse,
117  .exportable = kHardenedBoolTrue,
118  .security_level = kOtcryptoKeySecurityLevelLow,
119  },
120  .keyblob_length = sizeof(private_keyblob),
121  .keyblob = private_keyblob,
122  .checksum = 0,
123  };
124 
125  // Construct the public key object.
126  // TODO(#20762): once key-import exists for ECDH, use that instead.
127  uint32_t public_key_buf[kP256CoordinateWords * 2];
128  memset(public_key_buf, 0, sizeof(public_key_buf));
129  memcpy(public_key_buf, qx.coordinate, qx.coordinate_len);
130  memcpy(public_key_buf + kP256CoordinateWords, qy.coordinate,
131  qy.coordinate_len);
132  otcrypto_unblinded_key_t public_key = {
133  .key_mode = kOtcryptoKeyModeEcdhP256,
134  .key_length = sizeof(public_key_buf),
135  .key = public_key_buf,
136  };
137 
138  // Create a destination for the shared secret.
139  size_t shared_secret_words = kP256SharedSecretBytes / sizeof(uint32_t);
140  uint32_t shared_secretblob[shared_secret_words * 2];
141  memset(shared_secretblob, 0, sizeof(shared_secretblob));
142  otcrypto_blinded_key_t shared_secret = {
143  .config =
144  {
145  .version = kOtcryptoLibVersion1,
146  .key_mode = kOtcryptoKeyModeAesCtr,
147  .key_length = kP256SharedSecretBytes,
148  .hw_backed = kHardenedBoolFalse,
149  .exportable = kHardenedBoolTrue,
150  .security_level = kOtcryptoKeySecurityLevelLow,
151  },
152  .keyblob_length = sizeof(shared_secretblob),
153  .keyblob = shared_secretblob,
154  };
155 
157  otcrypto_ecdh_p256(&private_key, &public_key, &shared_secret);
158  switch (status.value) {
159  case kOtcryptoStatusValueOk: {
160  *valid = true;
161  break;
162  }
163  case kOtcryptoStatusValueBadArgs: {
164  *valid = false;
165  break;
166  }
167  default: {
168  TRY(status);
169  break;
170  }
171  }
172 
173  // Unmask the shared secret.
174  uint32_t share0[shared_secret_words];
175  uint32_t share1[shared_secret_words];
177  shared_secret,
178  (otcrypto_word32_buf_t){.data = share0, .len = ARRAYSIZE(share0)},
179  (otcrypto_word32_buf_t){.data = share1, .len = ARRAYSIZE(share1)}));
180  for (size_t i = 0; i < shared_secret_words; i++) {
181  ss[i] = share0[i] ^ share1[i];
182  }
183  return OK_STATUS();
184 }
185 
186 /**
187  * Run ECDH with curve P-384.
188  *
189  * The caller should ensure at least kP384SharedSecretBytes of space are
190  * allocated at `ss`.
191  *
192  * If the cryptolib returns an error saying the input is invalid, the shared
193  * secret will be zero and the `valid` output will be false.
194  *
195  * @param d Private key.
196  * @param qx Public key x coordinate.
197  * @param qx Public key y coordinate.
198  * @param[out] ss Shared secret key.
199  * @param[out] valid Whether the input arguments were valid.
200  * @return Status code (OK or error).
201  */
202 static status_t ecdh_p384(cryptotest_ecdh_private_key_t d,
203  cryptotest_ecdh_coordinate_t qx,
204  cryptotest_ecdh_coordinate_t qy, uint32_t *ss,
205  bool *valid) {
206  if (d.d0_len > kP384MaskedPrivateKeyBytes ||
207  d.d1_len > kP384MaskedPrivateKeyBytes) {
208  LOG_ERROR(
209  "Bad P-384 private key share length (should both be <= %d): (d0 = %d, "
210  "d1 "
211  "= %d)",
212  kP384PrivateKeyBytes, d.d0_len, d.d1_len);
213  return INVALID_ARGUMENT();
214  }
215  if (qx.coordinate_len > kP384CoordinateBytes ||
216  qy.coordinate_len > kP384CoordinateBytes) {
217  LOG_ERROR(
218  "Bad P-384 coordinate length (should both be <= %d): (x = %d, y = %d)",
219  kP384CoordinateBytes, qx.coordinate_len, qy.coordinate_len);
220  return INVALID_ARGUMENT();
221  }
222 
223  // Construct the private key object.
224  // TODO(#20762): once key-import exists for ECDH, use that instead.
225  // Note: the test harness does not produce the extra masking bytes; leave
226  // them zeroed.
227  uint32_t private_keyblob[kP384MaskedPrivateKeyWords * 2];
228  memset(private_keyblob, 0, sizeof(private_keyblob));
229  memcpy(private_keyblob, d.d0, d.d0_len);
230  memcpy(private_keyblob + kP384MaskedPrivateKeyWords, d.d1, d.d1_len);
231  otcrypto_blinded_key_t private_key = {
232  .config =
233  {
234  .version = kOtcryptoLibVersion1,
235  .key_mode = kOtcryptoKeyModeEcdhP384,
236  .key_length = kP384PrivateKeyBytes,
237  .hw_backed = kHardenedBoolFalse,
238  .exportable = kHardenedBoolTrue,
239  .security_level = kOtcryptoKeySecurityLevelLow,
240  },
241  .keyblob_length = sizeof(private_keyblob),
242  .keyblob = private_keyblob,
243  .checksum = 0,
244  };
245 
246  // Construct the public key object.
247  // TODO(#20762): once key-import exists for ECDH, use that instead.
248  uint32_t public_key_buf[kP384CoordinateWords * 2];
249  memset(public_key_buf, 0, sizeof(public_key_buf));
250  memcpy(public_key_buf, qx.coordinate, qx.coordinate_len);
251  memcpy(public_key_buf + kP384CoordinateWords, qy.coordinate,
252  qy.coordinate_len);
253  otcrypto_unblinded_key_t public_key = {
254  .key_mode = kOtcryptoKeyModeEcdhP384,
255  .key_length = sizeof(public_key_buf),
256  .key = public_key_buf,
257  };
258 
259  // Create a destination for the shared secret.
260  size_t shared_secret_words = kP384SharedSecretBytes / sizeof(uint32_t);
261  uint32_t shared_secretblob[shared_secret_words * 2];
262  memset(shared_secretblob, 0, sizeof(shared_secretblob));
263  otcrypto_blinded_key_t shared_secret = {
264  .config =
265  {
266  .version = kOtcryptoLibVersion1,
267  .key_mode = kOtcryptoKeyModeAesCtr,
268  .key_length = kP384SharedSecretBytes,
269  .hw_backed = kHardenedBoolFalse,
270  .exportable = kHardenedBoolTrue,
271  .security_level = kOtcryptoKeySecurityLevelLow,
272  },
273  .keyblob_length = sizeof(shared_secretblob),
274  .keyblob = shared_secretblob,
275  };
276 
278  otcrypto_ecdh_p384(&private_key, &public_key, &shared_secret);
279  switch (status.value) {
280  case kOtcryptoStatusValueOk: {
281  *valid = true;
282  break;
283  }
284  case kOtcryptoStatusValueBadArgs: {
285  *valid = false;
286  break;
287  }
288  default: {
289  TRY(status);
290  break;
291  }
292  }
293 
294  // Unmask the shared secret.
295  uint32_t share0[shared_secret_words];
296  uint32_t share1[shared_secret_words];
298  shared_secret,
299  (otcrypto_word32_buf_t){.data = share0, .len = ARRAYSIZE(share0)},
300  (otcrypto_word32_buf_t){.data = share1, .len = ARRAYSIZE(share1)}));
301  for (size_t i = 0; i < shared_secret_words; i++) {
302  ss[i] = share0[i] ^ share1[i];
303  }
304  return OK_STATUS();
305 }
306 
307 status_t handle_ecdh(ujson_t *uj) {
308  // Declare ECDH parameter ujson deserializer types
309  cryptotest_ecdh_curve_t uj_curve;
310  cryptotest_ecdh_private_key_t uj_private_key;
311  cryptotest_ecdh_coordinate_t uj_qx;
312  cryptotest_ecdh_coordinate_t uj_qy;
313 
314  // Deserialize ujson byte stream into ECDH parameters
315  TRY(ujson_deserialize_cryptotest_ecdh_curve_t(uj, &uj_curve));
316  TRY(ujson_deserialize_cryptotest_ecdh_private_key_t(uj, &uj_private_key));
317  TRY(ujson_deserialize_cryptotest_ecdh_coordinate_t(uj, &uj_qx));
318  TRY(ujson_deserialize_cryptotest_ecdh_coordinate_t(uj, &uj_qy));
319 
320  cryptotest_ecdh_derive_output_t uj_output;
321  bool valid;
322  switch (uj_curve) {
323  case kCryptotestEcdhCurveP256: {
324  uint32_t shared_secret_words[kP256SharedSecretBytes / sizeof(uint32_t)];
325  TRY(ecdh_p256(uj_private_key, uj_qx, uj_qy, shared_secret_words, &valid));
326  memcpy(uj_output.shared_secret, shared_secret_words,
327  kP256SharedSecretBytes);
328  uj_output.shared_secret_len = kP256SharedSecretBytes;
329  break;
330  }
331  case kCryptotestEcdhCurveP384: {
332  uint32_t shared_secret_words[kP384SharedSecretBytes / sizeof(uint32_t)];
333  TRY(ecdh_p384(uj_private_key, uj_qx, uj_qy, shared_secret_words, &valid));
334  memcpy(uj_output.shared_secret, shared_secret_words,
335  kP384SharedSecretBytes);
336  uj_output.shared_secret_len = kP384SharedSecretBytes;
337  break;
338  }
339  default:
340  LOG_ERROR("Unsupported ECC curve: %d", uj_curve);
341  return INVALID_ARGUMENT();
342  }
343  uj_output.ok = (uint8_t)valid;
344 
345  RESP_OK(ujson_serialize_cryptotest_ecdh_derive_output_t, uj, &uj_output);
346  return OK_STATUS(0);
347 }