blob: 3bcd74b6534e26afeb1c7db612dd3dfe86fc8558 [file] [log] [blame]
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -04001#!/usr/bin/python
2
3#
4# Copyright (c) 2022 Project CHIP Authors
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#
18
19# Script that was used to fetch CHIP Development Product Attestation Authority (PAA)
20# certificates from DCL.
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -050021# For usage please run:.
22# python ./credentials/fetch-paa-certs-from-dcl.py --help
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040023
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040024import copy
Arkadiusz Bokowyd5bfb4b2023-01-09 16:15:00 +010025import os
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040026import re
Arkadiusz Bokowyd5bfb4b2023-01-09 16:15:00 +010027import subprocess
28import sys
Arkadiusz Bokowyd5bfb4b2023-01-09 16:15:00 +010029
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -050030import click
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -050031import requests
Arkadiusz Bokowyd5bfb4b2023-01-09 16:15:00 +010032from click_option_group import RequiredMutuallyExclusiveOptionGroup, optgroup
33from cryptography import x509
34from cryptography.hazmat.primitives import serialization
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040035
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -050036PRODUCTION_NODE_URL = "https://on.dcl.csa-iot.org:26657"
37PRODUCTION_NODE_URL_REST = "https://on.dcl.csa-iot.org"
38TEST_NODE_URL_REST = "https://on.test-net.dcl.csa-iot.org"
Vijay Selvaraja63f8262022-10-26 13:24:04 -040039
C Freeman81436152024-06-18 13:05:51 -040040MATTER_CERT_CA_SUBJECT = "MFIxDDAKBgNVBAoMA0NTQTEsMCoGA1UEAwwjTWF0dGVyIENlcnRpZmljYXRpb24gYW5kIFRlc3RpbmcgQ0ExFDASBgorBgEEAYKifAIBDARDNUEw"
41MATTER_CERT_CA_SUBJECT_KEY_ID = "97:E4:69:D0:C5:04:14:C2:6F:C7:01:F7:7E:94:77:39:09:8D:F6:A5"
42
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040043
44def parse_paa_root_certs(cmdpipe, paa_list):
45 """
46 example output of a query to all x509 root certs in DCL:
47
48 certs:
Vijay Selvarajeaa0c942022-07-11 08:53:01 -040049 - subject: MCExHzAdBgNVBAMMFk1hdHRlciBEZXZlbG9wbWVudCBQQUE=
50 subjectKeyId: FA:92:CF:09:5E:FA:42:E1:14:30:65:16:32:FE:FE:1B:2C:77:A7:C8
51 - subject: MDAxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBQTEUMBIGCisGAQQBgqJ8AgEMBDEyNUQ=
52 subjectKeyId: E2:90:8D:36:9C:3C:A3:C1:13:BB:09:E2:4D:C1:CC:C5:A6:66:91:D4
53 - subject: MEsxCzAJBgNVBAYTAlVTMQ8wDQYDVQQKDAZHb29nbGUxFTATBgNVBAMMDE1hdHRlciBQQUEgMTEUMBIGCisGAQQBgqJ8AgEMBDYwMDY=
54 subjectKeyId: B0:00:56:81:B8:88:62:89:62:80:E1:21:18:A1:A8:BE:09:DE:93:21
55 - subject: MFUxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjEtMCsGA1UEAxMkRGlnaUNlcnQgVEVTVCBSb290IENBIGZvciBNQVRURVIgUEtJ
56 subjectKeyId: C0:E0:64:15:00:EC:67:E2:7C:AF:7C:6E:2D:49:94:C7:73:DE:B7:BA
57 - subject: MDAxLjAsBgNVBAMMJU5vbiBQcm9kdWN0aW9uIE9OTFkgLSBYRk4gUEFBIENsYXNzIDM=
58 subjectKeyId: F8:99:A9:D5:AD:71:71:E4:C3:81:7F:14:10:7F:78:F0:D9:F7:62:E9
59 - subject: MEIxGDAWBgNVBAMMD01hdHRlciBUZXN0IFBBQTEQMA4GA1UECgwHU2Ftc3VuZzEUMBIGCisGAQQBgqJ8AgEMBDEwRTE=
60 subjectKeyId: CF:9E:0A:16:78:8B:40:30:EC:DD:AB:34:B9:C2:EC:7B:E5:34:55:C0
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040061
62 Brief:
63 This method will search for the first line that contains ': ' char sequence.
64 From there, it assumes every 2 lines contain subject and subject key id info of
65 a valid PAA root certificate.
66 The paa_list parameter will contain a list of all valid PAA Root certificates
67 from DCL.
68 """
69
70 result = {}
71
72 while True:
73 line = cmdpipe.stdout.readline()
74 if not line:
75 break
76 else:
77 if b': ' in line:
78 key, value = line.split(b': ')
C Freeman81436152024-06-18 13:05:51 -040079 result[key.strip(b' -').decode("utf-8")
80 ] = value.strip().decode("utf-8")
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040081 parse_paa_root_certs.counter += 1
82 if parse_paa_root_certs.counter % 2 == 0:
83 paa_list.append(copy.deepcopy(result))
84
85
C Freeman81436152024-06-18 13:05:51 -040086def write_cert(certificate, subject):
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -050087 filename = 'dcld_mirror_' + \
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -040088 re.sub('[^a-zA-Z0-9_-]', '', re.sub('[=, ]', '_', subject))
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -050089 with open(filename + '.pem', 'w+') as outfile:
90 outfile.write(certificate)
91 # convert pem file to der
Tennessee Carmel-Veilleuxbe99e032023-06-01 15:23:59 -040092 try:
93 with open(filename + '.pem', 'rb') as infile:
94 pem_certificate = x509.load_pem_x509_certificate(infile.read())
95 with open(filename + '.der', 'wb+') as outfile:
96 der_certificate = pem_certificate.public_bytes(
97 serialization.Encoding.DER)
98 outfile.write(der_certificate)
99 except (IOError, ValueError) as e:
C Freeman81436152024-06-18 13:05:51 -0400100 print(
101 f"ERROR: Failed to convert {filename + '.pem'}: {str(e)}. Skipping...")
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500102
103
104def parse_paa_root_cert_from_dcld(cmdpipe):
105 subject = None
106 certificate = ""
107
108 while True:
109 line = cmdpipe.stdout.readline()
110 if not line:
111 break
112 else:
113 if b'pemCert: |' in line:
114 while True:
115 line = cmdpipe.stdout.readline()
116 certificate += line.strip(b' \t').decode("utf-8")
117 if b'-----END CERTIFICATE-----' in line:
118 break
119 if b'subjectAsText:' in line:
120 subject = line.split(b': ')[1].strip().decode("utf-8")
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400121 break
Vijay Selvarajeaa0c942022-07-11 08:53:01 -0400122
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500123 return (certificate, subject)
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400124
125
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500126def use_dcld(dcld, production, cmdlist):
127 return [dcld] + cmdlist + (['--node', PRODUCTION_NODE_URL] if production else [])
128
129
130@click.command()
131@click.help_option('-h', '--help')
132@optgroup.group('Input data sources', cls=RequiredMutuallyExclusiveOptionGroup)
133@optgroup.option('--use-main-net-dcld', type=str, default='', metavar='PATH', help="Location of `dcld` binary, to use `dcld` for mirroring MainNet.")
134@optgroup.option('--use-test-net-dcld', type=str, default='', metavar='PATH', help="Location of `dcld` binary, to use `dcld` for mirroring TestNet.")
135@optgroup.option('--use-main-net-http', is_flag=True, type=str, help="Use RESTful API with HTTPS against public MainNet observer.")
136@optgroup.option('--use-test-net-http', is_flag=True, type=str, help="Use RESTful API with HTTPS against public TestNet observer.")
137@optgroup.group('Optional arguments')
138@optgroup.option('--paa-trust-store-path', default='paa-root-certs', type=str, metavar='PATH', help="PAA trust store path (default: paa-root-certs)")
139def main(use_main_net_dcld, use_test_net_dcld, use_main_net_http, use_test_net_http, paa_trust_store_path):
140 """DCL PAA mirroring tools"""
C Freeman81436152024-06-18 13:05:51 -0400141 fetch_paa_certs(use_main_net_dcld, use_test_net_dcld, use_main_net_http, use_test_net_http, paa_trust_store_path)
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400142
C Freeman81436152024-06-18 13:05:51 -0400143
144def get_cert_from_rest(rest_node_url, subject, subject_key_id):
145 response = requests.get(
146 f"{rest_node_url}/dcl/pki/certificates/{subject}/{subject_key_id}").json()["approvedCertificates"]["certs"][0]
147 certificate = response["pemCert"].rstrip("\n")
148 subject = response["subjectAsText"]
149 return certificate, subject
150
151
152def fetch_cd_signing_certs(store_path):
153 ''' Only supports using main net http currently.'''
154 rest_node_url = PRODUCTION_NODE_URL_REST
155 os.makedirs(store_path, exist_ok=True)
156 original_dir = os.getcwd()
157 os.chdir(store_path)
158
159 cd_signer_ids = requests.get(
160 f"{rest_node_url}/dcl/pki/child-certificates/{MATTER_CERT_CA_SUBJECT}/{MATTER_CERT_CA_SUBJECT_KEY_ID}").json()['childCertificates']['certIds']
161 for signer in cd_signer_ids:
162 subject = signer['subject']
163 subject_key_id = signer['subjectKeyId']
164 certificate, subject = get_cert_from_rest(rest_node_url, subject, subject_key_id)
165
166 print(f"Downloaded CD signing cert with subject: {subject}")
167 write_cert(certificate, subject)
168
169 os.chdir(original_dir)
170
171
172def fetch_paa_certs(use_main_net_dcld, use_test_net_dcld, use_main_net_http, use_test_net_http, paa_trust_store_path):
Vijay Selvaraja63f8262022-10-26 13:24:04 -0400173 production = False
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500174 dcld = use_test_net_dcld
Vijay Selvaraja63f8262022-10-26 13:24:04 -0400175
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500176 if len(use_main_net_dcld) > 0:
177 dcld = use_main_net_dcld
178 production = True
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400179
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500180 use_rest = use_main_net_http or use_test_net_http
181 if use_main_net_http:
182 production = True
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400183
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500184 rest_node_url = PRODUCTION_NODE_URL_REST if production else TEST_NODE_URL_REST
Vijay Selvaraja63f8262022-10-26 13:24:04 -0400185
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500186 os.makedirs(paa_trust_store_path, exist_ok=True)
C Freeman81436152024-06-18 13:05:51 -0400187 original_dir = os.getcwd()
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500188 os.chdir(paa_trust_store_path)
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400189
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500190 if use_rest:
C Freeman81436152024-06-18 13:05:51 -0400191 paa_list = requests.get(
192 f"{rest_node_url}/dcl/pki/root-certificates").json()["approvedRootCertificates"]["certs"]
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500193 else:
194 cmdlist = ['query', 'pki', 'all-x509-root-certs']
195
C Freeman81436152024-06-18 13:05:51 -0400196 cmdpipe = subprocess.Popen(use_dcld(
197 dcld, production, cmdlist), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500198
199 paa_list = []
200 parse_paa_root_certs.counter = 0
201 parse_paa_root_certs(cmdpipe, paa_list)
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400202
203 for paa in paa_list:
C Freeman81436152024-06-18 13:05:51 -0400204 if paa['subject'] == MATTER_CERT_CA_SUBJECT and paa['subjectKeyId'] == MATTER_CERT_CA_SUBJECT_KEY_ID:
205 # Don't include the CD signing cert as a PAA root.
206 continue
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500207 if use_rest:
C Freeman81436152024-06-18 13:05:51 -0400208 certificate, subject = get_cert_from_rest(rest_node_url, paa['subject'], paa['subjectKeyId'])
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500209 else:
C Freeman81436152024-06-18 13:05:51 -0400210 cmdlist = ['query', 'pki', 'x509-cert', '-u',
211 paa['subject'], '-k', paa['subjectKeyId']]
Vijay Selvaraja63f8262022-10-26 13:24:04 -0400212
C Freeman81436152024-06-18 13:05:51 -0400213 cmdpipe = subprocess.Popen(use_dcld(
214 dcld, production, cmdlist), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400215
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500216 (certificate, subject) = parse_paa_root_cert_from_dcld(cmdpipe)
217
218 certificate = certificate.rstrip('\n')
219
C Freeman81436152024-06-18 13:05:51 -0400220 print(f"Downloaded PAA certificate with subject: {subject}")
221 write_cert(certificate, subject)
222
223 os.chdir(original_dir)
Vijay Selvaraj395bb1f2022-03-23 15:36:37 -0400224
225
226if __name__ == "__main__":
Vijay Selvarajb0d92ca2022-11-07 17:01:16 -0500227 if len(sys.argv) == 1:
228 main.main(['--help'])
229 else:
230 main()