blob: e17f273bbaa885e5581c425156d6dbac4fa24bd7 [file] [log] [blame]
Max Bires57c187a2021-03-03 16:30:16 -08001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <keymaster/cppcose/cppcose.h>
18
19#include <iostream>
20#include <stdio.h>
21
22#include <cppbor.h>
23#include <cppbor_parse.h>
24
25#include <openssl/err.h>
26
27namespace cppcose {
28
29namespace {
30
31ErrMsgOr<bssl::UniquePtr<EVP_CIPHER_CTX>> aesGcmInitAndProcessAad(const bytevec& key,
32 const bytevec& nonce,
33 const bytevec& aad,
34 bool encrypt) {
35 if (key.size() != kAesGcmKeySize) return "Invalid key size";
36
37 bssl::UniquePtr<EVP_CIPHER_CTX> ctx(EVP_CIPHER_CTX_new());
38 if (!ctx) return "Failed to allocate cipher context";
39
40 if (!EVP_CipherInit_ex(ctx.get(), EVP_aes_256_gcm(), nullptr /* engine */, key.data(),
41 nonce.data(), encrypt ? 1 : 0)) {
42 return "Failed to initialize cipher";
43 }
44
45 int outlen;
46 if (!aad.empty() && !EVP_CipherUpdate(ctx.get(), nullptr /* out; null means AAD */, &outlen,
47 aad.data(), aad.size())) {
48 return "Failed to process AAD";
49 }
50
51 return std::move(ctx);
52}
53
54} // namespace
55
56ErrMsgOr<bytevec> generateCoseMac0Mac(const bytevec& macKey, const bytevec& externalAad,
57 const bytevec& payload) {
Seth Moore448141b2021-04-22 20:34:03 +000058 if (macKey.empty()) {
59 return "Empty MAC key";
60 }
61
Max Bires57c187a2021-03-03 16:30:16 -080062 auto macStructure = cppbor::Array()
63 .add("MAC0")
64 .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
65 .add(externalAad)
66 .add(payload)
67 .encode();
68
69 bytevec macTag(SHA256_DIGEST_LENGTH);
70 uint8_t* out = macTag.data();
71 unsigned int outLen;
72 out = HMAC(EVP_sha256(), //
73 macKey.data(), macKey.size(), //
74 macStructure.data(), macStructure.size(), //
75 out, &outLen);
76
77 assert(out != nullptr && outLen == macTag.size());
78 if (out == nullptr || outLen != macTag.size()) {
79 return "Error computing public key MAC";
80 }
81
82 return macTag;
83}
84
85ErrMsgOr<cppbor::Array> constructCoseMac0(const bytevec& macKey, const bytevec& externalAad,
86 const bytevec& payload) {
87 auto tag = generateCoseMac0Mac(macKey, externalAad, payload);
88 if (!tag) return tag.moveMessage();
89
90 return cppbor::Array()
91 .add(cppbor::Map().add(ALGORITHM, HMAC_256).canonicalize().encode())
92 .add(cppbor::Map() /* unprotected */)
93 .add(payload)
94 .add(tag.moveValue());
95}
96
97ErrMsgOr<bytevec /* payload */> verifyAndParseCoseMac0(const cppbor::Item* macItem,
98 const bytevec& macKey) {
99 auto mac = macItem ? macItem->asArray() : nullptr;
100 if (!mac || mac->size() != kCoseMac0EntryCount) {
101 return "Invalid COSE_Mac0";
102 }
103
104 auto protectedParms = mac->get(kCoseMac0ProtectedParams)->asBstr();
105 auto unprotectedParms = mac->get(kCoseMac0UnprotectedParams)->asMap();
106 auto payload = mac->get(kCoseMac0Payload)->asBstr();
107 auto tag = mac->get(kCoseMac0Tag)->asBstr();
108 if (!protectedParms || !unprotectedParms || !payload || !tag) {
109 return "Invalid COSE_Mac0 contents";
110 }
111
112 auto [protectedMap, _, errMsg] = cppbor::parse(protectedParms);
113 if (!protectedMap || !protectedMap->asMap()) {
114 return "Invalid Mac0 protected: " + errMsg;
115 }
116 auto& algo = protectedMap->asMap()->get(ALGORITHM);
117 if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
118 return "Unsupported Mac0 algorithm";
119 }
120
121 auto macTag = generateCoseMac0Mac(macKey, {} /* external_aad */, payload->value());
122 if (!macTag) return macTag.moveMessage();
123
124 if (macTag->size() != tag->value().size() ||
125 CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
126 return "MAC tag mismatch";
127 }
128
129 return payload->value();
130}
131
132ErrMsgOr<bytevec> createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams,
133 const bytevec& payload, const bytevec& aad) {
134 bytevec signatureInput = cppbor::Array()
135 .add("Signature1") //
136 .add(protectedParams)
137 .add(aad)
138 .add(payload)
139 .encode();
140
141 if (key.size() != ED25519_PRIVATE_KEY_LEN) return "Invalid signing key";
142 bytevec signature(ED25519_SIGNATURE_LEN);
143 if (!ED25519_sign(signature.data(), signatureInput.data(), signatureInput.size(), key.data())) {
144 return "Signing failed";
145 }
146
147 return signature;
148}
149
150ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, cppbor::Map protectedParams,
151 const bytevec& payload, const bytevec& aad) {
152 bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode();
153 auto signature = createCoseSign1Signature(key, protParms, payload, aad);
154 if (!signature) return signature.moveMessage();
155
156 return cppbor::Array()
157 .add(std::move(protParms))
158 .add(cppbor::Map() /* unprotected parameters */)
159 .add(std::move(payload))
160 .add(std::move(*signature));
161}
162
163ErrMsgOr<cppbor::Array> constructCoseSign1(const bytevec& key, const bytevec& payload,
164 const bytevec& aad) {
165 return constructCoseSign1(key, {} /* protectedParams */, payload, aad);
166}
167
168ErrMsgOr<bytevec> verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1,
169 const bytevec& signingCoseKey, const bytevec& aad) {
170 if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) {
171 return "Invalid COSE_Sign1";
172 }
173
174 const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr();
175 const cppbor::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap();
176 const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr();
Max Bires57c187a2021-03-03 16:30:16 -0800177
Seth Moore448141b2021-04-22 20:34:03 +0000178 if (!protectedParams || !unprotectedParams || !payload) {
179 return "Missing input parameters";
Max Bires57c187a2021-03-03 16:30:16 -0800180 }
181
182 auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams);
183 if (!parsedProtParams) {
184 return errMsg + " when parsing protected params.";
185 }
186 if (!parsedProtParams->asMap()) {
187 return "Protected params must be a map";
188 }
189
190 auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
191 if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) {
192 return "Unsupported signature algorithm";
193 }
194
195 if (!ignoreSignature) {
Seth Moore448141b2021-04-22 20:34:03 +0000196 const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr();
197 if (!signature || signature->value().empty()) {
198 return "Missing signature input";
199 }
200
Max Bires57c187a2021-03-03 16:30:16 -0800201 bool selfSigned = signingCoseKey.empty();
202 auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey);
Seth Moore448141b2021-04-22 20:34:03 +0000203 if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty()) {
204 return "Bad signing key: " + key.moveMessage();
205 }
Max Bires57c187a2021-03-03 16:30:16 -0800206
207 bytevec signatureInput =
208 cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode();
209
210 if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(),
211 key->getBstrValue(CoseKey::PUBKEY_X)->data())) {
212 return "Signature verification failed";
213 }
214 }
215
216 return payload->value();
217}
218
219ErrMsgOr<bytevec> createCoseEncryptCiphertext(const bytevec& key, const bytevec& nonce,
220 const bytevec& protectedParams,
221 const bytevec& plaintextPayload, const bytevec& aad) {
222 auto ciphertext = aesGcmEncrypt(key, nonce,
223 cppbor::Array() // Enc strucure as AAD
224 .add("Encrypt") // Context
225 .add(protectedParams) // Protected
226 .add(aad) // External AAD
227 .encode(),
228 plaintextPayload);
229
230 if (!ciphertext) return ciphertext.moveMessage();
231 return ciphertext.moveValue();
232}
233
234ErrMsgOr<cppbor::Array> constructCoseEncrypt(const bytevec& key, const bytevec& nonce,
235 const bytevec& plaintextPayload, const bytevec& aad,
236 cppbor::Array recipients) {
237 auto encryptProtectedHeader = cppbor::Map() //
238 .add(ALGORITHM, AES_GCM_256)
239 .canonicalize()
240 .encode();
241
242 auto ciphertext =
243 createCoseEncryptCiphertext(key, nonce, encryptProtectedHeader, plaintextPayload, aad);
244 if (!ciphertext) return ciphertext.moveMessage();
245
246 return cppbor::Array()
247 .add(encryptProtectedHeader) // Protected
248 .add(cppbor::Map().add(IV, nonce).canonicalize()) // Unprotected
249 .add(*ciphertext) // Payload
250 .add(std::move(recipients));
251}
252
253ErrMsgOr<std::pair<bytevec /* pubkey */, bytevec /* key ID */>>
254getSenderPubKeyFromCoseEncrypt(const cppbor::Item* coseEncrypt) {
255 if (!coseEncrypt || !coseEncrypt->asArray() ||
256 coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
257 return "Invalid COSE_Encrypt";
258 }
259
260 auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
261 if (!recipients || !recipients->asArray() || recipients->asArray()->size() != 1) {
262 return "Invalid recipients list";
263 }
264
265 auto& recipient = recipients->asArray()->get(0);
266 if (!recipient || !recipient->asArray() || recipient->asArray()->size() != 3) {
267 return "Invalid COSE_recipient";
268 }
269
270 auto& ciphertext = recipient->asArray()->get(2);
271 if (!ciphertext->asSimple() || !ciphertext->asSimple()->asNull()) {
272 return "Unexpected value in recipients ciphertext field " +
273 cppbor::prettyPrint(ciphertext.get());
274 }
275
276 auto& protParms = recipient->asArray()->get(0);
277 if (!protParms || !protParms->asBstr()) return "Invalid protected params";
278 auto [parsedProtParms, _, errMsg] = cppbor::parse(protParms->asBstr());
279 if (!parsedProtParms) return "Failed to parse protected params: " + errMsg;
280 if (!parsedProtParms->asMap()) return "Invalid protected params";
281
282 auto& algorithm = parsedProtParms->asMap()->get(ALGORITHM);
283 if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != ECDH_ES_HKDF_256) {
284 return "Invalid algorithm";
285 }
286
287 auto& unprotParms = recipient->asArray()->get(1);
288 if (!unprotParms || !unprotParms->asMap()) return "Invalid unprotected params";
289
290 auto& senderCoseKey = unprotParms->asMap()->get(COSE_KEY);
291 if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key";
292
293 auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE);
294 if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) {
295 return "Invalid key type";
296 }
297
298 auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE);
299 if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) {
300 return "Unsupported curve";
301 }
302
303 auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X);
304 if (!pubkey || !pubkey->asBstr() ||
305 pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) {
306 return "Invalid X25519 public key";
307 }
308
309 auto& key_id = unprotParms->asMap()->get(KEY_ID);
310 if (key_id && key_id->asBstr()) {
311 return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value());
312 }
313
314 // If no key ID, just return an empty vector.
315 return std::make_pair(pubkey->asBstr()->value(), bytevec{});
316}
317
318ErrMsgOr<bytevec> decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt,
319 const bytevec& external_aad) {
320 if (!coseEncrypt || !coseEncrypt->asArray() ||
321 coseEncrypt->asArray()->size() != kCoseEncryptEntryCount) {
322 return "Invalid COSE_Encrypt";
323 }
324
325 auto& protParms = coseEncrypt->asArray()->get(kCoseEncryptProtectedParams);
326 auto& unprotParms = coseEncrypt->asArray()->get(kCoseEncryptUnprotectedParams);
327 auto& ciphertext = coseEncrypt->asArray()->get(kCoseEncryptPayload);
328 auto& recipients = coseEncrypt->asArray()->get(kCoseEncryptRecipients);
329
330 if (!protParms || !protParms->asBstr() || !unprotParms || !ciphertext || !recipients) {
331 return "Invalid COSE_Encrypt";
332 }
333
334 auto [parsedProtParams, _, errMsg] = cppbor::parse(protParms->asBstr()->value());
335 if (!parsedProtParams) {
336 return errMsg + " when parsing protected params.";
337 }
338 if (!parsedProtParams->asMap()) {
339 return "Protected params must be a map";
340 }
341
342 auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM);
343 if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != AES_GCM_256) {
344 return "Unsupported encryption algorithm";
345 }
346
347 if (!unprotParms->asMap() || unprotParms->asMap()->size() != 1) {
348 return "Invalid unprotected params";
349 }
350
351 auto& nonce = unprotParms->asMap()->get(IV);
352 if (!nonce || !nonce->asBstr() || nonce->asBstr()->value().size() != kAesGcmNonceLength) {
353 return "Invalid nonce";
354 }
355
356 if (!ciphertext->asBstr()) return "Invalid ciphertext";
357
358 auto aad = cppbor::Array() // Enc strucure as AAD
359 .add("Encrypt") // Context
360 .add(protParms->asBstr()->value()) // Protected
361 .add(external_aad) // External AAD
362 .encode();
363
364 return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value());
365}
366
367ErrMsgOr<bytevec> x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA,
368 const bytevec& pubKeyB, bool senderIsA) {
Seth Moore448141b2021-04-22 20:34:03 +0000369 if (privKeyA.empty() || pubKeyA.empty() || pubKeyB.empty()) {
370 return "Missing input key parameters";
371 }
372
Max Bires57c187a2021-03-03 16:30:16 -0800373 bytevec rawSharedKey(X25519_SHARED_KEY_LEN);
374 if (!::X25519(rawSharedKey.data(), privKeyA.data(), pubKeyB.data())) {
375 return "ECDH operation failed";
376 }
377
378 bytevec kdfContext = cppbor::Array()
379 .add(AES_GCM_256)
380 .add(cppbor::Array() // Sender Info
381 .add(cppbor::Bstr("client"))
382 .add(bytevec{} /* nonce */)
383 .add(senderIsA ? pubKeyA : pubKeyB))
384 .add(cppbor::Array() // Recipient Info
385 .add(cppbor::Bstr("server"))
386 .add(bytevec{} /* nonce */)
387 .add(senderIsA ? pubKeyB : pubKeyA))
388 .add(cppbor::Array() // SuppPubInfo
389 .add(kAesGcmKeySizeBits) // output key length
390 .add(bytevec{})) // protected
391 .encode();
392
393 bytevec retval(SHA256_DIGEST_LENGTH);
394 bytevec salt{};
395 if (!HKDF(retval.data(), retval.size(), //
396 EVP_sha256(), //
397 rawSharedKey.data(), rawSharedKey.size(), //
398 salt.data(), salt.size(), //
399 kdfContext.data(), kdfContext.size())) {
400 return "ECDH HKDF failed";
401 }
402
403 return retval;
404}
405
406ErrMsgOr<bytevec> aesGcmEncrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
407 const bytevec& plaintext) {
408 auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, true /* encrypt */);
409 if (!ctx) return ctx.moveMessage();
410
411 bytevec ciphertext(plaintext.size() + kAesGcmTagSize);
412 int outlen;
413 if (!EVP_CipherUpdate(ctx->get(), ciphertext.data(), &outlen, plaintext.data(),
414 plaintext.size())) {
415 return "Failed to encrypt plaintext";
416 }
417 assert(plaintext.size() == outlen);
418
419 if (!EVP_CipherFinal_ex(ctx->get(), ciphertext.data() + outlen, &outlen)) {
420 return "Failed to finalize encryption";
421 }
422 assert(outlen == 0);
423
424 if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_GET_TAG, kAesGcmTagSize,
425 ciphertext.data() + plaintext.size())) {
426 return "Failed to retrieve tag";
427 }
428
429 return ciphertext;
430}
431
432ErrMsgOr<bytevec> aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const bytevec& aad,
433 const bytevec& ciphertextWithTag) {
434 auto ctx = aesGcmInitAndProcessAad(key, nonce, aad, false /* encrypt */);
435 if (!ctx) return ctx.moveMessage();
436
437 if (ciphertextWithTag.size() < kAesGcmTagSize) return "Missing tag";
438
439 bytevec plaintext(ciphertextWithTag.size() - kAesGcmTagSize);
440 int outlen;
441 if (!EVP_CipherUpdate(ctx->get(), plaintext.data(), &outlen, ciphertextWithTag.data(),
442 ciphertextWithTag.size() - kAesGcmTagSize)) {
443 return "Failed to decrypt plaintext";
444 }
445 assert(plaintext.size() == outlen);
446
447 bytevec tag(ciphertextWithTag.end() - kAesGcmTagSize, ciphertextWithTag.end());
448 if (!EVP_CIPHER_CTX_ctrl(ctx->get(), EVP_CTRL_GCM_SET_TAG, kAesGcmTagSize, tag.data())) {
449 return "Failed to set tag: " + std::to_string(ERR_peek_last_error());
450 }
451
452 if (!EVP_CipherFinal_ex(ctx->get(), nullptr, &outlen)) {
453 return "Failed to finalize encryption";
454 }
455 assert(outlen == 0);
456
457 return plaintext;
458}
459
460} // namespace cppcose