Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2024 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
19 : #include <credentials/attestation_verifier/TestDACRevocationDelegateImpl.h>
20 : #include <lib/support/Base64.h>
21 : #include <lib/support/BytesToHex.h>
22 : #include <lib/support/logging/CHIPLogging.h>
23 :
24 : #include <algorithm>
25 : #include <fstream>
26 : #include <json/json.h>
27 :
28 : using namespace chip::Crypto;
29 :
30 : namespace chip {
31 : namespace Credentials {
32 :
33 : namespace {
34 :
35 51 : CHIP_ERROR BytesToHexStr(const ByteSpan & bytes, std::string & outHexStr)
36 : {
37 51 : size_t hexLength = bytes.size() * 2;
38 51 : outHexStr.resize(hexLength);
39 :
40 51 : Encoding::HexFlags flags = Encoding::HexFlags::kUppercase;
41 51 : return BytesToHex(bytes.data(), bytes.size(), &outHexStr[0], hexLength, flags);
42 : }
43 :
44 : } // anonymous namespace
45 :
46 0 : CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationSetPath(std::string_view path)
47 : {
48 0 : VerifyOrReturnError(path.empty() != true, CHIP_ERROR_INVALID_ARGUMENT);
49 0 : mDeviceAttestationRevocationSetPath = path;
50 0 : return CHIP_NO_ERROR;
51 : }
52 :
53 11 : CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationData(const std::string & jsonData)
54 : {
55 11 : mRevocationData = jsonData;
56 11 : return CHIP_NO_ERROR;
57 : }
58 :
59 0 : void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationSetPath()
60 : {
61 : // clear the string_view
62 0 : mDeviceAttestationRevocationSetPath = mDeviceAttestationRevocationSetPath.substr(0, 0);
63 0 : }
64 :
65 11 : void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationData()
66 : {
67 11 : mRevocationData.clear();
68 11 : }
69 :
70 : // Check if issuer and AKID matches with the crl signer OR crl signer delegator's subject and SKID
71 8 : bool TestDACRevocationDelegateImpl::CrossValidateCert(const Json::Value & revokedSet, const std::string & akidHexStr,
72 : const std::string & issuerNameBase64Str)
73 : {
74 8 : std::string certBase64;
75 8 : [[maybe_unused]] std::string certType;
76 :
77 8 : if (revokedSet.isMember("crl_signer_delegator"))
78 : {
79 1 : certBase64 = revokedSet["crl_signer_delegator"].asString();
80 1 : certType = "CRL Signer delegator";
81 : }
82 : else
83 : {
84 7 : certBase64 = revokedSet["crl_signer_cert"].asString();
85 7 : certType = "CRL Signer";
86 : }
87 :
88 8 : uint8_t certDerBuf[kMax_x509_Certificate_Length] = { 0 };
89 8 : MutableByteSpan certDER(certDerBuf);
90 :
91 : // Verify we have enough room to store the decoded certificate
92 8 : size_t maxDecodeLen = BASE64_MAX_DECODED_LEN(certBase64.size());
93 8 : VerifyOrReturnValue(certDER.size() >= maxDecodeLen, false);
94 :
95 8 : uint16_t derLen = Base64Decode(certBase64.c_str(), static_cast<uint16_t>(certBase64.size()), certDER.data());
96 8 : VerifyOrReturnValue(derLen != UINT16_MAX, false);
97 8 : certDER.reduce_size(derLen);
98 :
99 8 : std::string subject;
100 8 : std::string keyId;
101 :
102 8 : VerifyOrReturnValue(CHIP_NO_ERROR == GetSubjectNameBase64Str(certDER, subject), false);
103 7 : VerifyOrReturnValue(CHIP_NO_ERROR == GetSKIDHexStr(certDER, keyId), false);
104 :
105 7 : ChipLogDetail(NotSpecified, "%s: Subject: %s", certType.c_str(), subject.c_str());
106 7 : ChipLogDetail(NotSpecified, "%s: SKID: %s", certType.c_str(), keyId.c_str());
107 :
108 7 : return (akidHexStr == keyId && issuerNameBase64Str == subject);
109 8 : }
110 :
111 : // This method parses the below JSON Scheme
112 : // [
113 : // {
114 : // "type": "revocation_set",
115 : // "issuer_subject_key_id": "<issuer subject key ID as uppercase hex, 20 bytes>",
116 : // "issuer_name": "<ASN.1 SEQUENCE of Issuer of the CRL as base64>",
117 : // "revoked_serial_numbers: [
118 : // "serial1 bytes as base64",
119 : // "serial2 bytes as base64"
120 : // ]
121 : // "crl_signer_cert": "<base64 encoded DER certificate>",
122 : // "crl_signer_delegator": "<base64 encoded DER certificate>",
123 : // }
124 : // ]
125 : //
126 22 : bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const std::string & akidHexStr, const std::string & issuerNameBase64Str,
127 : const std::string & serialNumberHexStr)
128 : {
129 22 : Json::Value jsonData;
130 :
131 : // Try direct data first, then fall back to file
132 22 : if (!mRevocationData.empty())
133 : {
134 22 : std::string errs;
135 44 : std::istringstream jsonStream(!mRevocationData.empty() ? mRevocationData : "[]");
136 22 : if (!Json::parseFromStream(Json::CharReaderBuilder(), jsonStream, &jsonData, &errs))
137 : {
138 2 : ChipLogError(NotSpecified, "Failed to parse JSON data: %s", errs.c_str());
139 2 : return false;
140 : }
141 24 : }
142 0 : else if (!mDeviceAttestationRevocationSetPath.empty())
143 : {
144 0 : std::string errs;
145 0 : std::ifstream file(mDeviceAttestationRevocationSetPath.c_str());
146 0 : if (!file.is_open())
147 : {
148 0 : ChipLogError(NotSpecified, "Failed to open file: %s", mDeviceAttestationRevocationSetPath.c_str());
149 0 : return false;
150 : }
151 :
152 0 : bool parsingSuccessful = Json::parseFromStream(Json::CharReaderBuilder(), file, &jsonData, &errs);
153 0 : file.close();
154 :
155 0 : if (!parsingSuccessful)
156 : {
157 0 : ChipLogError(NotSpecified, "Failed to parse JSON from file: %s", errs.c_str());
158 0 : return false;
159 : }
160 0 : }
161 : else
162 : {
163 0 : ChipLogDetail(NotSpecified, "No revocation data available");
164 : // No revocation data available
165 0 : return false;
166 : }
167 :
168 : // 6.2.4.2. Determining Revocation Status of an Entity
169 37 : for (const auto & revokedSet : jsonData)
170 : {
171 23 : if (revokedSet["issuer_name"].asString() != issuerNameBase64Str)
172 : {
173 13 : continue;
174 : }
175 10 : if (revokedSet["issuer_subject_key_id"].asString() != akidHexStr)
176 : {
177 2 : continue;
178 : }
179 :
180 : // 4.a cross validate PAI with crl signer OR crl signer delegator
181 : // 4.b cross validate DAC with crl signer OR crl signer delegator
182 13 : VerifyOrReturnValue(CrossValidateCert(revokedSet, akidHexStr, issuerNameBase64Str), false);
183 :
184 : // 4.c check if serial number is revoked
185 10 : for (const auto & revokedSerialNumber : revokedSet["revoked_serial_numbers"])
186 : {
187 8 : if (revokedSerialNumber.asString() == serialNumberHexStr)
188 : {
189 5 : return true;
190 : }
191 : }
192 : }
193 14 : return false;
194 22 : }
195 :
196 29 : CHIP_ERROR TestDACRevocationDelegateImpl::GetKeyIDHexStr(const ByteSpan & certDer, std::string & outKeyIDHexStr,
197 : KeyIdType keyIdType)
198 : {
199 : static_assert(kAuthorityKeyIdentifierLength == kSubjectKeyIdentifierLength, "AKID and SKID length mismatch");
200 :
201 : uint8_t keyIdBuf[kAuthorityKeyIdentifierLength];
202 29 : MutableByteSpan keyId(keyIdBuf);
203 :
204 29 : switch (keyIdType)
205 : {
206 22 : case KeyIdType::kAKID:
207 22 : ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, keyId));
208 22 : break;
209 :
210 7 : case KeyIdType::kSKID:
211 7 : ReturnErrorOnFailure(ExtractSKIDFromX509Cert(certDer, keyId));
212 7 : break;
213 :
214 0 : default:
215 0 : return CHIP_ERROR_INVALID_ARGUMENT;
216 : }
217 :
218 29 : return BytesToHexStr(keyId, outKeyIDHexStr);
219 : }
220 :
221 22 : CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, std::string & outAKIDHexStr)
222 : {
223 22 : return GetKeyIDHexStr(certDer, outAKIDHexStr, KeyIdType::kAKID);
224 : }
225 :
226 7 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSKIDHexStr(const ByteSpan & certDer, std::string & outSKIDHexStr)
227 : {
228 7 : return GetKeyIDHexStr(certDer, outSKIDHexStr, KeyIdType::kSKID);
229 : }
230 :
231 22 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan & certDer, std::string & outSerialNumberHexStr)
232 : {
233 22 : uint8_t serialNumberBuf[kMaxCertificateSerialNumberLength] = { 0 };
234 22 : MutableByteSpan serialNumber(serialNumberBuf);
235 :
236 22 : ReturnErrorOnFailure(ExtractSerialNumberFromX509Cert(certDer, serialNumber));
237 22 : return BytesToHexStr(serialNumber, outSerialNumberHexStr);
238 : }
239 :
240 30 : CHIP_ERROR TestDACRevocationDelegateImpl::GetRDNBase64Str(const ByteSpan & certDer, std::string & outRDNBase64String,
241 : RDNType rdnType)
242 : {
243 30 : uint8_t rdnBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
244 30 : MutableByteSpan rdn(rdnBuf);
245 :
246 30 : switch (rdnType)
247 : {
248 22 : case RDNType::kIssuer:
249 22 : ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, rdn));
250 22 : break;
251 :
252 8 : case RDNType::kSubject:
253 8 : ReturnErrorOnFailure(ExtractSubjectFromX509Cert(certDer, rdn));
254 7 : break;
255 :
256 0 : default:
257 0 : return CHIP_ERROR_INVALID_ARGUMENT;
258 : }
259 :
260 : // calculate the b64 length needed
261 29 : size_t b64LenNeeded = BASE64_ENCODED_LEN(rdn.size());
262 :
263 : // Ensure string has enough capacity for base64 encoded data
264 29 : outRDNBase64String.resize(b64LenNeeded);
265 :
266 29 : uint16_t encodedLen = Base64Encode(rdn.data(), static_cast<uint16_t>(rdn.size()), &outRDNBase64String[0]);
267 29 : outRDNBase64String.resize(encodedLen);
268 :
269 29 : return CHIP_NO_ERROR;
270 : }
271 :
272 22 : CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer, std::string & outIssuerNameBase64String)
273 : {
274 22 : return GetRDNBase64Str(certDer, outIssuerNameBase64String, RDNType::kIssuer);
275 : }
276 :
277 8 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSubjectNameBase64Str(const ByteSpan & certDer,
278 : std::string & outSubjectNameBase64String)
279 : {
280 8 : return GetRDNBase64Str(certDer, outSubjectNameBase64String, RDNType::kSubject);
281 : }
282 :
283 : // @param certDer Certificate, in DER format, to check for revocation
284 22 : bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
285 : {
286 22 : std::string serialNumber;
287 22 : std::string akid;
288 22 : std::string issuerName;
289 :
290 22 : VerifyOrReturnValue(CHIP_NO_ERROR == GetIssuerNameBase64Str(certDer, issuerName), false);
291 22 : ChipLogDetail(NotSpecified, "Issuer: %.*s", static_cast<int>(issuerName.size()), issuerName.data());
292 :
293 22 : VerifyOrReturnValue(CHIP_NO_ERROR == GetSerialNumberHexStr(certDer, serialNumber), false);
294 22 : ChipLogDetail(NotSpecified, "Serial Number: %.*s", static_cast<int>(serialNumber.size()), serialNumber.data());
295 :
296 22 : VerifyOrReturnValue(CHIP_NO_ERROR == GetAKIDHexStr(certDer, akid), false);
297 22 : ChipLogDetail(NotSpecified, "AKID: %.*s", static_cast<int>(akid.size()), akid.data());
298 :
299 22 : return IsEntryInRevocationSet(akid, issuerName, serialNumber);
300 22 : }
301 :
302 13 : void TestDACRevocationDelegateImpl::CheckForRevokedDACChain(
303 : const DeviceAttestationVerifier::AttestationInfo & info,
304 : Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion)
305 : {
306 13 : AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess;
307 :
308 13 : if (mDeviceAttestationRevocationSetPath.empty() && mRevocationData.empty())
309 : {
310 2 : onCompletion->mCall(onCompletion->mContext, info, attestationError);
311 2 : return;
312 : }
313 :
314 11 : ChipLogDetail(NotSpecified, "Checking for revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
315 :
316 11 : if (IsCertificateRevoked(info.dacDerBuffer))
317 : {
318 3 : ChipLogProgress(NotSpecified, "Found revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
319 3 : attestationError = AttestationVerificationResult::kDacRevoked;
320 : }
321 :
322 11 : ChipLogDetail(NotSpecified, "Checking for revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
323 :
324 11 : if (IsCertificateRevoked(info.paiDerBuffer))
325 : {
326 2 : ChipLogProgress(NotSpecified, "Found revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
327 :
328 2 : if (attestationError == AttestationVerificationResult::kDacRevoked)
329 : {
330 1 : attestationError = AttestationVerificationResult::kPaiAndDacRevoked;
331 : }
332 : else
333 : {
334 1 : attestationError = AttestationVerificationResult::kPaiRevoked;
335 : }
336 : }
337 :
338 11 : onCompletion->mCall(onCompletion->mContext, info, attestationError);
339 : }
340 :
341 : } // namespace Credentials
342 : } // namespace chip
|