Skip to content

Commit ad949c7

Browse files
authored
Merge branch 'master' into esp32/add_thread_br_ota
2 parents c13b4f4 + 593861d commit ad949c7

File tree

61 files changed

+2218
-1616
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2218
-1616
lines changed

.github/PULL_REQUEST_TEMPLATE.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
1+
2+
#### Testing
3+
14
> !!!!!!!!!! Please delete the instructions below and replace with PR
2-
> description
5+
> description above.
36
>
47
> If you have an issue number, please use a syntax of `Fixes #12345` and a brief
58
> change description
69
>
710
> If you do not have an issue number, please have a good description of the
811
> problem and the fix. Help the reviewer understand what to expect.
912
>
10-
> Complete/append to the `### Testing` section below, to describe how testing
13+
> Complete/append to the `### Testing` section above, to describe how testing
1114
> was done. See
1215
> <https://github.com/project-chip/connectedhomeip/blob/master/CONTRIBUTING.md#pull-requests>
1316
>
1417
> Make sure you delete these instructions (to prove you have read them).
1518
>
1619
> !!!!!!!!!! Instructions end
17-
18-
#### Testing

.github/workflows/build.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,8 @@ jobs:
363363
python -m ensurepip --upgrade
364364
python -m pip install -r scripts/setup/requirements.setuppayload.txt
365365
python3 src/setup_payload/tests/run_python_setup_payload_test.py out/chip-tool
366+
- name: Run revocation set generation tests
367+
run: scripts/run_in_build_env.sh 'python3 -m unittest -v credentials/generate_revocation_set.py'
366368

367369
build_linux_python_lighting_device:
368370
name: Build on Linux (python lighting-app)

credentials/generate-revocation-set.py credentials/generate_revocation_set.py

+202-49
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import base64
2424
import json
2525
import logging
26+
import os
2627
import subprocess
2728
import sys
29+
import unittest
2830
from enum import Enum
2931
from typing import Optional
3032

@@ -199,6 +201,98 @@ def fetch_crl_from_url(url: str, timeout: int) -> x509.CertificateRevocationList
199201
logging.error('Failed to fetch a valid CRL')
200202

201203

204+
def generate_revocation_set_from_crl(crl_file: x509.CertificateRevocationList,
205+
crl_signer_certificate: x509.Certificate,
206+
certificate_authority_name_b64: str,
207+
certificate_akid_hex: str,
208+
crl_signer_delegator_cert: x509.Certificate) -> dict:
209+
"""Generate a revocation set from a CRL file.
210+
211+
Args:
212+
crl_file: The CRL object containing revoked certificates
213+
crl_signer_certificate: The certificate object used to sign the CRL
214+
certificate_authority_name_b64: Base64 encoded issuer name
215+
certificate_akid_hex: Hex encoded Authority Key Identifier
216+
crl_signer_delegator_cert: crl signer delegator certificate object
217+
218+
Returns:
219+
dict: A dictionary containing the revocation set data with fields:
220+
- type: "revocation_set"
221+
- issuer_subject_key_id: Authority Key Identifier (hex)
222+
- issuer_name: Issuer name (base64)
223+
- revoked_serial_numbers: List of revoked serial numbers
224+
- crl_signer_cert: CRL signer certificate (base64 DER)
225+
- crl_signer_delegator: Optional delegator certificate (base64 DER)
226+
"""
227+
serialnumber_list = []
228+
229+
for revoked_cert in crl_file:
230+
try:
231+
cert_issuer_entry_ext = revoked_cert.extensions.get_extension_for_oid(x509.CRLEntryExtensionOID.CERTIFICATE_ISSUER)
232+
revoked_cert_issuer = cert_issuer_entry_ext.value.get_values_for_type(x509.DirectoryName)[0].public_bytes()
233+
revoked_cert_issuer_b64 = base64.b64encode(revoked_cert_issuer).decode('utf-8')
234+
235+
if revoked_cert_issuer_b64 is not None:
236+
# check if this really are the same thing
237+
if revoked_cert_issuer_b64 != certificate_authority_name_b64:
238+
logging.warning("CRL Issuer is not CRL File Issuer, continue...")
239+
continue
240+
except Exception:
241+
pass
242+
243+
serialnumber_list.append(bytes(str('{:02X}'.format(revoked_cert.serial_number)), 'utf-8').decode('utf-8'))
244+
245+
entry = {
246+
"type": "revocation_set",
247+
"issuer_subject_key_id": certificate_akid_hex,
248+
"issuer_name": certificate_authority_name_b64,
249+
"revoked_serial_numbers": serialnumber_list,
250+
"crl_signer_cert": base64.b64encode(crl_signer_certificate.public_bytes(serialization.Encoding.DER)).decode('utf-8'),
251+
}
252+
253+
if crl_signer_delegator_cert:
254+
entry["crl_signer_delegator"] = base64.b64encode(
255+
crl_signer_delegator_cert.public_bytes(serialization.Encoding.DER)).decode('utf-8')
256+
257+
return entry
258+
259+
260+
# This is implemented as per point (9) in 6.2.4.1. Conceptual algorithm for revocation set construction
261+
def get_certificate_authority_details(crl_signer_certificate: x509.Certificate,
262+
crl_signer_delegator_cert: x509.Certificate,
263+
paa_certificate_object: x509.Certificate,
264+
is_paa: bool) -> tuple[str, str]:
265+
"""Get certificate authority name and AKID based on certificate hierarchy.
266+
267+
Args:
268+
crl_signer_certificate: The CRL signer certificate
269+
crl_signer_delegator_cert: Optional delegator certificate
270+
paa_certificate_object: Optional PAA certificate
271+
is_paa: Whether this is a PAA certificate
272+
273+
Returns:
274+
tuple[str, str]: (certificate_authority_name_b64, certificate_akid_hex)
275+
"""
276+
if is_paa and not is_self_signed_certificate(crl_signer_certificate):
277+
cert_for_details = paa_certificate_object
278+
logging.debug("Using PAA certificate for details")
279+
elif crl_signer_delegator_cert:
280+
cert_for_details = crl_signer_delegator_cert
281+
logging.debug("Using CRL Signer Delegator certificate for details")
282+
else:
283+
cert_for_details = crl_signer_certificate
284+
logging.debug("Using CRL Signer certificate for details")
285+
286+
certificate_authority_name_b64 = get_subject_b64(cert_for_details)
287+
certificate_akid = get_skid(cert_for_details)
288+
certificate_akid_hex = ''.join('{:02X}'.format(x) for x in certificate_akid)
289+
290+
logging.debug(f"Certificate Authority Name: {certificate_authority_name_b64}")
291+
logging.debug(f"Certificate AKID: {certificate_akid_hex}")
292+
293+
return certificate_authority_name_b64, certificate_akid_hex
294+
295+
202296
class DCLDClient:
203297
'''
204298
A client for interacting with DCLD using either the REST API or command line interface (CLI).
@@ -325,7 +419,7 @@ def get_issuer_cert(self, cert: x509.Certificate) -> Optional[x509.Certificate]:
325419

326420
return issuer_certificate_object
327421

328-
def get_revocations_points_by_skid(self, issuer_subject_key_id) -> list[dict]:
422+
def get_revocations_points_by_skid(self, issuer_subject_key_id: str) -> list[dict]:
329423
'''
330424
Get revocation points by subject key ID
331425
@@ -349,7 +443,12 @@ def get_revocations_points_by_skid(self, issuer_subject_key_id) -> list[dict]:
349443
return response["pkiRevocationDistributionPointsByIssuerSubjectKeyID"]["points"]
350444

351445

352-
@click.command()
446+
@click.group()
447+
def cli():
448+
pass
449+
450+
451+
@cli.command('from-dcl')
353452
@click.help_option('-h', '--help')
354453
@optgroup.group('Input data sources', cls=RequiredMutuallyExclusiveOptionGroup)
355454
@optgroup.option('--use-main-net-dcld', type=str, default='', metavar='PATH', help="Location of `dcld` binary, to use `dcld` for mirroring MainNet.")
@@ -362,9 +461,8 @@ def get_revocations_points_by_skid(self, issuer_subject_key_id) -> list[dict]:
362461
@optgroup.option('--log-level', default='INFO', show_default=True, type=click.Choice(__LOG_LEVELS__.keys(),
363462
case_sensitive=False), callback=lambda c, p, v: __LOG_LEVELS__[v],
364463
help='Determines the verbosity of script output')
365-
def main(use_main_net_dcld: str, use_test_net_dcld: str, use_main_net_http: bool, use_test_net_http: bool, output: str, log_level: str):
366-
"""Tool to construct revocation set from DCL"""
367-
464+
def from_dcl(use_main_net_dcld, use_test_net_dcld, use_main_net_http, use_test_net_http, output, log_level):
465+
"""Generate revocation set from DCL"""
368466
logging.basicConfig(
369467
level=log_level,
370468
format='%(asctime)s %(name)s %(levelname)-7s %(message)s',
@@ -467,65 +565,120 @@ def main(use_main_net_dcld: str, use_test_net_dcld: str, use_main_net_http: bool
467565
# TODO: 8. Validate CRL as per Section 6.3 of RFC 5280
468566

469567
# 9. decide on certificate authority name and AKID
470-
if revocation_point["isPAA"] and not is_self_signed_certificate(crl_signer_certificate):
471-
certificate_authority_name_b64 = get_subject_b64(paa_certificate_object)
472-
certificate_akid = get_skid(paa_certificate_object)
473-
elif crl_signer_delegator_cert:
474-
certificate_authority_name_b64 = get_subject_b64(crl_signer_delegator_cert)
475-
certificate_akid = get_skid(crl_signer_delegator_cert)
476-
else:
477-
certificate_authority_name_b64 = get_subject_b64(crl_signer_certificate)
478-
certificate_akid = get_skid(crl_signer_certificate)
568+
certificate_authority_name_b64, certificate_akid_hex = get_certificate_authority_details(
569+
crl_signer_certificate, crl_signer_delegator_cert, paa_certificate_object, revocation_point["isPAA"])
479570

480571
# validate issuer skid matchces with the one in revocation points
481-
certificate_akid_hex = ''.join('{:02X}'.format(x) for x in certificate_akid)
482-
483-
logging.debug(f"Certificate Authority Name: {certificate_authority_name_b64}")
484-
logging.debug(f"Certificate AKID: {certificate_akid_hex}")
485572
logging.debug(f"revocation_point['issuerSubjectKeyID']: {revocation_point['issuerSubjectKeyID']}")
486573

487574
if revocation_point["issuerSubjectKeyID"] != certificate_akid_hex:
488575
logging.warning("CRL Issuer Subject Key ID is not CRL Signer Subject Key ID, continue...")
489576
continue
490577

491-
serialnumber_list = []
492578
# 10. Iterate through the Revoked Certificates List
493-
for revoked_cert in crl_file:
494-
try:
495-
revoked_cert_issuer = revoked_cert.extensions.get_extension_for_oid(
496-
x509.CRLEntryExtensionOID.CERTIFICATE_ISSUER).value.get_values_for_type(x509.DirectoryName).value
497-
498-
if revoked_cert_issuer is not None:
499-
# check if this really are the same thing
500-
if revoked_cert_issuer != certificate_authority_name_b64:
501-
logging.warning("CRL Issuer is not CRL File Issuer, continue...")
502-
continue
503-
except Exception:
504-
logging.warning("certificateIssuer entry extension not found in CRL")
505-
pass
506-
507-
serialnumber_list.append(bytes(str('{:02X}'.format(revoked_cert.serial_number)), 'utf-8').decode('utf-8'))
508-
509-
entry = {
510-
"type": "revocation_set",
511-
"issuer_subject_key_id": certificate_akid_hex,
512-
"issuer_name": certificate_authority_name_b64,
513-
"revoked_serial_numbers": serialnumber_list,
514-
"crl_signer_cert": base64.b64encode(crl_signer_certificate.public_bytes(serialization.Encoding.DER)).decode('utf-8'),
515-
}
516-
517-
if crl_signer_delegator_cert:
518-
entry["crl_signer_delegator"] = base64.b64encode(
519-
crl_signer_delegator_cert.public_bytes(serialization.Encoding.DER)).decode('utf-8'),
579+
entry = generate_revocation_set_from_crl(crl_file, crl_signer_certificate,
580+
certificate_authority_name_b64, certificate_akid_hex, crl_signer_delegator_cert)
520581
logging.debug(f"Entry to append: {entry}")
521582
revocation_set.append(entry)
522583

523584
with open(output, 'w+') as outfile:
524585
json.dump(revocation_set, outfile, indent=4)
525586

526587

588+
@cli.command('from-crl')
589+
@click.option('--crl', required=True, type=click.File('rb'), help='Path to the CRL file')
590+
@click.option('--crl-signer', required=True, type=click.File('rb'), help='Path to the signer certificate')
591+
@click.option('--delegator', type=click.File('rb'), help='Path to the delegator certificate (optional)')
592+
@click.option('--paa', type=click.File('rb'), help='Path to the PAA certificate (optional)')
593+
@click.option('--output', default='revocation_set.json', type=click.File('w'), help='Output filename (default: revocation_set.json)')
594+
@click.option('--is-paa', default=False, is_flag=True, help='Indicates if the CRL issuer is the PAA')
595+
def from_crl(crl, crl_signer, delegator, paa, output, is_paa):
596+
"""Generate revocation set from a single CRL file"""
597+
crl = x509.load_pem_x509_crl(crl.read())
598+
crl_signer = x509.load_pem_x509_certificate(crl_signer.read())
599+
delegator = x509.load_pem_x509_certificate(delegator.read()) if delegator else None
600+
paa = x509.load_pem_x509_certificate(paa.read()) if paa else None
601+
602+
ca_name_b64, ca_akid_hex = get_certificate_authority_details(crl_signer, delegator, paa, is_paa)
603+
revocation_set = generate_revocation_set_from_crl(crl, crl_signer, ca_name_b64, ca_akid_hex, delegator)
604+
output.write(json.dumps([revocation_set], indent=4))
605+
606+
607+
class TestRevocationSetGeneration(unittest.TestCase):
608+
"""Test class for revocation set generation"""
609+
610+
def setUp(self):
611+
# Get the directory containing this file
612+
self.test_base_dir = os.path.dirname(os.path.abspath(__file__))
613+
614+
def get_test_file_path(self, filename):
615+
return os.path.join(self.test_base_dir, 'test', filename)
616+
617+
def compare_revocation_sets(self, generated_set, expected_file):
618+
with open(os.path.join(self.test_base_dir, expected_file), 'r') as f:
619+
expected_set = json.load(f)
620+
621+
# Compare the contents
622+
self.assertEqual(len([generated_set]), len(expected_set))
623+
expected = expected_set[0]
624+
625+
# Compare required fields
626+
self.assertEqual(generated_set['type'], expected['type'])
627+
self.assertEqual(generated_set['issuer_subject_key_id'], expected['issuer_subject_key_id'])
628+
self.assertEqual(generated_set['issuer_name'], expected['issuer_name'])
629+
self.assertEqual(set(generated_set['revoked_serial_numbers']), set(expected['revoked_serial_numbers']))
630+
self.assertEqual(generated_set['crl_signer_cert'], expected['crl_signer_cert'])
631+
632+
# Compare optional fields if present in either set
633+
if 'crl_signer_delegator' in generated_set and 'crl_signer_delegator' in expected:
634+
self.assertEqual(generated_set['crl_signer_delegator'], expected['crl_signer_delegator'],
635+
"CRL signer delegator certificates do not match")
636+
elif 'crl_signer_delegator' in generated_set or 'crl_signer_delegator' in expected:
637+
self.fail("CRL signer delegator certificate is missing in one of the sets")
638+
639+
def test_paa_revocation_set(self):
640+
"""Test generation of PAA revocation set"""
641+
with open(self.get_test_file_path('revoked-attestation-certificates/Chip-Test-PAA-FFF1-CRL.pem'), 'rb') as f:
642+
crl = x509.load_pem_x509_crl(f.read())
643+
with open(self.get_test_file_path('revoked-attestation-certificates/Chip-Test-PAA-FFF1-Cert.pem'), 'rb') as f:
644+
crl_signer = x509.load_pem_x509_certificate(f.read())
645+
646+
ca_name_b64, ca_akid_hex = get_certificate_authority_details(
647+
crl_signer, None, None, True)
648+
revocation_set = generate_revocation_set_from_crl(
649+
crl, crl_signer, ca_name_b64, ca_akid_hex, None)
650+
651+
self.compare_revocation_sets(
652+
revocation_set,
653+
'test/revoked-attestation-certificates/revocation-sets/revocation-set-for-paa.json'
654+
)
655+
656+
def test_pai_revocation_set(self):
657+
"""Test generation of PAI revocation set"""
658+
with open(self.get_test_file_path('revoked-attestation-certificates/Matter-Development-PAI-FFF1-noPID-CRL.pem'), 'rb') as f:
659+
crl = x509.load_pem_x509_crl(f.read())
660+
with open(self.get_test_file_path('revoked-attestation-certificates/Matter-Development-PAI-FFF1-noPID-Cert.pem'), 'rb') as f:
661+
crl_signer = x509.load_pem_x509_certificate(f.read())
662+
with open(self.get_test_file_path('revoked-attestation-certificates/Chip-Test-PAA-FFF1-Cert.pem'), 'rb') as f:
663+
paa = x509.load_pem_x509_certificate(f.read())
664+
665+
ca_name_b64, ca_akid_hex = get_certificate_authority_details(
666+
crl_signer, None, paa, False)
667+
revocation_set = generate_revocation_set_from_crl(
668+
crl, crl_signer, ca_name_b64, ca_akid_hex, None)
669+
670+
self.compare_revocation_sets(
671+
revocation_set,
672+
'test/revoked-attestation-certificates/revocation-sets/revocation-set-for-pai.json'
673+
)
674+
675+
527676
if __name__ == "__main__":
528-
if len(sys.argv) == 1:
529-
main.main(['--help'])
677+
if len(sys.argv) > 1 and sys.argv[1] == 'test':
678+
# Remove the 'test' argument and run tests
679+
sys.argv.pop(1)
680+
unittest.main()
681+
elif len(sys.argv) == 1:
682+
cli.main(['--help'])
530683
else:
531-
main()
684+
cli()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
[
2+
{
3+
"type": "revocation_set",
4+
"issuer_subject_key_id": "6AFD22771F511FECBF1641976710DCDC31A1717E",
5+
"issuer_name": "MDAxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBQTEUMBIGCisGAQQBgqJ8AgEMBEZGRjE=",
6+
"revoked_serial_numbers": ["302664392B8A3F2A"],
7+
"crl_signer_cert": "MIIBvTCCAWSgAwIBAgIITqjoMYLUHBwwCgYIKoZIzj0EAwIwMDEYMBYGA1UEAwwPTWF0dGVyIFRlc3QgUEFBMRQwEgYKKwYBBAGConwCAQwERkZGMTAgFw0yMTA2MjgxNDIzNDNaGA85OTk5MTIzMTIzNTk1OVowMDEYMBYGA1UEAwwPTWF0dGVyIFRlc3QgUEFBMRQwEgYKKwYBBAGConwCAQwERkZGMTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABLbLY3KIfyko9brIGqnZOuJDHK2p154kL2UXfvnO2TKijs0Duq9qj8oYShpQNUKWDUU/MD8fGUIddR6Pjxqam3WjZjBkMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRq/SJ3H1Ef7L8WQZdnENzcMaFxfjAfBgNVHSMEGDAWgBRq/SJ3H1Ef7L8WQZdnENzcMaFxfjAKBggqhkjOPQQDAgNHADBEAiBQqoAC9NkyqaAFOPZTaK0P/8jvu8m+t9pWmDXPmqdRDgIgI7rI/g8j51RFtlM5CBpHmUkpxyqvChVI1A0DTVFLJd4="
8+
}
9+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[
2+
{
3+
"type": "revocation_set",
4+
"issuer_subject_key_id": "63540E47F64B1C38D13884A462D16C195D8FFB3C",
5+
"issuer_name": "MD0xJTAjBgNVBAMMHE1hdHRlciBEZXYgUEFJIDB4RkZGMSBubyBQSUQxFDASBgorBgEEAYKifAIBDARGRkYx",
6+
"revoked_serial_numbers": [
7+
"AB042494323FE54",
8+
"19367D978EAC533A",
9+
"2569383D24BB36EA"
10+
],
11+
"crl_signer_cert": "MIIByzCCAXGgAwIBAgIIVq2CIq2UW2QwCgYIKoZIzj0EAwIwMDEYMBYGA1UEAwwPTWF0dGVyIFRlc3QgUEFBMRQwEgYKKwYBBAGConwCAQwERkZGMTAgFw0yMjAyMDUwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowPTElMCMGA1UEAwwcTWF0dGVyIERldiBQQUkgMHhGRkYxIG5vIFBJRDEUMBIGCisGAQQBgqJ8AgEMBEZGRjEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARBmpMVwhc+DIyHbQPM/JRIUmR/f+xeUIL0BZko7KiUxZQVEwmsYx5MsDOSr2hLC6+35ls7gWLC9Sv5MbjneqqCo2YwZDASBgNVHRMBAf8ECDAGAQH/AgEAMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUY1QOR/ZLHDjROISkYtFsGV2P+zwwHwYDVR0jBBgwFoAUav0idx9RH+y/FkGXZxDc3DGhcX4wCgYIKoZIzj0EAwIDSAAwRQIhALLvJ/Sa6bUPuR7qyUxNC9u415KcbLiPrOUpNo0SBUwMAiBlXckrhr2QmIKmxiF3uCXX0F7b58Ivn+pxIg5+pwP4kQ=="
12+
}
13+
]

examples/contact-sensor-app/bouffalolab/data_model/contact-sensor-app.matter

+2-2
Original file line numberDiff line numberDiff line change
@@ -2235,12 +2235,12 @@ endpoint 0 {
22352235
callback attribute clientsSupportedPerFabric;
22362236
ram attribute userActiveModeTriggerHint default = 0x100;
22372237
ram attribute userActiveModeTriggerInstruction default = "Push setup button for Active Mode";
2238-
ram attribute operatingMode default = 0;
2238+
callback attribute operatingMode;
22392239
callback attribute maximumCheckInBackOff;
22402240
callback attribute generatedCommandList;
22412241
callback attribute acceptedCommandList;
22422242
callback attribute attributeList;
2243-
ram attribute featureMap default = 0x000f;
2243+
callback attribute featureMap;
22442244
ram attribute clusterRevision default = 3;
22452245

22462246
handle command RegisterClient;

0 commit comments

Comments
 (0)