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 55 : CHIP_ERROR BytesToHexStr(const ByteSpan & bytes, std::string & outHexStr)
36 : {
37 55 : size_t hexLength = bytes.size() * 2;
38 55 : outHexStr.resize(hexLength);
39 :
40 55 : Encoding::HexFlags flags = Encoding::HexFlags::kUppercase;
41 55 : 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 12 : CHIP_ERROR TestDACRevocationDelegateImpl::SetDeviceAttestationRevocationData(const std::string & jsonData)
54 : {
55 12 : mRevocationData = jsonData;
56 12 : 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 12 : void TestDACRevocationDelegateImpl::ClearDeviceAttestationRevocationData()
66 : {
67 12 : mRevocationData.clear();
68 12 : }
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 24 : bool TestDACRevocationDelegateImpl::IsEntryInRevocationSet(const std::string & akidHexStr, const std::string & issuerNameBase64Str,
127 : const std::string & serialNumberHexStr)
128 : {
129 24 : Json::Value jsonData;
130 :
131 : // Try direct data first, then fall back to file
132 24 : if (!mRevocationData.empty())
133 : {
134 24 : std::string errs;
135 48 : std::istringstream jsonStream(!mRevocationData.empty() ? mRevocationData : "[]");
136 24 : 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 26 : }
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 : ChipLogProgress(NotSpecified, "No revocation data available");
164 : // No revocation data available
165 0 : return false;
166 : }
167 :
168 22 : VerifyOrReturnValue(jsonData.isArray(), false, ChipLogError(NotSpecified, "Revocation set is not a valid JSON Array"));
169 :
170 : // 6.2.4.2. Determining Revocation Status of an Entity
171 37 : for (const auto & revokedSet : jsonData)
172 : {
173 29 : VerifyOrReturnValue(revokedSet.isObject(), false,
174 : ChipLogError(NotSpecified, "Revocation set entry is not a valid JSON object"));
175 :
176 23 : if (revokedSet["issuer_name"].asString() != issuerNameBase64Str)
177 : {
178 13 : continue;
179 : }
180 10 : if (revokedSet["issuer_subject_key_id"].asString() != akidHexStr)
181 : {
182 2 : continue;
183 : }
184 :
185 : // 4.a cross validate PAI with crl signer OR crl signer delegator
186 : // 4.b cross validate DAC with crl signer OR crl signer delegator
187 8 : VerifyOrReturnValue(CrossValidateCert(revokedSet, akidHexStr, issuerNameBase64Str), false);
188 :
189 : // 4.c check if serial number is revoked
190 10 : for (const auto & revokedSerialNumber : revokedSet["revoked_serial_numbers"])
191 : {
192 8 : if (revokedSerialNumber.asString() == serialNumberHexStr)
193 : {
194 5 : return true;
195 : }
196 : }
197 : }
198 14 : return false;
199 24 : }
200 :
201 31 : CHIP_ERROR TestDACRevocationDelegateImpl::GetKeyIDHexStr(const ByteSpan & certDer, std::string & outKeyIDHexStr,
202 : KeyIdType keyIdType)
203 : {
204 : static_assert(kAuthorityKeyIdentifierLength == kSubjectKeyIdentifierLength, "AKID and SKID length mismatch");
205 :
206 : uint8_t keyIdBuf[kAuthorityKeyIdentifierLength];
207 31 : MutableByteSpan keyId(keyIdBuf);
208 :
209 31 : switch (keyIdType)
210 : {
211 24 : case KeyIdType::kAKID:
212 24 : ReturnErrorOnFailure(ExtractAKIDFromX509Cert(certDer, keyId));
213 24 : break;
214 :
215 7 : case KeyIdType::kSKID:
216 7 : ReturnErrorOnFailure(ExtractSKIDFromX509Cert(certDer, keyId));
217 7 : break;
218 :
219 0 : default:
220 0 : return CHIP_ERROR_INVALID_ARGUMENT;
221 : }
222 :
223 31 : return BytesToHexStr(keyId, outKeyIDHexStr);
224 : }
225 :
226 24 : CHIP_ERROR TestDACRevocationDelegateImpl::GetAKIDHexStr(const ByteSpan & certDer, std::string & outAKIDHexStr)
227 : {
228 24 : return GetKeyIDHexStr(certDer, outAKIDHexStr, KeyIdType::kAKID);
229 : }
230 :
231 7 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSKIDHexStr(const ByteSpan & certDer, std::string & outSKIDHexStr)
232 : {
233 7 : return GetKeyIDHexStr(certDer, outSKIDHexStr, KeyIdType::kSKID);
234 : }
235 :
236 24 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSerialNumberHexStr(const ByteSpan & certDer, std::string & outSerialNumberHexStr)
237 : {
238 24 : uint8_t serialNumberBuf[kMaxCertificateSerialNumberLength] = { 0 };
239 24 : MutableByteSpan serialNumber(serialNumberBuf);
240 :
241 24 : ReturnErrorOnFailure(ExtractSerialNumberFromX509Cert(certDer, serialNumber));
242 24 : return BytesToHexStr(serialNumber, outSerialNumberHexStr);
243 : }
244 :
245 32 : CHIP_ERROR TestDACRevocationDelegateImpl::GetRDNBase64Str(const ByteSpan & certDer, std::string & outRDNBase64String,
246 : RDNType rdnType)
247 : {
248 32 : uint8_t rdnBuf[kMaxCertificateDistinguishedNameLength] = { 0 };
249 32 : MutableByteSpan rdn(rdnBuf);
250 :
251 32 : switch (rdnType)
252 : {
253 24 : case RDNType::kIssuer:
254 24 : ReturnErrorOnFailure(ExtractIssuerFromX509Cert(certDer, rdn));
255 24 : break;
256 :
257 8 : case RDNType::kSubject:
258 8 : ReturnErrorOnFailure(ExtractSubjectFromX509Cert(certDer, rdn));
259 7 : break;
260 :
261 0 : default:
262 0 : return CHIP_ERROR_INVALID_ARGUMENT;
263 : }
264 :
265 : // calculate the b64 length needed
266 31 : size_t b64LenNeeded = BASE64_ENCODED_LEN(rdn.size());
267 :
268 : // Ensure string has enough capacity for base64 encoded data
269 31 : outRDNBase64String.resize(b64LenNeeded);
270 :
271 31 : uint16_t encodedLen = Base64Encode(rdn.data(), static_cast<uint16_t>(rdn.size()), &outRDNBase64String[0]);
272 31 : outRDNBase64String.resize(encodedLen);
273 :
274 31 : return CHIP_NO_ERROR;
275 : }
276 :
277 24 : CHIP_ERROR TestDACRevocationDelegateImpl::GetIssuerNameBase64Str(const ByteSpan & certDer, std::string & outIssuerNameBase64String)
278 : {
279 24 : return GetRDNBase64Str(certDer, outIssuerNameBase64String, RDNType::kIssuer);
280 : }
281 :
282 8 : CHIP_ERROR TestDACRevocationDelegateImpl::GetSubjectNameBase64Str(const ByteSpan & certDer,
283 : std::string & outSubjectNameBase64String)
284 : {
285 8 : return GetRDNBase64Str(certDer, outSubjectNameBase64String, RDNType::kSubject);
286 : }
287 :
288 : // @param certDer Certificate, in DER format, to check for revocation
289 24 : bool TestDACRevocationDelegateImpl::IsCertificateRevoked(const ByteSpan & certDer)
290 : {
291 24 : std::string serialNumber;
292 24 : std::string akid;
293 24 : std::string issuerName;
294 :
295 24 : VerifyOrReturnValue(CHIP_NO_ERROR == GetIssuerNameBase64Str(certDer, issuerName), false);
296 24 : ChipLogDetail(NotSpecified, "Issuer: %s", NullTerminated(issuerName.data(), issuerName.size()).c_str());
297 :
298 24 : VerifyOrReturnValue(CHIP_NO_ERROR == GetSerialNumberHexStr(certDer, serialNumber), false);
299 24 : ChipLogDetail(NotSpecified, "Serial Number: %s", NullTerminated(serialNumber.data(), serialNumber.size()).c_str());
300 :
301 24 : VerifyOrReturnValue(CHIP_NO_ERROR == GetAKIDHexStr(certDer, akid), false);
302 24 : ChipLogDetail(NotSpecified, "AKID: %s", NullTerminated(akid.data(), akid.size()).c_str());
303 :
304 24 : return IsEntryInRevocationSet(akid, issuerName, serialNumber);
305 24 : }
306 :
307 14 : void TestDACRevocationDelegateImpl::CheckForRevokedDACChain(
308 : const DeviceAttestationVerifier::AttestationInfo & info,
309 : Callback::Callback<DeviceAttestationVerifier::OnAttestationInformationVerification> * onCompletion)
310 : {
311 14 : AttestationVerificationResult attestationError = AttestationVerificationResult::kSuccess;
312 :
313 14 : if (mDeviceAttestationRevocationSetPath.empty() && mRevocationData.empty())
314 : {
315 2 : ChipLogProgress(NotSpecified, "WARNING: No revocation information available. Revocation checks will be skipped!");
316 2 : onCompletion->mCall(onCompletion->mContext, info, attestationError);
317 2 : return;
318 : }
319 :
320 12 : ChipLogDetail(NotSpecified, "Checking for revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
321 :
322 12 : if (IsCertificateRevoked(info.dacDerBuffer))
323 : {
324 3 : ChipLogProgress(NotSpecified, "Found revoked DAC in %s", mDeviceAttestationRevocationSetPath.c_str());
325 3 : attestationError = AttestationVerificationResult::kDacRevoked;
326 : }
327 :
328 12 : ChipLogDetail(NotSpecified, "Checking for revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
329 :
330 12 : if (IsCertificateRevoked(info.paiDerBuffer))
331 : {
332 2 : ChipLogProgress(NotSpecified, "Found revoked PAI in %s", mDeviceAttestationRevocationSetPath.c_str());
333 :
334 2 : if (attestationError == AttestationVerificationResult::kDacRevoked)
335 : {
336 1 : attestationError = AttestationVerificationResult::kPaiAndDacRevoked;
337 : }
338 : else
339 : {
340 1 : attestationError = AttestationVerificationResult::kPaiRevoked;
341 : }
342 : }
343 :
344 12 : onCompletion->mCall(onCompletion->mContext, info, attestationError);
345 : }
346 :
347 : } // namespace Credentials
348 : } // namespace chip
|