|
9 | 9 | from openwisp_controller.config import settings as app_settings |
10 | 10 | from openwisp_utils.base import TimeStampedEditableModel |
11 | 11 |
|
| 12 | +MAC_ADDRESS_OID = "1.3.6.1.4.1.65901.1" |
| 13 | +DEVICE_UUID_OID = "1.3.6.1.4.1.65901.2" |
| 14 | + |
12 | 15 |
|
13 | 16 | class AbstractDeviceCertificate(TimeStampedEditableModel): |
14 | 17 | config = models.ForeignKey( |
@@ -84,63 +87,69 @@ def _get_common_name(self): |
84 | 87 | common_name = f"{common_name}-{unique_slug}" |
85 | 88 | return common_name |
86 | 89 |
|
87 | | - def _auto_create_cert(self, name, common_name): |
88 | | - """ |
89 | | - Automatically creates and assigns a client x509 certificate |
90 | | - using Blueprint cloning and custom hardware OID injection. |
91 | | - """ |
| 90 | + def _build_cert(self, name, common_name): |
| 91 | + """Build (but do not save) a Cert instance from template + blueprint.""" |
92 | 92 | ca = self.template.ca |
93 | 93 | blueprint = self.template.blueprint_cert |
94 | | - device = self.config.device |
95 | 94 | cert_model = self.__class__.cert.field.related_model |
96 | 95 |
|
97 | | - # blueprint property cloning with CA fallback |
98 | | - key_length = blueprint.key_length if blueprint else ca.key_length |
99 | | - digest = blueprint.digest if blueprint else str(ca.digest) |
100 | | - country_code = blueprint.country_code if blueprint else ca.country_code |
101 | | - state = blueprint.state if blueprint else ca.state |
102 | | - city = blueprint.city if blueprint else ca.city |
103 | | - organization_name = ( |
104 | | - blueprint.organization_name if blueprint else ca.organization_name |
| 96 | + attrs = self._clone_blueprint_attrs(ca, blueprint) |
| 97 | + extensions = self._build_extensions(blueprint) |
| 98 | + cert = cert_model( |
| 99 | + name=name, |
| 100 | + ca=ca, |
| 101 | + common_name=common_name, |
| 102 | + extensions=extensions, |
| 103 | + **attrs, |
| 104 | + ) |
| 105 | + return self._auto_create_cert_extra(cert) |
| 106 | + |
| 107 | + def _clone_blueprint_attrs(self, ca, blueprint): |
| 108 | + """ |
| 109 | + Extracts base X.509 attributes (such as key length, digest, and |
| 110 | + location data) from the provided blueprint certificate. |
| 111 | + """ |
| 112 | + source = blueprint or ca |
| 113 | + digest = str(source.digest) if not blueprint else source.digest |
| 114 | + return dict( |
| 115 | + key_length=source.key_length, |
| 116 | + digest=digest, |
| 117 | + country_code=source.country_code, |
| 118 | + state=source.state, |
| 119 | + city=source.city, |
| 120 | + organization_name=source.organization_name, |
| 121 | + email=source.email, |
105 | 122 | ) |
106 | | - email = blueprint.email if blueprint else ca.email |
107 | 123 |
|
| 124 | + def _build_extensions(self, blueprint): |
| 125 | + """Compiles the list of X.509 extensions for the new certificate.""" |
108 | 126 | if blueprint and blueprint.extensions: |
109 | 127 | extensions = copy.deepcopy(blueprint.extensions) |
110 | 128 | else: |
111 | 129 | extensions = [{"name": "nsCertType", "value": "client", "critical": False}] |
| 130 | + extensions.extend(self._get_hardware_oid_extensions()) |
| 131 | + return extensions |
112 | 132 |
|
113 | | - # inject MAC and UUID as custom OIDs, prerequisite: #228 in django-x509 |
114 | | - mac_oid = "1.3.6.1.4.1.65901.1" |
115 | | - uuid_oid = "1.3.6.1.4.1.65901.2" |
116 | | - extensions.extend( |
117 | | - [ |
118 | | - { |
119 | | - "oid": mac_oid, |
120 | | - "value": f"ASN1:UTF8:string:{device.mac_address}", |
121 | | - "critical": False, |
122 | | - }, |
123 | | - { |
124 | | - "oid": uuid_oid, |
125 | | - "value": f"ASN1:UTF8:string:{device.id}", |
126 | | - "critical": False, |
127 | | - }, |
128 | | - ] |
129 | | - ) |
130 | | - cert = cert_model( |
131 | | - name=name, |
132 | | - ca=ca, |
133 | | - key_length=key_length, |
134 | | - digest=digest, |
135 | | - country_code=country_code, |
136 | | - state=state, |
137 | | - city=city, |
138 | | - organization_name=organization_name, |
139 | | - email=email, |
140 | | - common_name=common_name, |
141 | | - extensions=extensions, |
142 | | - ) |
143 | | - cert = self._auto_create_cert_extra(cert) |
| 133 | + def _get_hardware_oid_extensions(self): |
| 134 | + device = self.config.device |
| 135 | + return [ |
| 136 | + { |
| 137 | + "oid": MAC_ADDRESS_OID, |
| 138 | + "value": f"ASN1:UTF8:string:{device.mac_address}", |
| 139 | + "critical": False, |
| 140 | + }, |
| 141 | + { |
| 142 | + "oid": DEVICE_UUID_OID, |
| 143 | + "value": f"ASN1:UTF8:string:{device.id}", |
| 144 | + "critical": False, |
| 145 | + }, |
| 146 | + ] |
| 147 | + |
| 148 | + def _auto_create_cert(self, name, common_name): |
| 149 | + """ |
| 150 | + Automatically creates and assigns a client x509 certificate |
| 151 | + """ |
| 152 | + cert = self._build_cert(name=name, common_name=common_name) |
144 | 153 | cert.full_clean() |
145 | 154 | cert.save() |
146 | 155 | self.cert = cert |
|
0 commit comments