diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CaController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CaController.php index 72e21c039..d0947365b 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CaController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CaController.php @@ -127,15 +127,8 @@ class CaController extends ApiMutableModelControllerBase } } $certmdl = new Cert(); - foreach ($certmdl->cert->iterateItems() as $cert) { - $x509_2 = openssl_x509_parse((string)$cert->crt_payload); - if ($x509_2 !== false) { - if ($this->compare_issuer($x509_2['issuer'], $x509['subject'])) { - $cert->caref = (string)$node->refid; - } - } - } - $certmdl->serializeToConfig(); + $certmdl->linkCaRefs(null, $this->getModel()); + $certmdl->serializeToConfig(false, true); /* we need to force save to maintain integrity */ } } break; diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php index 45b305c7a..bf43cb938 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php @@ -110,6 +110,7 @@ class CertController extends ApiMutableModelControllerBase $error = gettext('Invalid private key provided'); } } + $this->getModel()->linkCaRefs($node->refid); break; case 'import_csr': if (CertStore::parseX509((string)$node->crt_payload) === false) { diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.php b/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.php index a203e9d5e..c075275ca 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.php +++ b/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.php @@ -37,4 +37,57 @@ use OPNsense\Base\Messages\Message; */ class Cert extends BaseModel { + /** + * compare subject to issuer (subject fits within issuer DN) + * @param array $subject to find + * @param array $issuer to match on + * @return bool + */ + private function compare_issuer(array $subject, array $issuer): bool + { + return empty(array_diff( + array_map('serialize', $subject), + array_map('serialize', $issuer) + )); + } + + /** + * link certificates to ca's in our trust store (based on issuer) + * @param string $refid optional certificate reference id to link + * @param null|Ca $ca_mdl optional Ca model to use, comstructs one when not offered + * @return void + */ + public function linkCaRefs(?string $refid=null, mixed $ca_mdl=null): void + { + $ca_subjects = []; + foreach ($this->cert->iterateItems() as $cert) { + if ($refid != null && $cert->refid != $refid) { + continue; + } + $cert_x509 = openssl_x509_parse((string)$cert->crt_payload); + if ($cert_x509 === false) { + continue; + } + if (empty($ca_subjects)) { + /* collect on first item for matching */ + $mdl = $ca_mdl == null ? new Ca() : $ca_mdl; + foreach ($mdl->ca->iterateItems() as $ca) { + $x509 = openssl_x509_parse((string)$ca->crt_payload); + if ($x509 === false) { + continue; + } + /* add sort key, longer paths should match earlier. e.g. NL,ZH should precede NL */ + $key = sprintf('%04d-%s', count($x509['subject']), $ca->refid); + $ca_subjects[$key] = ['subject' => $x509['subject'], 'caref' => (string)$ca->refid]; + } + krsort($ca_subjects); + } + foreach ($ca_subjects as $caref => $item) { + if ($this->compare_issuer($item['subject'], $cert_x509['issuer'])) { + $cert->caref = $item['caref']; + break; + } + } + } + } }