1 /*
2 * Copyright 2021 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/remote_provisioning_utils.h>
18
19 #include <algorithm>
20 #include <span>
21
22 #include <keymaster/cppcose/cppcose.h>
23 #include <keymaster/logger.h>
24
25 namespace keymaster {
26
27 using cppcose::ALGORITHM;
28 using cppcose::COSE_KEY;
29 using cppcose::CoseKey;
30 using cppcose::CoseKeyCurve;
31 using cppcose::EC2;
32 using cppcose::ECDH_ES_HKDF_256;
33 using cppcose::ES256;
34 using cppcose::generateCoseMac0Mac;
35 using cppcose::HMAC_256;
36 using cppcose::kCoseMac0EntryCount;
37 using cppcose::kCoseMac0Payload;
38 using cppcose::kCoseMac0ProtectedParams;
39 using cppcose::kCoseMac0Tag;
40 using cppcose::kCoseMac0UnprotectedParams;
41 using cppcose::KEY_ID;
42 using cppcose::OCTET_KEY_PAIR;
43 using cppcose::P256;
44 using cppcose::verifyAndParseCoseSign1;
45
46 using byte_view = std::span<const uint8_t>;
47
48 struct KeyInfo {
49 CoseKeyCurve curve;
50 byte_view pubkey;
51 // Note: There's no need to include algorithm here, since it is assumed
52 // that all root keys are EDDSA.
53
operator ==keymaster::KeyInfo54 bool operator==(const KeyInfo& other) const {
55 return curve == other.curve &&
56 std::equal(pubkey.begin(), pubkey.end(), other.pubkey.begin(), other.pubkey.end());
57 }
58 };
59
60 // The production root signing key for Google Endpoint Encryption Key cert chains.
61 inline constexpr uint8_t kGeekRoot[] = {
62 0x99, 0xB9, 0xEE, 0xDD, 0x5E, 0xE4, 0x52, 0xF6, 0x85, 0xC6, 0x4C, 0x62, 0xDC, 0x3E, 0x61, 0xAB,
63 0x57, 0x48, 0x7D, 0x75, 0x37, 0x29, 0xAD, 0x76, 0x80, 0x32, 0xD2, 0xB3, 0xCB, 0x63, 0x58, 0xD9};
64
65 // Hard-coded set of acceptable public COSE_Keys that can act as roots of EEK chains.
66 inline constexpr KeyInfo kAuthorizedEekRoots[] = {
67 {CoseKeyCurve::ED25519, byte_view(kGeekRoot, sizeof(kGeekRoot))},
68 };
69
70 StatusOr<std::pair<std::vector<uint8_t> /* EEK pub */, std::vector<uint8_t> /* EEK ID */>>
validateAndExtractEekPubAndId(bool testMode,const KeymasterBlob & endpointEncryptionCertChain)71 validateAndExtractEekPubAndId(bool testMode, const KeymasterBlob& endpointEncryptionCertChain) {
72 auto [item, newPos, errMsg] =
73 cppbor::parse(endpointEncryptionCertChain.begin(), endpointEncryptionCertChain.end());
74
75 if (!item || !item->asArray()) {
76 LOG_E("Error parsing EEK chain: %s", errMsg.c_str());
77 return kStatusFailed;
78 }
79
80 const cppbor::Array* certArr = item->asArray();
81 std::vector<uint8_t> lastPubKey;
82 for (size_t i = 0; i < certArr->size(); ++i) {
83 auto cosePubKey =
84 verifyAndParseCoseSign1(certArr->get(i)->asArray(), lastPubKey, {} /* AAD */);
85 if (!cosePubKey) {
86 LOG_E("Failed to validate EEK chain: %s", cosePubKey.moveMessage().c_str());
87 return kStatusInvalidEek;
88 }
89 lastPubKey = *std::move(cosePubKey);
90
91 // In prod mode the first pubkey should match a well-known Google public key.
92 if (!testMode && i == 0) {
93 auto parsedPubKey = CoseKey::parse(lastPubKey);
94 if (!parsedPubKey) {
95 LOG_E("%s", parsedPubKey.moveMessage().c_str());
96 return kStatusFailed;
97 }
98
99 auto curve = parsedPubKey->getIntValue(CoseKey::CURVE);
100 if (!curve) {
101 LOG_E("Key is missing required label 'CURVE'");
102 return kStatusInvalidEek;
103 }
104
105 auto rawPubKey = parsedPubKey->getBstrValue(CoseKey::PUBKEY_X);
106 if (!rawPubKey) {
107 LOG_E("Key is missing required label 'PUBKEY_X'");
108 return kStatusInvalidEek;
109 }
110
111 KeyInfo matcher = {static_cast<CoseKeyCurve>(*curve),
112 byte_view(rawPubKey->data(), rawPubKey->size())};
113 if (std::find(std::begin(kAuthorizedEekRoots), std::end(kAuthorizedEekRoots),
114 matcher) == std::end(kAuthorizedEekRoots)) {
115 LOG_E("Unrecognized root of EEK chain");
116 return kStatusInvalidEek;
117 }
118 }
119 }
120
121 auto eek = CoseKey::parseX25519(lastPubKey, true /* requireKid */);
122 if (!eek) {
123 LOG_E("Failed to get EEK: %s", eek.moveMessage().c_str());
124 return kStatusInvalidEek;
125 }
126
127 return std::make_pair(eek->getBstrValue(CoseKey::PUBKEY_X).value(),
128 eek->getBstrValue(CoseKey::KEY_ID).value());
129 }
130
131 StatusOr<cppbor::Array /* pubkeys */>
validateAndExtractPubkeys(bool testMode,uint32_t numKeys,KeymasterBlob * keysToSign,const cppcose::HmacSha256Function & macFunction)132 validateAndExtractPubkeys(bool testMode, uint32_t numKeys, KeymasterBlob* keysToSign,
133 const cppcose::HmacSha256Function& macFunction) {
134 auto pubKeysToMac = cppbor::Array();
135 for (size_t i = 0; i < numKeys; i++) {
136 auto [macedKeyItem, _, coseMacErrMsg] =
137 cppbor::parse(keysToSign[i].begin(), keysToSign[i].end());
138 if (!macedKeyItem || !macedKeyItem->asArray() ||
139 macedKeyItem->asArray()->size() != kCoseMac0EntryCount) {
140 LOG_E("Invalid COSE_Mac0 structure");
141 return kStatusFailed;
142 }
143
144 auto protectedParms = macedKeyItem->asArray()->get(kCoseMac0ProtectedParams)->asBstr();
145 auto unprotectedParms = macedKeyItem->asArray()->get(kCoseMac0UnprotectedParams)->asMap();
146 auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr();
147 auto tag = macedKeyItem->asArray()->get(kCoseMac0Tag)->asBstr();
148 if (!protectedParms || !unprotectedParms || !payload || !tag) {
149 LOG_E("Invalid COSE_Mac0 contents");
150 return kStatusFailed;
151 }
152
153 auto [protectedMap, __, errMsg] = cppbor::parse(protectedParms);
154 if (!protectedMap || !protectedMap->asMap()) {
155 LOG_E("Invalid Mac0 protected: %s", errMsg.c_str());
156 return kStatusFailed;
157 }
158 auto& algo = protectedMap->asMap()->get(ALGORITHM);
159 if (!algo || !algo->asInt() || algo->asInt()->value() != HMAC_256) {
160 LOG_E("Unsupported Mac0 algorithm");
161 return kStatusFailed;
162 }
163
164 auto pubKey = CoseKey::parse(payload->value(), EC2, ES256, P256);
165 if (!pubKey) {
166 LOG_E("%s", pubKey.moveMessage().c_str());
167 return kStatusFailed;
168 }
169
170 bool testKey = static_cast<bool>(pubKey->getMap().get(CoseKey::TEST_KEY));
171 if (testMode && !testKey) {
172 LOG_E("Production key in test request");
173 return kStatusProductionKeyInTestRequest;
174 } else if (!testMode && testKey) {
175 LOG_E("Test key in production request");
176 return kStatusTestKeyInProductionRequest;
177 }
178
179 auto macTag = generateCoseMac0Mac(macFunction, {} /* external_aad */, payload->value());
180 if (!macTag) {
181 LOG_E("%s", macTag.moveMessage().c_str());
182 return kStatusInvalidMac;
183 }
184 if (macTag->size() != tag->value().size() ||
185 CRYPTO_memcmp(macTag->data(), tag->value().data(), macTag->size()) != 0) {
186 LOG_E("MAC tag mismatch");
187 return kStatusInvalidMac;
188 }
189
190 pubKeysToMac.add(pubKey->moveMap());
191 }
192
193 return pubKeysToMac;
194 }
195
buildCertReqRecipients(const std::vector<uint8_t> & pubkey,const std::vector<uint8_t> & kid)196 cppbor::Array buildCertReqRecipients(const std::vector<uint8_t>& pubkey,
197 const std::vector<uint8_t>& kid) {
198 return cppbor::Array() // Array of recipients
199 .add(cppbor::Array() // Recipient
200 .add(cppbor::Map() // Protected
201 .add(ALGORITHM, ECDH_ES_HKDF_256)
202 .canonicalize()
203 .encode())
204 .add(cppbor::Map() // Unprotected
205 .add(COSE_KEY, cppbor::Map()
206 .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR)
207 .add(CoseKey::CURVE, cppcose::X25519)
208 .add(CoseKey::PUBKEY_X, pubkey)
209 .canonicalize())
210 .add(KEY_ID, kid)
211 .canonicalize())
212 .add(cppbor::Null())); // No ciphertext
213 }
214
215 } // namespace keymaster
216