Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2020 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 : /**
19 : * @file
20 : * The implementation of the Setup Payload. Currently only needed to
21 : * verify the validity of a Setup Payload
22 : */
23 :
24 : #include "SetupPayload.h"
25 :
26 : #include <crypto/CHIPCryptoPAL.h>
27 : #include <lib/core/CHIPCore.h>
28 : #include <lib/core/CHIPVendorIdentifiers.hpp>
29 : #include <lib/core/TLV.h>
30 : #include <lib/core/TLVData.h>
31 : #include <lib/core/TLVUtilities.h>
32 : #include <lib/support/CodeUtils.h>
33 : #include <setup_payload/ManualSetupPayloadParser.h>
34 : #include <setup_payload/QRCodeSetupPayloadParser.h>
35 : #include <utility>
36 :
37 : namespace chip {
38 :
39 : // Check the Setup Payload for validity
40 : //
41 : // `vendor_id` and `product_id` are allowed all of uint16_t
42 64 : bool PayloadContents::isValidQRCodePayload(ValidationMode mode) const
43 : {
44 : // 3-bit value specifying the QR code payload version.
45 64 : VerifyOrReturnValue(version < (1 << kVersionFieldLengthInBits), false);
46 :
47 63 : VerifyOrReturnValue(static_cast<uint8_t>(commissioningFlow) < (1 << kCommissioningFlowFieldLengthInBits), false);
48 :
49 : // Device Commissioning Flow
50 : // Even in ValidationMode::kConsume we can only handle modes that we understand.
51 : // 0: Standard commissioning flow: such a device, when uncommissioned, always enters commissioning mode upon power-up, subject
52 : // to the rules in [ref_Announcement_Commencement]. 1: User-intent commissioning flow: user action required to enter
53 : // commissioning mode. 2: Custom commissioning flow: interaction with a vendor-specified means is needed before commissioning.
54 : // 3: Reserved
55 62 : VerifyOrReturnValue(commissioningFlow == CommissioningFlow::kStandard ||
56 : commissioningFlow == CommissioningFlow::kUserActionRequired ||
57 : commissioningFlow == CommissioningFlow::kCustom,
58 : false);
59 :
60 : // General discriminator validity is enforced by the SetupDiscriminator class, but it can't be short for QR a code.
61 62 : VerifyOrReturnValue(!discriminator.IsShortDiscriminator(), false);
62 :
63 : // RendevouzInformation must be present for a QR code.
64 60 : VerifyOrReturnValue(rendezvousInformation.HasValue(), false);
65 54 : if (mode == ValidationMode::kProduce)
66 : {
67 52 : chip::RendezvousInformationFlags valid(RendezvousInformationFlag::kBLE, RendezvousInformationFlag::kOnNetwork,
68 : RendezvousInformationFlag::kSoftAP, RendezvousInformationFlag::kWiFiPAF,
69 : RendezvousInformationFlag::kNFC);
70 52 : VerifyOrReturnValue(rendezvousInformation.Value().HasOnly(valid), false);
71 : }
72 :
73 53 : return CheckPayloadCommonConstraints();
74 : }
75 :
76 14 : bool PayloadContents::isValidManualCode(ValidationMode mode) const
77 : {
78 : // No additional constraints apply to Manual Pairing Codes.
79 : // (If the payload has a long discriminator it will be converted automatically.)
80 14 : return CheckPayloadCommonConstraints();
81 : }
82 :
83 70 : bool PayloadContents::IsValidSetupPIN(uint32_t setupPIN)
84 : {
85 : // SHALL be restricted to the values 0x0000001 to 0x5F5E0FE (00000001 to 99999998 in decimal), excluding the invalid Passcode
86 : // values.
87 70 : if (setupPIN == kSetupPINCodeUndefinedValue || setupPIN > kSetupPINCodeMaximumValue || setupPIN == 11111111 ||
88 64 : setupPIN == 22222222 || setupPIN == 33333333 || setupPIN == 44444444 || setupPIN == 55555555 || setupPIN == 66666666 ||
89 64 : setupPIN == 77777777 || setupPIN == 88888888 || setupPIN == 12345678 || setupPIN == 87654321)
90 : {
91 6 : return false;
92 : }
93 :
94 64 : return true;
95 : }
96 :
97 67 : bool PayloadContents::CheckPayloadCommonConstraints() const
98 : {
99 : // Validation rules in this method apply to all validation modes.
100 :
101 : // Even in ValidationMode::kConsume we don't understand how to handle any payload version other than 0.
102 67 : VerifyOrReturnValue(version == 0, false);
103 :
104 67 : VerifyOrReturnValue(IsValidSetupPIN(setUpPINCode), false);
105 :
106 : // VendorID must be unspecified (0) or in valid range expected.
107 61 : VerifyOrReturnValue((vendorID == VendorId::Unspecified) || IsVendorIdValidOperationally(vendorID), false);
108 :
109 : // A value of 0x0000 SHALL NOT be assigned to a product since Product ID = 0x0000 is used for these specific cases:
110 : // * To announce an anonymized Product ID as part of device discovery
111 : // * To indicate an OTA software update file applies to multiple Product IDs equally.
112 : // * To avoid confusion when presenting the Onboarding Payload for ECM with multiple nodes
113 : // In these special cases the vendorID must be 0 (Unspecified)
114 61 : VerifyOrReturnValue(productID != 0 || vendorID == VendorId::Unspecified, false);
115 :
116 61 : return true;
117 : }
118 :
119 40 : bool PayloadContents::operator==(const PayloadContents & input) const
120 : {
121 40 : return (this->version == input.version && this->vendorID == input.vendorID && this->productID == input.productID &&
122 80 : this->commissioningFlow == input.commissioningFlow && this->rendezvousInformation == input.rendezvousInformation &&
123 120 : this->discriminator == input.discriminator && this->setUpPINCode == input.setUpPINCode);
124 : }
125 :
126 11 : CHIP_ERROR SetupPayload::addOptionalVendorData(uint8_t tag, std::string data)
127 : {
128 11 : OptionalQRCodeInfo info;
129 11 : info.tag = tag;
130 11 : info.type = optionalQRCodeInfoTypeString;
131 11 : info.data = std::move(data);
132 :
133 11 : return addOptionalVendorData(info);
134 11 : }
135 :
136 6 : CHIP_ERROR SetupPayload::addOptionalVendorData(uint8_t tag, int32_t data)
137 : {
138 6 : OptionalQRCodeInfo info;
139 6 : info.tag = tag;
140 6 : info.type = optionalQRCodeInfoTypeInt32;
141 6 : info.int32 = data;
142 :
143 6 : return addOptionalVendorData(info);
144 6 : }
145 :
146 86 : std::vector<OptionalQRCodeInfo> SetupPayload::getAllOptionalVendorData() const
147 : {
148 86 : std::vector<OptionalQRCodeInfo> returnedOptionalInfo;
149 108 : for (auto & entry : optionalVendorData)
150 : {
151 22 : returnedOptionalInfo.push_back(entry.second);
152 : }
153 86 : return returnedOptionalInfo;
154 : }
155 :
156 4 : CHIP_ERROR SetupPayload::removeOptionalVendorData(uint8_t tag)
157 : {
158 4 : VerifyOrReturnError(optionalVendorData.find(tag) != optionalVendorData.end(), CHIP_ERROR_KEY_NOT_FOUND);
159 2 : optionalVendorData.erase(tag);
160 :
161 2 : return CHIP_NO_ERROR;
162 : }
163 :
164 7 : CHIP_ERROR SetupPayload::addSerialNumber(std::string serialNumber)
165 : {
166 7 : OptionalQRCodeInfoExtension info;
167 7 : info.tag = kSerialNumberTag;
168 7 : info.type = optionalQRCodeInfoTypeString;
169 7 : info.data = std::move(serialNumber);
170 :
171 7 : return addOptionalExtensionData(info);
172 7 : }
173 :
174 2 : CHIP_ERROR SetupPayload::addSerialNumber(uint32_t serialNumber)
175 : {
176 2 : OptionalQRCodeInfoExtension info;
177 2 : info.tag = kSerialNumberTag;
178 2 : info.type = optionalQRCodeInfoTypeUInt32;
179 2 : info.uint32 = serialNumber;
180 :
181 2 : return addOptionalExtensionData(info);
182 2 : }
183 :
184 4 : CHIP_ERROR SetupPayload::getSerialNumber(std::string & outSerialNumber) const
185 : {
186 4 : CHIP_ERROR err = CHIP_NO_ERROR;
187 4 : OptionalQRCodeInfoExtension info;
188 4 : ReturnErrorOnFailure(getOptionalExtensionData(kSerialNumberTag, info));
189 :
190 2 : switch (info.type)
191 : {
192 1 : case (optionalQRCodeInfoTypeString):
193 1 : outSerialNumber = info.data;
194 1 : break;
195 1 : case (optionalQRCodeInfoTypeUInt32):
196 1 : outSerialNumber = std::to_string(info.uint32);
197 1 : break;
198 0 : default:
199 0 : err = CHIP_ERROR_INVALID_ARGUMENT;
200 0 : break;
201 : }
202 :
203 2 : return err;
204 4 : }
205 :
206 3 : CHIP_ERROR SetupPayload::removeSerialNumber()
207 : {
208 3 : VerifyOrReturnError(optionalExtensionData.find(kSerialNumberTag) != optionalExtensionData.end(), CHIP_ERROR_KEY_NOT_FOUND);
209 1 : optionalExtensionData.erase(kSerialNumberTag);
210 :
211 1 : return CHIP_NO_ERROR;
212 : }
213 :
214 1 : CHIP_ERROR SetupPayload::generateRandomSetupPin(uint32_t & setupPINCode)
215 : {
216 1 : uint8_t retries = 0;
217 1 : const uint8_t maxRetries = 10;
218 :
219 : do
220 : {
221 1 : ReturnErrorOnFailure(Crypto::DRBG_get_bytes(reinterpret_cast<uint8_t *>(&setupPINCode), sizeof(setupPINCode)));
222 :
223 : // Passcodes shall be restricted to the values 00000001 to 99999998 in decimal, see 5.1.1.6
224 : // TODO: Consider revising this method to ensure uniform distribution of setup PIN codes
225 1 : setupPINCode = (setupPINCode % kSetupPINCodeMaximumValue) + 1;
226 :
227 : // Make sure that the Generated Setup Pin code is not one of the invalid passcodes/pin codes defined in the
228 : // specification.
229 1 : if (IsValidSetupPIN(setupPINCode))
230 : {
231 1 : return CHIP_NO_ERROR;
232 : }
233 :
234 0 : retries++;
235 : // We got pretty unlucky with the random number generator, Just try again.
236 : // This shouldn't take many retries assuming DRBG_get_bytes is not broken.
237 0 : } while (retries < maxRetries);
238 :
239 0 : return CHIP_ERROR_INTERNAL;
240 : }
241 :
242 21 : CHIP_ERROR SetupPayload::addOptionalVendorData(const OptionalQRCodeInfo & info)
243 : {
244 21 : VerifyOrReturnError(IsVendorTag(info.tag), CHIP_ERROR_INVALID_ARGUMENT);
245 19 : optionalVendorData[info.tag] = info;
246 :
247 19 : return CHIP_NO_ERROR;
248 : }
249 :
250 12 : CHIP_ERROR SetupPayload::addOptionalExtensionData(const OptionalQRCodeInfoExtension & info)
251 : {
252 12 : VerifyOrReturnError(IsCommonTag(info.tag), CHIP_ERROR_INVALID_ARGUMENT);
253 12 : optionalExtensionData[info.tag] = info;
254 :
255 12 : return CHIP_NO_ERROR;
256 : }
257 :
258 4 : CHIP_ERROR SetupPayload::getOptionalVendorData(uint8_t tag, OptionalQRCodeInfo & info) const
259 : {
260 4 : const auto it = optionalVendorData.find(tag);
261 4 : VerifyOrReturnError(it != optionalVendorData.end(), CHIP_ERROR_KEY_NOT_FOUND);
262 4 : info = it->second;
263 :
264 4 : return CHIP_NO_ERROR;
265 : }
266 :
267 7 : CHIP_ERROR SetupPayload::getOptionalExtensionData(uint8_t tag, OptionalQRCodeInfoExtension & info) const
268 : {
269 7 : const auto it = optionalExtensionData.find(tag);
270 7 : VerifyOrReturnError(it != optionalExtensionData.end(), CHIP_ERROR_KEY_NOT_FOUND);
271 5 : info = it->second;
272 5 : return CHIP_NO_ERROR;
273 : }
274 :
275 3 : optionalQRCodeInfoType SetupPayload::getNumericTypeFor(uint8_t tag) const
276 : {
277 3 : optionalQRCodeInfoType elemType = optionalQRCodeInfoTypeUnknown;
278 :
279 3 : if (IsVendorTag(tag))
280 : {
281 2 : elemType = optionalQRCodeInfoTypeInt32;
282 : }
283 1 : else if (tag == kSerialNumberTag)
284 : {
285 1 : elemType = optionalQRCodeInfoTypeUInt32;
286 : }
287 :
288 3 : return elemType;
289 : }
290 :
291 79 : std::vector<OptionalQRCodeInfoExtension> SetupPayload::getAllOptionalExtensionData() const
292 : {
293 79 : std::vector<OptionalQRCodeInfoExtension> returnedOptionalInfo;
294 94 : for (auto & entry : optionalExtensionData)
295 : {
296 15 : returnedOptionalInfo.push_back(entry.second);
297 : }
298 79 : return returnedOptionalInfo;
299 : }
300 :
301 38 : bool SetupPayload::operator==(const SetupPayload & input) const
302 : {
303 38 : std::vector<OptionalQRCodeInfo> inputOptionalVendorData;
304 38 : std::vector<OptionalQRCodeInfoExtension> inputOptionalExtensionData;
305 :
306 38 : VerifyOrReturnError(PayloadContents::operator==(input), false);
307 :
308 37 : inputOptionalVendorData = input.getAllOptionalVendorData();
309 37 : VerifyOrReturnError(optionalVendorData.size() == inputOptionalVendorData.size(), false);
310 :
311 41 : for (const OptionalQRCodeInfo & inputInfo : inputOptionalVendorData)
312 : {
313 4 : OptionalQRCodeInfo info;
314 4 : CHIP_ERROR err = getOptionalVendorData(inputInfo.tag, info);
315 4 : VerifyOrReturnError(err == CHIP_NO_ERROR, false);
316 4 : VerifyOrReturnError(inputInfo.type == info.type, false);
317 4 : VerifyOrReturnError(inputInfo.data == info.data, false);
318 4 : VerifyOrReturnError(inputInfo.int32 == info.int32, false);
319 4 : }
320 :
321 37 : inputOptionalExtensionData = input.getAllOptionalExtensionData();
322 37 : VerifyOrReturnError(optionalExtensionData.size() == inputOptionalExtensionData.size(), false);
323 :
324 40 : for (const OptionalQRCodeInfoExtension & inputInfo : inputOptionalExtensionData)
325 : {
326 3 : OptionalQRCodeInfoExtension info;
327 3 : CHIP_ERROR err = getOptionalExtensionData(inputInfo.tag, info);
328 3 : VerifyOrReturnError(err == CHIP_NO_ERROR, false);
329 3 : VerifyOrReturnError(inputInfo.type == info.type, false);
330 3 : VerifyOrReturnError(inputInfo.data == info.data, false);
331 3 : VerifyOrReturnError(inputInfo.int32 == info.int32, false);
332 3 : VerifyOrReturnError(inputInfo.int64 == info.int64, false);
333 3 : VerifyOrReturnError(inputInfo.uint32 == info.uint32, false);
334 3 : VerifyOrReturnError(inputInfo.uint64 == info.uint64, false);
335 3 : }
336 :
337 37 : return true;
338 38 : }
339 :
340 9 : CHIP_ERROR SetupPayload::FromStringRepresentation(std::string stringRepresentation, std::vector<SetupPayload> & outPayloads)
341 : {
342 : // We're going to assume that in practice all these allocations are small
343 : // enough that allocation failure will not happen. If that ever turns out
344 : // to not be the case, we may need to figure out how to handle that.
345 :
346 : // std::string::starts_with is C++20, sadly.
347 9 : bool isQRCode = (stringRepresentation.rfind(kQRCodePrefix, 0) == 0);
348 9 : if (!isQRCode)
349 : {
350 4 : outPayloads.clear();
351 4 : auto & payload = outPayloads.emplace_back();
352 4 : ReturnErrorOnFailure(ManualSetupPayloadParser(stringRepresentation).populatePayload(payload));
353 2 : VerifyOrReturnError(payload.isValidManualCode(), CHIP_ERROR_INVALID_ARGUMENT);
354 1 : return CHIP_NO_ERROR;
355 : }
356 :
357 5 : ReturnErrorOnFailure(QRCodeSetupPayloadParser(stringRepresentation).populatePayloads(outPayloads));
358 :
359 11 : for (auto & entry : outPayloads)
360 : {
361 9 : VerifyOrReturnError(entry.isValidQRCodePayload(), CHIP_ERROR_INVALID_ARGUMENT);
362 : }
363 2 : return CHIP_NO_ERROR;
364 : }
365 :
366 : } // namespace chip
|